mirror of
https://github.com/oxen-io/session-android.git
synced 2025-02-17 12:58:25 +00:00
Menu redesign (#958)
* feat: Menu redesign * Add bottomsheet * Handle default peek height * Smooth out setting peek height * Move contacts prep to util * Dialog layout tweaks * Contact grouping tweaks * Add new message dialog * Add public key input delegate * Add create group dialog * Add join community dialog * Handle dialog back navigation * Enter community url tab tweaks * Scan QR code tab refactor * Scan qr code refactor * Direct and community tabs refactor * Add session id copy context menu item * Set dialog background colours * Set full dialog background colour * Minor tweaks * Add closed group contact search * Cleanup * Add content descriptions * Resize community chips * Fix new conversation screen paddings * Fix fade in/out of join community screen * Prevent creating conversation with empty public key * Resize and position create group loader * Fix back nav after creating direct message conversation * Fix inter-screen transitions * Fix new conversation background colours * Fix background colours * Rename contact list header for clarity * Bug fixes * Enable scrolling of Enter Session ID tab of the new message dialog * Minor refactor * Switch to child fragment manager * Fix member search on create group screen Co-authored-by: charles <charles@oxen.io>
This commit is contained in:
parent
3fcd972c2a
commit
fbd1721eaf
@ -30,6 +30,7 @@ dependencies {
|
|||||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||||
implementation 'com.google.android.material:material:1.2.1'
|
implementation 'com.google.android.material:material:1.2.1'
|
||||||
|
implementation 'com.google.android:flexbox:2.0.1'
|
||||||
implementation 'androidx.legacy:legacy-support-v13:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v13:1.0.0'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation 'androidx.preference:preference-ktx:1.1.1'
|
implementation 'androidx.preference:preference-ktx:1.1.1'
|
||||||
|
@ -22,7 +22,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
|||||||
import androidx.test.filters.LargeTest
|
import androidx.test.filters.LargeTest
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import network.loki.messenger.util.InputBarButtonDrawableMatcher.Companion.inputButtonWithDrawable
|
import network.loki.messenger.util.InputBarButtonDrawableMatcher.Companion.inputButtonWithDrawable
|
||||||
import network.loki.messenger.util.NewConversationButtonDrawableMatcher.Companion.newConversationButtonWithDrawable
|
|
||||||
import org.hamcrest.Matcher
|
import org.hamcrest.Matcher
|
||||||
import org.hamcrest.Matchers.allOf
|
import org.hamcrest.Matchers.allOf
|
||||||
import org.hamcrest.Matchers.not
|
import org.hamcrest.Matchers.not
|
||||||
@ -39,7 +38,6 @@ import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBar
|
|||||||
import org.thoughtcrime.securesms.home.HomeActivity
|
import org.thoughtcrime.securesms.home.HomeActivity
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp
|
import org.thoughtcrime.securesms.mms.GlideApp
|
||||||
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@LargeTest
|
@LargeTest
|
||||||
class HomeActivityTests {
|
class HomeActivityTests {
|
||||||
@ -90,8 +88,8 @@ class HomeActivityTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun goToMyChat() {
|
private fun goToMyChat() {
|
||||||
onView(newConversationButtonWithDrawable(R.drawable.ic_plus)).perform(ViewActions.click())
|
onView(withId(R.id.newConversationButton)).perform(ViewActions.click())
|
||||||
onView(newConversationButtonWithDrawable(R.drawable.ic_message)).perform(ViewActions.click())
|
onView(withId(R.id.createPrivateChatButton)).perform(ViewActions.click())
|
||||||
// new chat
|
// new chat
|
||||||
onView(withId(R.id.publicKeyEditText)).perform(ViewActions.closeSoftKeyboard())
|
onView(withId(R.id.publicKeyEditText)).perform(ViewActions.closeSoftKeyboard())
|
||||||
onView(withId(R.id.copyButton)).perform(ViewActions.click())
|
onView(withId(R.id.copyButton)).perform(ViewActions.click())
|
||||||
|
@ -5,24 +5,6 @@ import androidx.annotation.DrawableRes
|
|||||||
import org.hamcrest.Description
|
import org.hamcrest.Description
|
||||||
import org.hamcrest.TypeSafeMatcher
|
import org.hamcrest.TypeSafeMatcher
|
||||||
import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarButton
|
import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarButton
|
||||||
import org.thoughtcrime.securesms.home.NewConversationButtonSetView
|
|
||||||
|
|
||||||
class NewConversationButtonDrawableMatcher(@DrawableRes private val expectedId: Int): TypeSafeMatcher<View>() {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
@JvmStatic fun newConversationButtonWithDrawable(@DrawableRes expectedId: Int) = NewConversationButtonDrawableMatcher(expectedId)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun describeTo(description: Description?) {
|
|
||||||
description?.appendText("with drawable on button with resource id: $expectedId")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun matchesSafely(item: View): Boolean {
|
|
||||||
if (item !is NewConversationButtonSetView.Button) return false
|
|
||||||
|
|
||||||
return item.getIconID() == expectedId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class InputBarButtonDrawableMatcher(@DrawableRes private val expectedId: Int): TypeSafeMatcher<View>() {
|
class InputBarButtonDrawableMatcher(@DrawableRes private val expectedId: Int): TypeSafeMatcher<View>() {
|
||||||
|
|
||||||
|
@ -140,23 +140,10 @@
|
|||||||
android:name="org.thoughtcrime.securesms.preferences.QRCodeActivity"
|
android:name="org.thoughtcrime.securesms.preferences.QRCodeActivity"
|
||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
android:theme="@style/Theme.Session.DayNight.FlatActionBar" />
|
android:theme="@style/Theme.Session.DayNight.FlatActionBar" />
|
||||||
<activity
|
|
||||||
android:name="org.thoughtcrime.securesms.dms.CreatePrivateChatActivity"
|
|
||||||
android:screenOrientation="portrait"
|
|
||||||
android:windowSoftInputMode="adjustResize"
|
|
||||||
android:theme="@style/Theme.Session.DayNight.FlatActionBar" />
|
|
||||||
<activity
|
|
||||||
android:name="org.thoughtcrime.securesms.groups.CreateClosedGroupActivity"
|
|
||||||
android:screenOrientation="portrait" />
|
|
||||||
<activity
|
<activity
|
||||||
android:name="org.thoughtcrime.securesms.groups.EditClosedGroupActivity"
|
android:name="org.thoughtcrime.securesms.groups.EditClosedGroupActivity"
|
||||||
android:label="@string/activity_edit_closed_group_title"
|
android:label="@string/activity_edit_closed_group_title"
|
||||||
android:screenOrientation="portrait" />
|
android:screenOrientation="portrait" />
|
||||||
<activity
|
|
||||||
android:name="org.thoughtcrime.securesms.groups.JoinPublicChatActivity"
|
|
||||||
android:screenOrientation="portrait"
|
|
||||||
android:windowSoftInputMode="adjustResize"
|
|
||||||
android:theme="@style/Theme.Session.DayNight.FlatActionBar" />
|
|
||||||
<activity
|
<activity
|
||||||
android:name="org.thoughtcrime.securesms.onboarding.SeedActivity"
|
android:name="org.thoughtcrime.securesms.onboarding.SeedActivity"
|
||||||
android:screenOrientation="portrait" />
|
android:screenOrientation="portrait" />
|
||||||
|
@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.thoughtcrime.securesms.components
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewConfiguration
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
|
import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
import kotlin.math.sign
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Layout to wrap a scrollable component inside a ViewPager2. Provided as a solution to the problem
|
||||||
|
* where pages of ViewPager2 have nested scrollable elements that scroll in the same direction as
|
||||||
|
* ViewPager2. The scrollable element needs to be the immediate and only child of this host layout.
|
||||||
|
*
|
||||||
|
* This solution has limitations when using multiple levels of nested scrollable elements
|
||||||
|
* (e.g. a horizontal RecyclerView in a vertical RecyclerView in a horizontal ViewPager2).
|
||||||
|
*/
|
||||||
|
class NestedScrollableHost : FrameLayout {
|
||||||
|
constructor(context: Context) : super(context)
|
||||||
|
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||||
|
|
||||||
|
private var touchSlop = 0
|
||||||
|
private var initialX = 0f
|
||||||
|
private var initialY = 0f
|
||||||
|
private val parentViewPager: ViewPager2?
|
||||||
|
get() {
|
||||||
|
var v: View? = parent as? View
|
||||||
|
while (v != null && v !is ViewPager2) {
|
||||||
|
v = v.parent as? View
|
||||||
|
}
|
||||||
|
return v as? ViewPager2
|
||||||
|
}
|
||||||
|
|
||||||
|
private val child: View? get() = if (childCount > 0) getChildAt(0) else null
|
||||||
|
|
||||||
|
init {
|
||||||
|
touchSlop = ViewConfiguration.get(context).scaledTouchSlop
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun canChildScroll(orientation: Int, delta: Float): Boolean {
|
||||||
|
val direction = -delta.sign.toInt()
|
||||||
|
return when (orientation) {
|
||||||
|
0 -> child?.canScrollHorizontally(direction) ?: false
|
||||||
|
1 -> child?.canScrollVertically(direction) ?: false
|
||||||
|
else -> throw IllegalArgumentException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onInterceptTouchEvent(e: MotionEvent): Boolean {
|
||||||
|
handleInterceptTouchEvent(e)
|
||||||
|
return super.onInterceptTouchEvent(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleInterceptTouchEvent(e: MotionEvent) {
|
||||||
|
val orientation = parentViewPager?.orientation ?: return
|
||||||
|
|
||||||
|
// Early return if child can't scroll in same direction as parent
|
||||||
|
if (!canChildScroll(orientation, -1f) && !canChildScroll(orientation, 1f)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.action == MotionEvent.ACTION_DOWN) {
|
||||||
|
initialX = e.x
|
||||||
|
initialY = e.y
|
||||||
|
parent.requestDisallowInterceptTouchEvent(true)
|
||||||
|
} else if (e.action == MotionEvent.ACTION_MOVE) {
|
||||||
|
val dx = e.x - initialX
|
||||||
|
val dy = e.y - initialY
|
||||||
|
val isVpHorizontal = orientation == ORIENTATION_HORIZONTAL
|
||||||
|
|
||||||
|
// assuming ViewPager2 touch-slop is 2x touch-slop of child
|
||||||
|
val scaledDx = dx.absoluteValue * if (isVpHorizontal) .5f else 1f
|
||||||
|
val scaledDy = dy.absoluteValue * if (isVpHorizontal) 1f else .5f
|
||||||
|
|
||||||
|
if (scaledDx > touchSlop || scaledDy > touchSlop) {
|
||||||
|
if (isVpHorizontal == (scaledDy > scaledDx)) {
|
||||||
|
// Gesture is perpendicular, allow all parents to intercept
|
||||||
|
parent.requestDisallowInterceptTouchEvent(false)
|
||||||
|
} else {
|
||||||
|
// Gesture is parallel, query child if movement in that direction is possible
|
||||||
|
if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) {
|
||||||
|
// Child can scroll, disallow all parents to intercept
|
||||||
|
parent.requestDisallowInterceptTouchEvent(true)
|
||||||
|
} else {
|
||||||
|
// Child cannot scroll, allow all parents to intercept
|
||||||
|
parent.requestDisallowInterceptTouchEvent(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
package org.thoughtcrime.securesms.conversation.start
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import network.loki.messenger.databinding.ContactSectionHeaderBinding
|
||||||
|
import network.loki.messenger.databinding.ViewContactBinding
|
||||||
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
|
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||||
|
|
||||||
|
sealed class ContactListItem {
|
||||||
|
class Header(val name: String) : ContactListItem()
|
||||||
|
class Contact(val recipient: Recipient, val displayName: String) : ContactListItem()
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContactListAdapter(
|
||||||
|
private val context: Context,
|
||||||
|
private val glide: GlideRequests,
|
||||||
|
private val listener: (Recipient) -> Unit
|
||||||
|
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
var items = listOf<ContactListItem>()
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
private object ViewType {
|
||||||
|
const val Contact = 0
|
||||||
|
const val Header = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContactViewHolder(private val binding: ViewContactBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
fun bind(contact: ContactListItem.Contact, glide: GlideRequests, listener: (Recipient) -> Unit) {
|
||||||
|
binding.profilePictureView.root.glide = glide
|
||||||
|
binding.profilePictureView.root.update(contact.recipient)
|
||||||
|
binding.nameTextView.text = contact.displayName
|
||||||
|
binding.root.setOnClickListener { listener(contact.recipient) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unbind() {
|
||||||
|
binding.profilePictureView.root.recycle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HeaderViewHolder(
|
||||||
|
private val binding: ContactSectionHeaderBinding
|
||||||
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
fun bind(item: ContactListItem.Header) {
|
||||||
|
with(binding) {
|
||||||
|
label.text = item.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int {
|
||||||
|
return items.size
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
|
||||||
|
super.onViewRecycled(holder)
|
||||||
|
if (holder is ContactViewHolder) {
|
||||||
|
holder.unbind()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemViewType(position: Int): Int {
|
||||||
|
return when (items[position]) {
|
||||||
|
is ContactListItem.Header -> ViewType.Header
|
||||||
|
else -> ViewType.Contact
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
|
return if (viewType == ViewType.Contact) {
|
||||||
|
ContactViewHolder(
|
||||||
|
ViewContactBinding.inflate(LayoutInflater.from(context), parent, false)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
HeaderViewHolder(
|
||||||
|
ContactSectionHeaderBinding.inflate(LayoutInflater.from(context), parent, false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
|
||||||
|
val item = items[position]
|
||||||
|
if (viewHolder is ContactViewHolder) {
|
||||||
|
viewHolder.bind(item as ContactListItem.Contact, glide, listener)
|
||||||
|
} else if (viewHolder is HeaderViewHolder) {
|
||||||
|
viewHolder.bind(item as ContactListItem.Header)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package org.thoughtcrime.securesms.conversation.start
|
||||||
|
|
||||||
|
interface NewConversationDelegate {
|
||||||
|
fun onNewMessageSelected()
|
||||||
|
fun onCreateGroupSelected()
|
||||||
|
fun onJoinCommunitySelected()
|
||||||
|
fun onContactSelected(address: String)
|
||||||
|
fun onDialogBackPressed()
|
||||||
|
fun onDialogClosePressed()
|
||||||
|
}
|
@ -0,0 +1,99 @@
|
|||||||
|
package org.thoughtcrime.securesms.conversation.start
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.commit
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import network.loki.messenger.R
|
||||||
|
import org.session.libsession.utilities.Address
|
||||||
|
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||||
|
import org.thoughtcrime.securesms.dms.NewMessageFragment
|
||||||
|
import org.thoughtcrime.securesms.groups.CreateGroupFragment
|
||||||
|
import org.thoughtcrime.securesms.groups.JoinCommunityFragment
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class NewConversationFragment : BottomSheetDialogFragment(), NewConversationDelegate {
|
||||||
|
|
||||||
|
private val defaultPeekHeight: Int by lazy { (Resources.getSystem().displayMetrics.heightPixels * 0.94).toInt() }
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
return inflater.inflate(R.layout.fragment_new_conversation, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
replaceFragment(
|
||||||
|
fragment = NewConversationHomeFragment().apply { delegate = this@NewConversationFragment },
|
||||||
|
fragmentKey = NewConversationHomeFragment::class.java.simpleName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
val dialog = BottomSheetDialog(requireContext(), theme)
|
||||||
|
dialog.setOnShowListener {
|
||||||
|
val bottomSheetDialog = it as BottomSheetDialog
|
||||||
|
val parentLayout =
|
||||||
|
bottomSheetDialog.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet)
|
||||||
|
parentLayout?.let { it ->
|
||||||
|
val behaviour = BottomSheetBehavior.from(it)
|
||||||
|
val layoutParams = it.layoutParams
|
||||||
|
layoutParams.height = defaultPeekHeight
|
||||||
|
it.layoutParams = layoutParams
|
||||||
|
behaviour.state = BottomSheetBehavior.STATE_EXPANDED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dialog
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNewMessageSelected() {
|
||||||
|
replaceFragment(NewMessageFragment().apply { delegate = this@NewConversationFragment })
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateGroupSelected() {
|
||||||
|
replaceFragment(CreateGroupFragment().apply { delegate = this@NewConversationFragment })
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onJoinCommunitySelected() {
|
||||||
|
replaceFragment(JoinCommunityFragment().apply { delegate = this@NewConversationFragment })
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onContactSelected(address: String) {
|
||||||
|
val intent = Intent(requireContext(), ConversationActivityV2::class.java)
|
||||||
|
intent.putExtra(ConversationActivityV2.ADDRESS, Address.fromSerialized(address))
|
||||||
|
requireContext().startActivity(intent)
|
||||||
|
requireActivity().overridePendingTransition(R.anim.slide_from_bottom, R.anim.fade_scale_out)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDialogBackPressed() {
|
||||||
|
childFragmentManager.popBackStack()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDialogClosePressed() {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun replaceFragment(fragment: Fragment, fragmentKey: String? = null) {
|
||||||
|
childFragmentManager.commit {
|
||||||
|
setCustomAnimations(
|
||||||
|
R.anim.slide_from_right,
|
||||||
|
R.anim.fade_scale_out,
|
||||||
|
0,
|
||||||
|
R.anim.slide_to_right
|
||||||
|
)
|
||||||
|
replace(R.id.new_conversation_fragment_container, fragment)
|
||||||
|
addToBackStack(fragmentKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
package org.thoughtcrime.securesms.conversation.start
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import network.loki.messenger.R
|
||||||
|
import network.loki.messenger.databinding.FragmentNewConversationHomeBinding
|
||||||
|
import org.session.libsession.messaging.contacts.Contact
|
||||||
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
|
import org.session.libsignal.utilities.PublicKeyValidation
|
||||||
|
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||||
|
import org.thoughtcrime.securesms.mms.GlideApp
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class NewConversationHomeFragment : Fragment() {
|
||||||
|
|
||||||
|
private lateinit var binding: FragmentNewConversationHomeBinding
|
||||||
|
private val viewModel: NewConversationHomeViewModel by viewModels()
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var textSecurePreferences: TextSecurePreferences
|
||||||
|
|
||||||
|
lateinit var delegate: NewConversationDelegate
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
binding = FragmentNewConversationHomeBinding.inflate(inflater)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
binding.closeButton.setOnClickListener { delegate.onDialogClosePressed() }
|
||||||
|
binding.createPrivateChatButton.setOnClickListener { delegate.onNewMessageSelected() }
|
||||||
|
binding.createClosedGroupButton.setOnClickListener { delegate.onCreateGroupSelected() }
|
||||||
|
binding.joinCommunityButton.setOnClickListener { delegate.onJoinCommunitySelected() }
|
||||||
|
val adapter = ContactListAdapter(requireContext(), GlideApp.with(requireContext())) {
|
||||||
|
delegate.onContactSelected(it.address.serialize())
|
||||||
|
}
|
||||||
|
val unknownSectionTitle = getString(R.string.new_conversation_unknown_contacts_section_title)
|
||||||
|
val recipients = viewModel.recipients.value?.filter { !it.isGroupRecipient && it.address.serialize() != textSecurePreferences.getLocalNumber()!! } ?: emptyList()
|
||||||
|
val contactGroups = recipients.map {
|
||||||
|
val sessionId = it.address.serialize()
|
||||||
|
val contact = DatabaseComponent.get(requireContext()).sessionContactDatabase().getContactWithSessionID(sessionId)
|
||||||
|
val displayName = contact?.displayName(Contact.ContactContext.REGULAR) ?: sessionId
|
||||||
|
ContactListItem.Contact(it, displayName)
|
||||||
|
}.sortedBy { it.displayName }
|
||||||
|
.groupBy { if (PublicKeyValidation.isValid(it.displayName)) unknownSectionTitle else it.displayName.first().uppercase() }
|
||||||
|
.toMutableMap()
|
||||||
|
contactGroups.remove(unknownSectionTitle)?.let { contactGroups.put(unknownSectionTitle, it) }
|
||||||
|
adapter.items = contactGroups.flatMap { entry -> listOf(ContactListItem.Header(entry.key)) + entry.value }
|
||||||
|
binding.contactsRecyclerView.adapter = adapter
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package org.thoughtcrime.securesms.conversation.start
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
|
import org.thoughtcrime.securesms.database.ThreadDatabase
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class NewConversationHomeViewModel @Inject constructor(private val threadDb: ThreadDatabase): ViewModel() {
|
||||||
|
|
||||||
|
private val _recipients = MutableLiveData<List<Recipient>>()
|
||||||
|
val recipients: LiveData<List<Recipient>> = _recipients
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModelScope.launch {
|
||||||
|
threadDb.approvedConversationList.use { openCursor ->
|
||||||
|
val reader = threadDb.readerFor(openCursor)
|
||||||
|
val threads = mutableListOf<Recipient>()
|
||||||
|
while (true) {
|
||||||
|
threads += reader.next?.recipient ?: break
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
_recipients.value = threads
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,222 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.dms
|
|
||||||
|
|
||||||
import android.animation.Animator
|
|
||||||
import android.animation.AnimatorListenerAdapter
|
|
||||||
import android.content.ClipData
|
|
||||||
import android.content.ClipboardManager
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.text.InputType
|
|
||||||
import android.util.TypedValue
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.view.inputmethod.EditorInfo
|
|
||||||
import android.view.inputmethod.InputMethodManager
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.FragmentPagerAdapter
|
|
||||||
import network.loki.messenger.R
|
|
||||||
import network.loki.messenger.databinding.ActivityCreatePrivateChatBinding
|
|
||||||
import network.loki.messenger.databinding.FragmentEnterPublicKeyBinding
|
|
||||||
import nl.komponents.kovenant.ui.failUi
|
|
||||||
import nl.komponents.kovenant.ui.successUi
|
|
||||||
import org.session.libsession.snode.SnodeAPI
|
|
||||||
import org.session.libsession.utilities.Address
|
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
|
||||||
import org.session.libsignal.utilities.PublicKeyValidation
|
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
|
||||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
|
||||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
|
||||||
import org.thoughtcrime.securesms.util.ScanQRCodeWrapperFragment
|
|
||||||
import org.thoughtcrime.securesms.util.ScanQRCodeWrapperFragmentDelegate
|
|
||||||
|
|
||||||
class CreatePrivateChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCodeWrapperFragmentDelegate {
|
|
||||||
private lateinit var binding: ActivityCreatePrivateChatBinding
|
|
||||||
private val adapter = CreatePrivateChatActivityAdapter(this)
|
|
||||||
private var isKeyboardShowing = false
|
|
||||||
set(value) {
|
|
||||||
val hasChanged = (field != value)
|
|
||||||
field = value
|
|
||||||
if (hasChanged) {
|
|
||||||
adapter.isKeyboardShowing = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// region Lifecycle
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
|
|
||||||
super.onCreate(savedInstanceState, isReady)
|
|
||||||
binding = ActivityCreatePrivateChatBinding.inflate(layoutInflater)
|
|
||||||
// Set content view
|
|
||||||
setContentView(binding.root)
|
|
||||||
// Set title
|
|
||||||
supportActionBar!!.title = resources.getString(R.string.activity_create_private_chat_title)
|
|
||||||
// Set up view pager
|
|
||||||
binding.viewPager.adapter = adapter
|
|
||||||
binding.tabLayout.setupWithViewPager(binding.viewPager)
|
|
||||||
binding.rootLayout.viewTreeObserver.addOnGlobalLayoutListener {
|
|
||||||
val diff = binding.rootLayout.rootView.height - binding.rootLayout.height
|
|
||||||
val displayMetrics = this@CreatePrivateChatActivity.resources.displayMetrics
|
|
||||||
val estimatedKeyboardHeight =
|
|
||||||
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200.0f, displayMetrics)
|
|
||||||
this@CreatePrivateChatActivity.isKeyboardShowing = (diff > estimatedKeyboardHeight)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Updating
|
|
||||||
private fun showLoader() {
|
|
||||||
binding.loader.visibility = View.VISIBLE
|
|
||||||
binding.loader.animate().setDuration(150).alpha(1.0f).start()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun hideLoader() {
|
|
||||||
binding.loader.animate().setDuration(150).alpha(0.0f).setListener(object : AnimatorListenerAdapter() {
|
|
||||||
|
|
||||||
override fun onAnimationEnd(animation: Animator?) {
|
|
||||||
super.onAnimationEnd(animation)
|
|
||||||
binding.loader.visibility = View.GONE
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Interaction
|
|
||||||
override fun handleQRCodeScanned(hexEncodedPublicKey: String) {
|
|
||||||
createPrivateChatIfPossible(hexEncodedPublicKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createPrivateChatIfPossible(onsNameOrPublicKey: String) {
|
|
||||||
if (PublicKeyValidation.isValid(onsNameOrPublicKey)) {
|
|
||||||
createPrivateChat(onsNameOrPublicKey)
|
|
||||||
} else {
|
|
||||||
// This could be an ONS name
|
|
||||||
showLoader()
|
|
||||||
SnodeAPI.getSessionID(onsNameOrPublicKey).successUi { hexEncodedPublicKey ->
|
|
||||||
hideLoader()
|
|
||||||
this.createPrivateChat(hexEncodedPublicKey)
|
|
||||||
}.failUi { exception ->
|
|
||||||
hideLoader()
|
|
||||||
var message = resources.getString(R.string.fragment_enter_public_key_error_message)
|
|
||||||
exception.localizedMessage?.let {
|
|
||||||
message = it
|
|
||||||
}
|
|
||||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createPrivateChat(hexEncodedPublicKey: String) {
|
|
||||||
val recipient = Recipient.from(this, Address.fromSerialized(hexEncodedPublicKey), false)
|
|
||||||
val intent = Intent(this, ConversationActivityV2::class.java)
|
|
||||||
intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address)
|
|
||||||
intent.setDataAndType(getIntent().data, getIntent().type)
|
|
||||||
val existingThread = DatabaseComponent.get(this).threadDatabase().getThreadIdIfExistsFor(recipient)
|
|
||||||
intent.putExtra(ConversationActivityV2.THREAD_ID, existingThread)
|
|
||||||
startActivity(intent)
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
}
|
|
||||||
|
|
||||||
// region Adapter
|
|
||||||
private class CreatePrivateChatActivityAdapter(val activity: CreatePrivateChatActivity) : FragmentPagerAdapter(activity.supportFragmentManager) {
|
|
||||||
val enterPublicKeyFragment = EnterPublicKeyFragment()
|
|
||||||
var isKeyboardShowing = false
|
|
||||||
set(value) { field = value; enterPublicKeyFragment.isKeyboardShowing = isKeyboardShowing }
|
|
||||||
|
|
||||||
override fun getCount(): Int {
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItem(index: Int): Fragment {
|
|
||||||
return when (index) {
|
|
||||||
0 -> enterPublicKeyFragment
|
|
||||||
1 -> {
|
|
||||||
val result = ScanQRCodeWrapperFragment()
|
|
||||||
result.delegate = activity
|
|
||||||
result.message = activity.resources.getString(R.string.activity_create_private_chat_scan_qr_code_explanation)
|
|
||||||
result
|
|
||||||
}
|
|
||||||
else -> throw IllegalStateException()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getPageTitle(index: Int): CharSequence? {
|
|
||||||
return when (index) {
|
|
||||||
0 -> activity.resources.getString(R.string.activity_create_private_chat_enter_session_id_tab_title)
|
|
||||||
1 -> activity.resources.getString(R.string.activity_create_private_chat_scan_qr_code_tab_title)
|
|
||||||
else -> throw IllegalStateException()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Enter Public Key Fragment
|
|
||||||
class EnterPublicKeyFragment : Fragment() {
|
|
||||||
private lateinit var binding: FragmentEnterPublicKeyBinding
|
|
||||||
|
|
||||||
var isKeyboardShowing = false
|
|
||||||
set(value) { field = value; handleIsKeyboardShowingChanged() }
|
|
||||||
|
|
||||||
private val hexEncodedPublicKey: String
|
|
||||||
get() {
|
|
||||||
return TextSecurePreferences.getLocalNumber(requireContext())!!
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
|
||||||
binding = FragmentEnterPublicKeyBinding.inflate(inflater, container, false)
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
with(binding) {
|
|
||||||
publicKeyEditText.imeOptions = EditorInfo.IME_ACTION_DONE or 16777216 // Always use incognito keyboard
|
|
||||||
publicKeyEditText.setRawInputType(InputType.TYPE_CLASS_TEXT)
|
|
||||||
publicKeyEditText.setOnEditorActionListener { v, actionID, _ ->
|
|
||||||
if (actionID == EditorInfo.IME_ACTION_DONE) {
|
|
||||||
val imm = v.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
|
||||||
imm.hideSoftInputFromWindow(v.windowToken, 0)
|
|
||||||
createPrivateChatIfPossible()
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
publicKeyTextView.text = hexEncodedPublicKey
|
|
||||||
copyButton.setOnClickListener { copyPublicKey() }
|
|
||||||
shareButton.setOnClickListener { sharePublicKey() }
|
|
||||||
createPrivateChatButton.setOnClickListener { createPrivateChatIfPossible() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleIsKeyboardShowingChanged() {
|
|
||||||
binding.optionalContentContainer.isVisible = !isKeyboardShowing
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun copyPublicKey() {
|
|
||||||
val clipboard = requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
|
||||||
val clip = ClipData.newPlainText("Session ID", hexEncodedPublicKey)
|
|
||||||
clipboard.setPrimaryClip(clip)
|
|
||||||
Toast.makeText(requireContext(), R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sharePublicKey() {
|
|
||||||
val intent = Intent()
|
|
||||||
intent.action = Intent.ACTION_SEND
|
|
||||||
intent.putExtra(Intent.EXTRA_TEXT, hexEncodedPublicKey)
|
|
||||||
intent.type = "text/plain"
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createPrivateChatIfPossible() {
|
|
||||||
val hexEncodedPublicKey = binding.publicKeyEditText.text?.trim().toString()
|
|
||||||
val activity = requireActivity() as CreatePrivateChatActivity
|
|
||||||
activity.createPrivateChatIfPossible(hexEncodedPublicKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// endregion
|
|
@ -0,0 +1,101 @@
|
|||||||
|
package org.thoughtcrime.securesms.dms
|
||||||
|
|
||||||
|
import android.content.ClipData
|
||||||
|
import android.content.ClipboardManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.InputType
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.core.widget.addTextChangedListener
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import network.loki.messenger.R
|
||||||
|
import network.loki.messenger.databinding.FragmentEnterPublicKeyBinding
|
||||||
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
|
import org.thoughtcrime.securesms.util.QRCodeUtilities
|
||||||
|
import org.thoughtcrime.securesms.util.hideKeyboard
|
||||||
|
import org.thoughtcrime.securesms.util.toPx
|
||||||
|
|
||||||
|
class EnterPublicKeyFragment : Fragment() {
|
||||||
|
private lateinit var binding: FragmentEnterPublicKeyBinding
|
||||||
|
|
||||||
|
var delegate: EnterPublicKeyDelegate? = null
|
||||||
|
|
||||||
|
private val hexEncodedPublicKey: String
|
||||||
|
get() {
|
||||||
|
return TextSecurePreferences.getLocalNumber(requireContext())!!
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
|
binding = FragmentEnterPublicKeyBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
with(binding) {
|
||||||
|
publicKeyEditText.imeOptions = EditorInfo.IME_ACTION_DONE or 16777216 // Always use incognito keyboard
|
||||||
|
publicKeyEditText.setRawInputType(InputType.TYPE_CLASS_TEXT)
|
||||||
|
publicKeyEditText.setOnEditorActionListener { v, actionID, _ ->
|
||||||
|
if (actionID == EditorInfo.IME_ACTION_DONE) {
|
||||||
|
v.hideKeyboard()
|
||||||
|
handlePublicKeyEntered()
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
publicKeyEditText.addTextChangedListener { text -> createPrivateChatButton.isVisible = !text.isNullOrBlank() }
|
||||||
|
publicKeyEditText.setOnFocusChangeListener { _, hasFocus -> optionalContentContainer.isVisible = !hasFocus }
|
||||||
|
mainContainer.setOnTouchListener { _, _ ->
|
||||||
|
binding.optionalContentContainer.isVisible = true
|
||||||
|
publicKeyEditText.clearFocus()
|
||||||
|
publicKeyEditText.hideKeyboard()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
val size = toPx(228, resources)
|
||||||
|
val qrCode = QRCodeUtilities.encode(hexEncodedPublicKey, size, isInverted = false, hasTransparentBackground = false)
|
||||||
|
qrCodeImageView.setImageBitmap(qrCode)
|
||||||
|
publicKeyTextView.text = hexEncodedPublicKey
|
||||||
|
publicKeyTextView.setOnCreateContextMenuListener { contextMenu, view, _ ->
|
||||||
|
contextMenu.add(0, view.id, 0, R.string.copy).setOnMenuItemClickListener {
|
||||||
|
copyPublicKey()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
copyButton.setOnClickListener { copyPublicKey() }
|
||||||
|
shareButton.setOnClickListener { sharePublicKey() }
|
||||||
|
createPrivateChatButton.setOnClickListener { handlePublicKeyEntered(); publicKeyEditText.hideKeyboard() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun copyPublicKey() {
|
||||||
|
val clipboard = requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
|
val clip = ClipData.newPlainText("Session ID", hexEncodedPublicKey)
|
||||||
|
clipboard.setPrimaryClip(clip)
|
||||||
|
Toast.makeText(requireContext(), R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sharePublicKey() {
|
||||||
|
val intent = Intent()
|
||||||
|
intent.action = Intent.ACTION_SEND
|
||||||
|
intent.putExtra(Intent.EXTRA_TEXT, hexEncodedPublicKey)
|
||||||
|
intent.type = "text/plain"
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handlePublicKeyEntered() {
|
||||||
|
val hexEncodedPublicKey = binding.publicKeyEditText.text?.trim()?.toString()
|
||||||
|
if (hexEncodedPublicKey.isNullOrEmpty()) return
|
||||||
|
delegate?.handlePublicKeyEntered(hexEncodedPublicKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun interface EnterPublicKeyDelegate {
|
||||||
|
fun handlePublicKeyEntered(publicKey: String)
|
||||||
|
}
|
@ -0,0 +1,107 @@
|
|||||||
|
package org.thoughtcrime.securesms.dms
|
||||||
|
|
||||||
|
import android.animation.Animator
|
||||||
|
import android.animation.AnimatorListenerAdapter
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import network.loki.messenger.R
|
||||||
|
import network.loki.messenger.databinding.FragmentNewMessageBinding
|
||||||
|
import nl.komponents.kovenant.ui.failUi
|
||||||
|
import nl.komponents.kovenant.ui.successUi
|
||||||
|
import org.session.libsession.snode.SnodeAPI
|
||||||
|
import org.session.libsession.utilities.Address
|
||||||
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
|
import org.session.libsignal.utilities.PublicKeyValidation
|
||||||
|
import org.thoughtcrime.securesms.conversation.start.NewConversationDelegate
|
||||||
|
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||||
|
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class NewMessageFragment : Fragment() {
|
||||||
|
|
||||||
|
private lateinit var binding: FragmentNewMessageBinding
|
||||||
|
|
||||||
|
lateinit var delegate: NewConversationDelegate
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
binding = FragmentNewMessageBinding.inflate(inflater)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
binding.backButton.setOnClickListener { delegate.onDialogBackPressed() }
|
||||||
|
binding.closeButton.setOnClickListener { delegate.onDialogClosePressed() }
|
||||||
|
val onsOrPkDelegate = { onsNameOrPublicKey: String -> createPrivateChatIfPossible(onsNameOrPublicKey)}
|
||||||
|
val adapter = NewMessageFragmentAdapter(
|
||||||
|
parentFragment = this,
|
||||||
|
enterPublicKeyDelegate = onsOrPkDelegate,
|
||||||
|
scanPublicKeyDelegate = onsOrPkDelegate
|
||||||
|
)
|
||||||
|
binding.viewPager.adapter = adapter
|
||||||
|
val mediator = TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, pos ->
|
||||||
|
tab.text = when (pos) {
|
||||||
|
0 -> getString(R.string.activity_create_private_chat_enter_session_id_tab_title)
|
||||||
|
1 -> getString(R.string.activity_create_private_chat_scan_qr_code_tab_title)
|
||||||
|
else -> throw IllegalStateException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mediator.attach()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createPrivateChatIfPossible(onsNameOrPublicKey: String) {
|
||||||
|
if (PublicKeyValidation.isValid(onsNameOrPublicKey)) {
|
||||||
|
createPrivateChat(onsNameOrPublicKey)
|
||||||
|
} else {
|
||||||
|
// This could be an ONS name
|
||||||
|
showLoader()
|
||||||
|
SnodeAPI.getSessionID(onsNameOrPublicKey).successUi { hexEncodedPublicKey ->
|
||||||
|
hideLoader()
|
||||||
|
createPrivateChat(hexEncodedPublicKey)
|
||||||
|
}.failUi { exception ->
|
||||||
|
hideLoader()
|
||||||
|
var message = getString(R.string.fragment_enter_public_key_error_message)
|
||||||
|
exception.localizedMessage?.let {
|
||||||
|
message = it
|
||||||
|
}
|
||||||
|
Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createPrivateChat(hexEncodedPublicKey: String) {
|
||||||
|
val recipient = Recipient.from(requireContext(), Address.fromSerialized(hexEncodedPublicKey), false)
|
||||||
|
val intent = Intent(requireContext(), ConversationActivityV2::class.java)
|
||||||
|
intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address)
|
||||||
|
intent.setDataAndType(requireActivity().intent.data, requireActivity().intent.type)
|
||||||
|
val existingThread = DatabaseComponent.get(requireContext()).threadDatabase().getThreadIdIfExistsFor(recipient)
|
||||||
|
intent.putExtra(ConversationActivityV2.THREAD_ID, existingThread)
|
||||||
|
requireContext().startActivity(intent)
|
||||||
|
delegate.onDialogClosePressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showLoader() {
|
||||||
|
binding.loader.visibility = View.VISIBLE
|
||||||
|
binding.loader.animate().setDuration(150).alpha(1.0f).start()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hideLoader() {
|
||||||
|
binding.loader.animate().setDuration(150).alpha(0.0f).setListener(object : AnimatorListenerAdapter() {
|
||||||
|
|
||||||
|
override fun onAnimationEnd(animation: Animator?) {
|
||||||
|
super.onAnimationEnd(animation)
|
||||||
|
binding.loader.visibility = View.GONE
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package org.thoughtcrime.securesms.dms
|
||||||
|
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
|
import org.thoughtcrime.securesms.util.ScanQRCodeWrapperFragment
|
||||||
|
import org.thoughtcrime.securesms.util.ScanQRCodeWrapperFragmentDelegate
|
||||||
|
|
||||||
|
class NewMessageFragmentAdapter(
|
||||||
|
private val parentFragment: Fragment,
|
||||||
|
private val enterPublicKeyDelegate: EnterPublicKeyDelegate,
|
||||||
|
private val scanPublicKeyDelegate: ScanQRCodeWrapperFragmentDelegate
|
||||||
|
) : FragmentStateAdapter(parentFragment) {
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = 2
|
||||||
|
|
||||||
|
override fun createFragment(position: Int): Fragment {
|
||||||
|
return when (position) {
|
||||||
|
0 -> EnterPublicKeyFragment().apply { delegate = enterPublicKeyDelegate }
|
||||||
|
1 -> ScanQRCodeWrapperFragment().apply { delegate = scanPublicKeyDelegate }
|
||||||
|
else -> throw IllegalStateException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,145 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.groups
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.Menu
|
|
||||||
import android.view.MenuItem
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.loader.app.LoaderManager
|
|
||||||
import androidx.loader.content.Loader
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import network.loki.messenger.R
|
|
||||||
import network.loki.messenger.databinding.ActivityCreateClosedGroupBinding
|
|
||||||
import nl.komponents.kovenant.ui.failUi
|
|
||||||
import nl.komponents.kovenant.ui.successUi
|
|
||||||
import org.session.libsession.messaging.sending_receiving.MessageSender
|
|
||||||
import org.session.libsession.messaging.sending_receiving.groupSizeLimit
|
|
||||||
import org.session.libsession.utilities.Address
|
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
|
||||||
import org.thoughtcrime.securesms.contacts.SelectContactsAdapter
|
|
||||||
import org.thoughtcrime.securesms.contacts.SelectContactsLoader
|
|
||||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
|
||||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp
|
|
||||||
import org.thoughtcrime.securesms.util.fadeIn
|
|
||||||
import org.thoughtcrime.securesms.util.fadeOut
|
|
||||||
|
|
||||||
class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderManager.LoaderCallbacks<List<String>> {
|
|
||||||
private lateinit var binding: ActivityCreateClosedGroupBinding
|
|
||||||
private var isLoading = false
|
|
||||||
set(newValue) { field = newValue; invalidateOptionsMenu() }
|
|
||||||
private var members = listOf<String>()
|
|
||||||
set(value) { field = value; selectContactsAdapter.members = value }
|
|
||||||
private val publicKey: String
|
|
||||||
get() {
|
|
||||||
return TextSecurePreferences.getLocalNumber(this)!!
|
|
||||||
}
|
|
||||||
|
|
||||||
private val selectContactsAdapter by lazy {
|
|
||||||
SelectContactsAdapter(this, GlideApp.with(this))
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val closedGroupCreatedResultCode = 100
|
|
||||||
}
|
|
||||||
|
|
||||||
// region Lifecycle
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
|
|
||||||
super.onCreate(savedInstanceState, isReady)
|
|
||||||
binding = ActivityCreateClosedGroupBinding.inflate(layoutInflater)
|
|
||||||
setContentView(binding.root)
|
|
||||||
supportActionBar!!.title = resources.getString(R.string.activity_create_closed_group_title)
|
|
||||||
binding.recyclerView.adapter = this.selectContactsAdapter
|
|
||||||
binding.recyclerView.layoutManager = LinearLayoutManager(this)
|
|
||||||
binding.createNewPrivateChatButton.setOnClickListener { createNewPrivateChat() }
|
|
||||||
LoaderManager.getInstance(this).initLoader(0, null, this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
|
||||||
menuInflater.inflate(R.menu.menu_done, menu)
|
|
||||||
return members.isNotEmpty() && !isLoading
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Updating
|
|
||||||
override fun onCreateLoader(id: Int, bundle: Bundle?): Loader<List<String>> {
|
|
||||||
return SelectContactsLoader(this, setOf())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLoadFinished(loader: Loader<List<String>>, members: List<String>) {
|
|
||||||
update(members)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLoaderReset(loader: Loader<List<String>>) {
|
|
||||||
update(listOf())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun update(members: List<String>) {
|
|
||||||
//if there is a Note to self conversation, it loads self in the list, so we need to remove it here
|
|
||||||
this.members = members.minus(publicKey)
|
|
||||||
binding.mainContentContainer.visibility = if (members.isEmpty()) View.GONE else View.VISIBLE
|
|
||||||
binding.emptyStateContainer.visibility = if (members.isEmpty()) View.VISIBLE else View.GONE
|
|
||||||
invalidateOptionsMenu()
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Interaction
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
|
||||||
when(item.itemId) {
|
|
||||||
R.id.doneButton -> if (!isLoading) { createClosedGroup() }
|
|
||||||
}
|
|
||||||
return super.onOptionsItemSelected(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createNewPrivateChat() {
|
|
||||||
setResult(closedGroupCreatedResultCode)
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createClosedGroup() {
|
|
||||||
val name = binding.nameEditText.text.trim()
|
|
||||||
if (name.isEmpty()) {
|
|
||||||
return Toast.makeText(this, R.string.activity_create_closed_group_group_name_missing_error, Toast.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
if (name.length >= 64) {
|
|
||||||
return Toast.makeText(this, R.string.activity_create_closed_group_group_name_too_long_error, Toast.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
val selectedMembers = this.selectContactsAdapter.selectedMembers
|
|
||||||
if (selectedMembers.count() < 1) {
|
|
||||||
return Toast.makeText(this, R.string.activity_create_closed_group_not_enough_group_members_error, Toast.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
if (selectedMembers.count() >= groupSizeLimit) { // Minus one because we're going to include self later
|
|
||||||
return Toast.makeText(this, R.string.activity_create_closed_group_too_many_group_members_error, Toast.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(this)!!
|
|
||||||
isLoading = true
|
|
||||||
binding.loaderContainer.fadeIn()
|
|
||||||
MessageSender.createClosedGroup(name.toString(), selectedMembers + setOf( userPublicKey )).successUi { groupID ->
|
|
||||||
binding.loaderContainer.fadeOut()
|
|
||||||
isLoading = false
|
|
||||||
val threadID = DatabaseComponent.get(this).threadDatabase().getOrCreateThreadIdFor(Recipient.from(this, Address.fromSerialized(groupID), false))
|
|
||||||
if (!isFinishing) {
|
|
||||||
openConversationActivity(this, threadID, Recipient.from(this, Address.fromSerialized(groupID), false))
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
}.failUi {
|
|
||||||
binding.loaderContainer.fadeOut()
|
|
||||||
isLoading = false
|
|
||||||
Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
}
|
|
||||||
|
|
||||||
// region Convenience
|
|
||||||
private fun openConversationActivity(context: Context, threadId: Long, recipient: Recipient) {
|
|
||||||
val intent = Intent(context, ConversationActivityV2::class.java)
|
|
||||||
intent.putExtra(ConversationActivityV2.THREAD_ID, threadId)
|
|
||||||
intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address)
|
|
||||||
context.startActivity(intent)
|
|
||||||
}
|
|
||||||
// endregion
|
|
@ -0,0 +1,110 @@
|
|||||||
|
package org.thoughtcrime.securesms.groups
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import network.loki.messenger.R
|
||||||
|
import network.loki.messenger.databinding.FragmentCreateGroupBinding
|
||||||
|
import nl.komponents.kovenant.ui.failUi
|
||||||
|
import nl.komponents.kovenant.ui.successUi
|
||||||
|
import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||||
|
import org.session.libsession.messaging.sending_receiving.groupSizeLimit
|
||||||
|
import org.session.libsession.utilities.Address
|
||||||
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
|
import org.thoughtcrime.securesms.contacts.SelectContactsAdapter
|
||||||
|
import org.thoughtcrime.securesms.conversation.start.NewConversationDelegate
|
||||||
|
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||||
|
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||||
|
import org.thoughtcrime.securesms.keyboard.emoji.KeyboardPageSearchView
|
||||||
|
import org.thoughtcrime.securesms.mms.GlideApp
|
||||||
|
import org.thoughtcrime.securesms.util.fadeIn
|
||||||
|
import org.thoughtcrime.securesms.util.fadeOut
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class CreateGroupFragment : Fragment() {
|
||||||
|
|
||||||
|
private lateinit var binding: FragmentCreateGroupBinding
|
||||||
|
private val viewModel: CreateGroupViewModel by viewModels()
|
||||||
|
|
||||||
|
lateinit var delegate: NewConversationDelegate
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
binding = FragmentCreateGroupBinding.inflate(inflater)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
val adapter = SelectContactsAdapter(requireContext(), GlideApp.with(requireContext()))
|
||||||
|
binding.backButton.setOnClickListener { delegate.onDialogBackPressed() }
|
||||||
|
binding.closeButton.setOnClickListener { delegate.onDialogClosePressed() }
|
||||||
|
binding.contactSearch.callbacks = object : KeyboardPageSearchView.Callbacks {
|
||||||
|
override fun onQueryChanged(query: String) {
|
||||||
|
adapter.members = viewModel.filter(query).map { it.address.serialize() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.createNewPrivateChatButton.setOnClickListener { delegate.onNewMessageSelected() }
|
||||||
|
binding.recyclerView.adapter = adapter
|
||||||
|
var isLoading = false
|
||||||
|
binding.createClosedGroupButton.setOnClickListener {
|
||||||
|
if (isLoading) return@setOnClickListener
|
||||||
|
val name = binding.nameEditText.text.trim()
|
||||||
|
if (name.isEmpty()) {
|
||||||
|
return@setOnClickListener Toast.makeText(context, R.string.activity_create_closed_group_group_name_missing_error, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
if (name.length >= 30) {
|
||||||
|
return@setOnClickListener Toast.makeText(context, R.string.activity_create_closed_group_group_name_too_long_error, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
val selectedMembers = adapter.selectedMembers
|
||||||
|
if (selectedMembers.isEmpty()) {
|
||||||
|
return@setOnClickListener Toast.makeText(context, R.string.activity_create_closed_group_not_enough_group_members_error, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
if (selectedMembers.count() >= groupSizeLimit) { // Minus one because we're going to include self later
|
||||||
|
return@setOnClickListener Toast.makeText(context, R.string.activity_create_closed_group_too_many_group_members_error, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
val userPublicKey = TextSecurePreferences.getLocalNumber(requireContext())!!
|
||||||
|
isLoading = true
|
||||||
|
binding.loaderContainer.fadeIn()
|
||||||
|
MessageSender.createClosedGroup(name.toString(), selectedMembers + setOf( userPublicKey )).successUi { groupID ->
|
||||||
|
binding.loaderContainer.fadeOut()
|
||||||
|
isLoading = false
|
||||||
|
val threadID = DatabaseComponent.get(requireContext()).threadDatabase().getOrCreateThreadIdFor(Recipient.from(requireContext(), Address.fromSerialized(groupID), false))
|
||||||
|
openConversationActivity(
|
||||||
|
requireContext(),
|
||||||
|
threadID,
|
||||||
|
Recipient.from(requireContext(), Address.fromSerialized(groupID), false)
|
||||||
|
)
|
||||||
|
delegate.onDialogClosePressed()
|
||||||
|
}.failUi {
|
||||||
|
binding.loaderContainer.fadeOut()
|
||||||
|
isLoading = false
|
||||||
|
Toast.makeText(context, it.message, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.mainContentGroup.isVisible = !viewModel.recipients.value.isNullOrEmpty()
|
||||||
|
binding.emptyStateGroup.isVisible = viewModel.recipients.value.isNullOrEmpty()
|
||||||
|
viewModel.recipients.observe(viewLifecycleOwner) { recipients ->
|
||||||
|
adapter.members = recipients.map { it.address.serialize() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openConversationActivity(context: Context, threadId: Long, recipient: Recipient) {
|
||||||
|
val intent = Intent(context, ConversationActivityV2::class.java)
|
||||||
|
intent.putExtra(ConversationActivityV2.THREAD_ID, threadId)
|
||||||
|
intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address)
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package org.thoughtcrime.securesms.groups
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
|
import org.thoughtcrime.securesms.database.ThreadDatabase
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class CreateGroupViewModel @Inject constructor(
|
||||||
|
private val threadDb: ThreadDatabase,
|
||||||
|
private val textSecurePreferences: TextSecurePreferences
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val _recipients = MutableLiveData<List<Recipient>>()
|
||||||
|
val recipients: LiveData<List<Recipient>> = _recipients
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModelScope.launch {
|
||||||
|
threadDb.approvedConversationList.use { openCursor ->
|
||||||
|
val reader = threadDb.readerFor(openCursor)
|
||||||
|
val recipients = mutableListOf<Recipient>()
|
||||||
|
while (true) {
|
||||||
|
recipients += reader.next?.recipient ?: break
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
_recipients.value = recipients
|
||||||
|
.filter { !it.isGroupRecipient && it.hasApprovedMe() && it.address.serialize() != textSecurePreferences.getLocalNumber() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun filter(query: String): List<Recipient> {
|
||||||
|
return _recipients.value?.filter {
|
||||||
|
it.address.serialize().contains(query, ignoreCase = true) || it.name?.contains(query, ignoreCase = true) == true
|
||||||
|
} ?: emptyList()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
package org.thoughtcrime.securesms.groups
|
||||||
|
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.core.widget.addTextChangedListener
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import com.google.android.material.chip.Chip
|
||||||
|
import network.loki.messenger.R
|
||||||
|
import network.loki.messenger.databinding.FragmentEnterCommunityUrlBinding
|
||||||
|
import org.session.libsession.messaging.open_groups.OpenGroupApi
|
||||||
|
import org.thoughtcrime.securesms.BaseActionBarActivity
|
||||||
|
import org.thoughtcrime.securesms.util.State
|
||||||
|
import org.thoughtcrime.securesms.util.hideKeyboard
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class EnterCommunityUrlFragment : Fragment() {
|
||||||
|
private lateinit var binding: FragmentEnterCommunityUrlBinding
|
||||||
|
private val viewModel by activityViewModels<DefaultGroupsViewModel>()
|
||||||
|
|
||||||
|
var delegate: EnterCommunityUrlDelegate? = null
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
|
binding = FragmentEnterCommunityUrlBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
binding.communityUrlEditText.imeOptions = binding.communityUrlEditText.imeOptions or 16777216 // Always use incognito keyboard
|
||||||
|
binding.communityUrlEditText.addTextChangedListener { text -> binding.joinCommunityButton.isEnabled = !text.isNullOrBlank() }
|
||||||
|
binding.communityUrlEditText.setOnFocusChangeListener { _, hasFocus -> binding.defaultRoomsContainer.isVisible = !hasFocus }
|
||||||
|
binding.mainContainer.setOnTouchListener { _, _ ->
|
||||||
|
binding.defaultRoomsContainer.isVisible = true
|
||||||
|
binding.communityUrlEditText.clearFocus()
|
||||||
|
binding.communityUrlEditText.hideKeyboard()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
binding.joinCommunityButton.setOnClickListener { joinCommunityIfPossible() }
|
||||||
|
viewModel.defaultRooms.observe(viewLifecycleOwner) { state ->
|
||||||
|
binding.defaultRoomsContainer.isVisible = state is State.Success
|
||||||
|
binding.defaultRoomsLoaderContainer.isVisible = state is State.Loading
|
||||||
|
binding.defaultRoomsLoader.isVisible = state is State.Loading
|
||||||
|
when (state) {
|
||||||
|
State.Loading -> {
|
||||||
|
// TODO: Show a binding.loader
|
||||||
|
}
|
||||||
|
is State.Error -> {
|
||||||
|
// TODO: Hide the binding.loader
|
||||||
|
}
|
||||||
|
is State.Success -> {
|
||||||
|
populateDefaultGroups(state.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun populateDefaultGroups(groups: List<OpenGroupApi.DefaultGroup>) {
|
||||||
|
binding.defaultRoomsFlexboxLayout.removeAllViews()
|
||||||
|
groups.iterator().forEach { defaultGroup ->
|
||||||
|
val chip = layoutInflater.inflate(R.layout.default_group_chip, binding.defaultRoomsFlexboxLayout, false) as Chip
|
||||||
|
val drawable = defaultGroup.image?.let { bytes ->
|
||||||
|
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
|
||||||
|
RoundedBitmapDrawableFactory.create(resources, bitmap).apply {
|
||||||
|
isCircular = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chip.chipIcon = drawable
|
||||||
|
chip.text = defaultGroup.name
|
||||||
|
chip.setOnClickListener {
|
||||||
|
delegate?.handleCommunityUrlEntered(defaultGroup.joinURL)
|
||||||
|
}
|
||||||
|
binding.defaultRoomsFlexboxLayout.addView(chip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// region Convenience
|
||||||
|
private fun joinCommunityIfPossible() {
|
||||||
|
val inputMethodManager = requireContext().getSystemService(BaseActionBarActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
inputMethodManager.hideSoftInputFromWindow(binding.communityUrlEditText.windowToken, 0)
|
||||||
|
val communityUrl = binding.communityUrlEditText.text.trim().toString().lowercase(Locale.US)
|
||||||
|
delegate?.handleCommunityUrlEntered(communityUrl)
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
fun interface EnterCommunityUrlDelegate {
|
||||||
|
fun handleCommunityUrlEntered(url: String)
|
||||||
|
}
|
@ -0,0 +1,125 @@
|
|||||||
|
package org.thoughtcrime.securesms.groups
|
||||||
|
|
||||||
|
import android.animation.Animator
|
||||||
|
import android.animation.AnimatorListenerAdapter
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import network.loki.messenger.R
|
||||||
|
import network.loki.messenger.databinding.FragmentJoinCommunityBinding
|
||||||
|
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||||
|
import org.session.libsession.utilities.Address
|
||||||
|
import org.session.libsession.utilities.GroupUtil
|
||||||
|
import org.session.libsession.utilities.OpenGroupUrlParser
|
||||||
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
|
import org.session.libsignal.utilities.Log
|
||||||
|
import org.thoughtcrime.securesms.conversation.start.NewConversationDelegate
|
||||||
|
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||||
|
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class JoinCommunityFragment : Fragment() {
|
||||||
|
|
||||||
|
private lateinit var binding: FragmentJoinCommunityBinding
|
||||||
|
|
||||||
|
lateinit var delegate: NewConversationDelegate
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
binding = FragmentJoinCommunityBinding.inflate(inflater)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
binding.backButton.setOnClickListener { delegate.onDialogBackPressed() }
|
||||||
|
binding.closeButton.setOnClickListener { delegate.onDialogClosePressed() }
|
||||||
|
fun showLoader() {
|
||||||
|
binding.loader.visibility = View.VISIBLE
|
||||||
|
binding.loader.animate().setDuration(150).alpha(1.0f).start()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hideLoader() {
|
||||||
|
binding.loader.animate().setDuration(150).alpha(0.0f).setListener(object : AnimatorListenerAdapter() {
|
||||||
|
|
||||||
|
override fun onAnimationEnd(animation: Animator?) {
|
||||||
|
super.onAnimationEnd(animation)
|
||||||
|
binding.loader.visibility = View.GONE
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fun joinCommunityIfPossible(url: String) {
|
||||||
|
val openGroup = try {
|
||||||
|
OpenGroupUrlParser.parseUrl(url)
|
||||||
|
} catch (e: OpenGroupUrlParser.Error) {
|
||||||
|
when (e) {
|
||||||
|
is OpenGroupUrlParser.Error.MalformedURL -> return Toast.makeText(activity, R.string.activity_join_public_chat_error, Toast.LENGTH_SHORT).show()
|
||||||
|
is OpenGroupUrlParser.Error.InvalidPublicKey -> return Toast.makeText(activity, R.string.invalid_public_key, Toast.LENGTH_SHORT).show()
|
||||||
|
is OpenGroupUrlParser.Error.NoPublicKey -> return Toast.makeText(activity, R.string.invalid_public_key, Toast.LENGTH_SHORT).show()
|
||||||
|
is OpenGroupUrlParser.Error.NoRoom -> return Toast.makeText(activity, R.string.activity_join_public_chat_error, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
showLoader()
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val sanitizedServer = openGroup.server.removeSuffix("/")
|
||||||
|
val openGroupID = "$sanitizedServer.${openGroup.room}"
|
||||||
|
OpenGroupManager.add(sanitizedServer, openGroup.room, openGroup.serverPublicKey, requireContext())
|
||||||
|
val storage = MessagingModuleConfiguration.shared.storage
|
||||||
|
storage.onOpenGroupAdded(sanitizedServer)
|
||||||
|
val threadID = GroupManager.getOpenGroupThreadID(openGroupID, requireContext())
|
||||||
|
val groupID = GroupUtil.getEncodedOpenGroupID(openGroupID.toByteArray())
|
||||||
|
|
||||||
|
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(requireContext())
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
val recipient = Recipient.from(requireContext(), Address.fromSerialized(groupID), false)
|
||||||
|
openConversationActivity(requireContext(), threadID, recipient)
|
||||||
|
delegate.onDialogClosePressed()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("Loki", "Couldn't join open group.", e)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
hideLoader()
|
||||||
|
Toast.makeText(activity, R.string.activity_join_public_chat_error, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val urlDelegate = { url: String -> joinCommunityIfPossible(url) }
|
||||||
|
binding.viewPager.adapter = JoinCommunityFragmentAdapter(
|
||||||
|
parentFragment = this,
|
||||||
|
enterCommunityUrlDelegate = urlDelegate,
|
||||||
|
scanQrCodeDelegate = urlDelegate
|
||||||
|
)
|
||||||
|
val mediator = TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, pos ->
|
||||||
|
tab.text = when (pos) {
|
||||||
|
0 -> getString(R.string.activity_join_public_chat_enter_community_url_tab_title)
|
||||||
|
1 -> getString(R.string.activity_join_public_chat_scan_qr_code_tab_title)
|
||||||
|
else -> throw IllegalStateException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mediator.attach()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openConversationActivity(context: Context, threadId: Long, recipient: Recipient) {
|
||||||
|
val intent = Intent(context, ConversationActivityV2::class.java)
|
||||||
|
intent.putExtra(ConversationActivityV2.THREAD_ID, threadId)
|
||||||
|
intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address)
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package org.thoughtcrime.securesms.groups
|
||||||
|
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
|
import org.thoughtcrime.securesms.util.ScanQRCodeWrapperFragment
|
||||||
|
import org.thoughtcrime.securesms.util.ScanQRCodeWrapperFragmentDelegate
|
||||||
|
|
||||||
|
class JoinCommunityFragmentAdapter(
|
||||||
|
private val parentFragment: Fragment,
|
||||||
|
private val enterCommunityUrlDelegate: EnterCommunityUrlDelegate,
|
||||||
|
private val scanQrCodeDelegate: ScanQRCodeWrapperFragmentDelegate
|
||||||
|
) : FragmentStateAdapter(parentFragment) {
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = 2
|
||||||
|
|
||||||
|
override fun createFragment(position: Int): Fragment {
|
||||||
|
return when (position) {
|
||||||
|
0 -> EnterCommunityUrlFragment().apply { delegate = enterCommunityUrlDelegate }
|
||||||
|
1 -> ScanQRCodeWrapperFragment().apply { delegate = scanQrCodeDelegate }
|
||||||
|
else -> throw IllegalStateException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,229 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.groups
|
|
||||||
|
|
||||||
import android.animation.Animator
|
|
||||||
import android.animation.AnimatorListenerAdapter
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.graphics.BitmapFactory
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.view.inputmethod.InputMethodManager
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
|
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.FragmentPagerAdapter
|
|
||||||
import androidx.fragment.app.activityViewModels
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import com.google.android.material.chip.Chip
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import network.loki.messenger.R
|
|
||||||
import network.loki.messenger.databinding.ActivityJoinPublicChatBinding
|
|
||||||
import network.loki.messenger.databinding.FragmentEnterChatUrlBinding
|
|
||||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
|
||||||
import org.session.libsession.messaging.open_groups.OpenGroupApi.DefaultGroup
|
|
||||||
import org.session.libsession.utilities.Address
|
|
||||||
import org.session.libsession.utilities.GroupUtil
|
|
||||||
import org.session.libsession.utilities.OpenGroupUrlParser
|
|
||||||
import org.session.libsession.utilities.OpenGroupUrlParser.Error
|
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
|
||||||
import org.session.libsignal.utilities.Log
|
|
||||||
import org.thoughtcrime.securesms.BaseActionBarActivity
|
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
|
||||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
|
||||||
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
|
|
||||||
import org.thoughtcrime.securesms.util.ScanQRCodeWrapperFragment
|
|
||||||
import org.thoughtcrime.securesms.util.ScanQRCodeWrapperFragmentDelegate
|
|
||||||
import org.thoughtcrime.securesms.util.State
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCodeWrapperFragmentDelegate {
|
|
||||||
private lateinit var binding: ActivityJoinPublicChatBinding
|
|
||||||
private val adapter = JoinPublicChatActivityAdapter(this)
|
|
||||||
|
|
||||||
// region Lifecycle
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
|
|
||||||
super.onCreate(savedInstanceState, isReady)
|
|
||||||
binding = ActivityJoinPublicChatBinding.inflate(layoutInflater)
|
|
||||||
// Set content view
|
|
||||||
setContentView(binding.root)
|
|
||||||
// Set title
|
|
||||||
supportActionBar!!.title = resources.getString(R.string.activity_join_public_chat_title)
|
|
||||||
// Set up view pager
|
|
||||||
binding.viewPager.adapter = adapter
|
|
||||||
binding.tabLayout.setupWithViewPager(binding.viewPager)
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Updating
|
|
||||||
private fun showLoader() {
|
|
||||||
binding.loader.visibility = View.VISIBLE
|
|
||||||
binding.loader.animate().setDuration(150).alpha(1.0f).start()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun hideLoader() {
|
|
||||||
binding.loader.animate().setDuration(150).alpha(0.0f).setListener(object : AnimatorListenerAdapter() {
|
|
||||||
|
|
||||||
override fun onAnimationEnd(animation: Animator?) {
|
|
||||||
super.onAnimationEnd(animation)
|
|
||||||
binding.loader.visibility = View.GONE
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Interaction
|
|
||||||
override fun handleQRCodeScanned(url: String) {
|
|
||||||
joinPublicChatIfPossible(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun joinPublicChatIfPossible(url: String) {
|
|
||||||
// Add "http" if not entered explicitly
|
|
||||||
val openGroup = try {
|
|
||||||
OpenGroupUrlParser.parseUrl(url)
|
|
||||||
} catch (e: Error) {
|
|
||||||
when (e) {
|
|
||||||
is Error.MalformedURL -> return Toast.makeText(this, R.string.activity_join_public_chat_error, Toast.LENGTH_SHORT).show()
|
|
||||||
is Error.InvalidPublicKey -> return Toast.makeText(this, R.string.invalid_public_key, Toast.LENGTH_SHORT).show()
|
|
||||||
is Error.NoPublicKey -> return Toast.makeText(this, R.string.invalid_public_key, Toast.LENGTH_SHORT).show()
|
|
||||||
is Error.NoRoom -> return Toast.makeText(this, R.string.activity_join_public_chat_error, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
showLoader()
|
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
|
||||||
try {
|
|
||||||
val sanitizedServer = openGroup.server.removeSuffix("/")
|
|
||||||
val openGroupID = "$sanitizedServer.${openGroup.room}"
|
|
||||||
OpenGroupManager.add(sanitizedServer, openGroup.room, openGroup.serverPublicKey, this@JoinPublicChatActivity)
|
|
||||||
val storage = MessagingModuleConfiguration.shared.storage
|
|
||||||
storage.onOpenGroupAdded(sanitizedServer)
|
|
||||||
val threadID = GroupManager.getOpenGroupThreadID(openGroupID, this@JoinPublicChatActivity)
|
|
||||||
val groupID = GroupUtil.getEncodedOpenGroupID(openGroupID.toByteArray())
|
|
||||||
|
|
||||||
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@JoinPublicChatActivity)
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
val recipient = Recipient.from(this@JoinPublicChatActivity, Address.fromSerialized(groupID), false)
|
|
||||||
openConversationActivity(this@JoinPublicChatActivity, threadID, recipient)
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("Loki", "Couldn't join open group.", e)
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
hideLoader()
|
|
||||||
Toast.makeText(this@JoinPublicChatActivity, R.string.activity_join_public_chat_error, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Convenience
|
|
||||||
private fun openConversationActivity(context: Context, threadId: Long, recipient: Recipient) {
|
|
||||||
val intent = Intent(context, ConversationActivityV2::class.java)
|
|
||||||
intent.putExtra(ConversationActivityV2.THREAD_ID, threadId)
|
|
||||||
intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address)
|
|
||||||
context.startActivity(intent)
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
}
|
|
||||||
|
|
||||||
// region Adapter
|
|
||||||
private class JoinPublicChatActivityAdapter(val activity: JoinPublicChatActivity) : FragmentPagerAdapter(activity.supportFragmentManager) {
|
|
||||||
|
|
||||||
override fun getCount(): Int {
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItem(index: Int): Fragment {
|
|
||||||
return when (index) {
|
|
||||||
0 -> EnterChatURLFragment()
|
|
||||||
1 -> {
|
|
||||||
val result = ScanQRCodeWrapperFragment()
|
|
||||||
result.delegate = activity
|
|
||||||
result.message = activity.resources.getString(R.string.activity_join_public_chat_scan_qr_code_explanation)
|
|
||||||
result
|
|
||||||
}
|
|
||||||
else -> throw IllegalStateException()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getPageTitle(index: Int): CharSequence {
|
|
||||||
return when (index) {
|
|
||||||
0 -> activity.resources.getString(R.string.activity_join_public_chat_enter_group_url_tab_title)
|
|
||||||
1 -> activity.resources.getString(R.string.activity_join_public_chat_scan_qr_code_tab_title)
|
|
||||||
else -> throw IllegalStateException()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Enter Chat URL Fragment
|
|
||||||
class EnterChatURLFragment : Fragment() {
|
|
||||||
private lateinit var binding: FragmentEnterChatUrlBinding
|
|
||||||
private val viewModel by activityViewModels<DefaultGroupsViewModel>()
|
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
|
||||||
binding = FragmentEnterChatUrlBinding.inflate(inflater, container, false)
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
binding.chatURLEditText.imeOptions = binding.chatURLEditText.imeOptions or 16777216 // Always use incognito keyboard
|
|
||||||
binding.joinPublicChatButton.setOnClickListener { joinPublicChatIfPossible() }
|
|
||||||
viewModel.defaultRooms.observe(viewLifecycleOwner) { state ->
|
|
||||||
binding.defaultRoomsContainer.isVisible = state is State.Success
|
|
||||||
binding.defaultRoomsLoaderContainer.isVisible = state is State.Loading
|
|
||||||
binding.defaultRoomsLoader.isVisible = state is State.Loading
|
|
||||||
when (state) {
|
|
||||||
State.Loading -> {
|
|
||||||
// TODO: Show a binding.loader
|
|
||||||
}
|
|
||||||
is State.Error -> {
|
|
||||||
// TODO: Hide the binding.loader
|
|
||||||
}
|
|
||||||
is State.Success -> {
|
|
||||||
populateDefaultGroups(state.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun populateDefaultGroups(groups: List<DefaultGroup>) {
|
|
||||||
binding.defaultRoomsGridLayout.removeAllViews()
|
|
||||||
binding.defaultRoomsGridLayout.useDefaultMargins = false
|
|
||||||
groups.iterator().forEach { defaultGroup ->
|
|
||||||
val chip = layoutInflater.inflate(R.layout.default_group_chip, binding.defaultRoomsGridLayout, false) as Chip
|
|
||||||
val drawable = defaultGroup.image?.let { bytes ->
|
|
||||||
val bitmap = BitmapFactory.decodeByteArray(bytes,0, bytes.size)
|
|
||||||
RoundedBitmapDrawableFactory.create(resources,bitmap).apply {
|
|
||||||
isCircular = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
chip.chipIcon = drawable
|
|
||||||
chip.text = defaultGroup.name
|
|
||||||
chip.setOnClickListener {
|
|
||||||
(requireActivity() as JoinPublicChatActivity).joinPublicChatIfPossible(defaultGroup.joinURL)
|
|
||||||
}
|
|
||||||
binding.defaultRoomsGridLayout.addView(chip)
|
|
||||||
}
|
|
||||||
if ((groups.size and 1) != 0) { // This checks that the number of rooms is even
|
|
||||||
layoutInflater.inflate(R.layout.grid_layout_filler, binding.defaultRoomsGridLayout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// region Convenience
|
|
||||||
private fun joinPublicChatIfPossible() {
|
|
||||||
val inputMethodManager = requireContext().getSystemService(BaseActionBarActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
|
||||||
inputMethodManager.hideSoftInputFromWindow(binding.chatURLEditText.windowToken, 0)
|
|
||||||
val chatURL = binding.chatURLEditText.text.trim().toString().toLowerCase(Locale.US)
|
|
||||||
(requireActivity() as JoinPublicChatActivity).joinPublicChatIfPossible(chatURL)
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
}
|
|
||||||
// endregion
|
|
@ -41,6 +41,7 @@ import org.session.libsignal.utilities.toHexString
|
|||||||
import org.thoughtcrime.securesms.ApplicationContext
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
import org.thoughtcrime.securesms.MuteDialog
|
import org.thoughtcrime.securesms.MuteDialog
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||||
|
import org.thoughtcrime.securesms.conversation.start.NewConversationFragment
|
||||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||||
import org.thoughtcrime.securesms.conversation.v2.utilities.NotificationUtils
|
import org.thoughtcrime.securesms.conversation.v2.utilities.NotificationUtils
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||||
@ -50,9 +51,6 @@ import org.thoughtcrime.securesms.database.RecipientDatabase
|
|||||||
import org.thoughtcrime.securesms.database.ThreadDatabase
|
import org.thoughtcrime.securesms.database.ThreadDatabase
|
||||||
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
||||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||||
import org.thoughtcrime.securesms.dms.CreatePrivateChatActivity
|
|
||||||
import org.thoughtcrime.securesms.groups.CreateClosedGroupActivity
|
|
||||||
import org.thoughtcrime.securesms.groups.JoinPublicChatActivity
|
|
||||||
import org.thoughtcrime.securesms.groups.OpenGroupManager
|
import org.thoughtcrime.securesms.groups.OpenGroupManager
|
||||||
import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter
|
import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter
|
||||||
import org.thoughtcrime.securesms.home.search.GlobalSearchInputLayout
|
import org.thoughtcrime.securesms.home.search.GlobalSearchInputLayout
|
||||||
@ -76,10 +74,9 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class HomeActivity : PassphraseRequiredActionBarActivity(),
|
class HomeActivity : PassphraseRequiredActionBarActivity(),
|
||||||
ConversationClickListener,
|
ConversationClickListener,
|
||||||
SeedReminderViewDelegate,
|
SeedReminderViewDelegate,
|
||||||
NewConversationButtonSetViewDelegate,
|
GlobalSearchInputLayout.GlobalSearchInputLayoutListener {
|
||||||
GlobalSearchInputLayout.GlobalSearchInputLayoutListener {
|
|
||||||
|
|
||||||
private lateinit var binding: ActivityHomeBinding
|
private lateinit var binding: ActivityHomeBinding
|
||||||
private lateinit var glide: GlideRequests
|
private lateinit var glide: GlideRequests
|
||||||
@ -178,7 +175,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
binding.recyclerView.adapter = homeAdapter
|
binding.recyclerView.adapter = homeAdapter
|
||||||
binding.globalSearchRecycler.adapter = globalSearchAdapter
|
binding.globalSearchRecycler.adapter = globalSearchAdapter
|
||||||
// Set up empty state view
|
// Set up empty state view
|
||||||
binding.createNewPrivateChatButton.setOnClickListener { createNewPrivateChat() }
|
binding.createNewPrivateChatButton.setOnClickListener { showNewConversation() }
|
||||||
IP2Country.configureIfNeeded(this@HomeActivity)
|
IP2Country.configureIfNeeded(this@HomeActivity)
|
||||||
homeViewModel.getObservable(this).observe(this) { newData ->
|
homeViewModel.getObservable(this).observe(this) { newData ->
|
||||||
val manager = binding.recyclerView.layoutManager as LinearLayoutManager
|
val manager = binding.recyclerView.layoutManager as LinearLayoutManager
|
||||||
@ -194,8 +191,8 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
updateEmptyState()
|
updateEmptyState()
|
||||||
}
|
}
|
||||||
homeViewModel.tryUpdateChannel()
|
homeViewModel.tryUpdateChannel()
|
||||||
// Set up new conversation button set
|
// Set up new conversation button
|
||||||
binding.newConversationButtonSet.delegate = this
|
binding.newConversationButton.setOnClickListener { showNewConversation() }
|
||||||
// Observe blocked contacts changed events
|
// Observe blocked contacts changed events
|
||||||
val broadcastReceiver = object : BroadcastReceiver() {
|
val broadcastReceiver = object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
@ -301,7 +298,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
binding.seedReminderView.isVisible = !TextSecurePreferences.getHasViewedSeed(this) && !isShown
|
binding.seedReminderView.isVisible = !TextSecurePreferences.getHasViewedSeed(this) && !isShown
|
||||||
binding.gradientView.isVisible = !isShown
|
binding.gradientView.isVisible = !isShown
|
||||||
binding.globalSearchRecycler.isVisible = isShown
|
binding.globalSearchRecycler.isVisible = isShown
|
||||||
binding.newConversationButtonSet.isVisible = !isShown
|
binding.newConversationButton.isVisible = !isShown
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupMessageRequestsBanner() {
|
private fun setupMessageRequestsBanner() {
|
||||||
@ -354,13 +351,6 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
ApplicationContext.getInstance(this).messageNotifier.setHomeScreenVisible(false)
|
ApplicationContext.getInstance(this).messageNotifier.setHomeScreenVisible(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
|
||||||
if (resultCode == CreateClosedGroupActivity.closedGroupCreatedResultCode) {
|
|
||||||
createNewPrivateChat()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
val broadcastReceiver = this.broadcastReceiver
|
val broadcastReceiver = this.broadcastReceiver
|
||||||
if (broadcastReceiver != null) {
|
if (broadcastReceiver != null) {
|
||||||
@ -623,19 +613,9 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
.create().show()
|
.create().show()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createNewPrivateChat() {
|
private fun showNewConversation() {
|
||||||
val intent = Intent(this, CreatePrivateChatActivity::class.java)
|
NewConversationFragment().show(supportFragmentManager, "NewConversationFragment")
|
||||||
show(intent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createNewClosedGroup() {
|
|
||||||
val intent = Intent(this, CreateClosedGroupActivity::class.java)
|
|
||||||
show(intent, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun joinOpenGroup() {
|
|
||||||
val intent = Intent(this, JoinPublicChatActivity::class.java)
|
|
||||||
show(intent)
|
|
||||||
}
|
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
||||||
|
@ -1,386 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.home
|
|
||||||
|
|
||||||
import android.animation.FloatEvaluator
|
|
||||||
import android.animation.PointFEvaluator
|
|
||||||
import android.animation.ValueAnimator
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.res.ColorStateList
|
|
||||||
import android.graphics.PointF
|
|
||||||
import android.graphics.Typeface
|
|
||||||
import android.os.Build
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.util.TypedValue
|
|
||||||
import android.view.Gravity
|
|
||||||
import android.view.HapticFeedbackConstants
|
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.RelativeLayout
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.annotation.ColorRes
|
|
||||||
import androidx.annotation.DrawableRes
|
|
||||||
import network.loki.messenger.R
|
|
||||||
import org.thoughtcrime.securesms.util.GlowViewUtilities
|
|
||||||
import org.thoughtcrime.securesms.util.NewConversationButtonImageView
|
|
||||||
import org.thoughtcrime.securesms.util.UiModeUtilities
|
|
||||||
import org.thoughtcrime.securesms.util.animateSizeChange
|
|
||||||
import org.thoughtcrime.securesms.util.contains
|
|
||||||
import org.thoughtcrime.securesms.util.disableClipping
|
|
||||||
import org.thoughtcrime.securesms.util.distanceTo
|
|
||||||
import org.thoughtcrime.securesms.util.getColorWithID
|
|
||||||
import org.thoughtcrime.securesms.util.isAbove
|
|
||||||
import org.thoughtcrime.securesms.util.isLeftOf
|
|
||||||
import org.thoughtcrime.securesms.util.isRightOf
|
|
||||||
import org.thoughtcrime.securesms.util.toPx
|
|
||||||
|
|
||||||
class NewConversationButtonSetView : RelativeLayout {
|
|
||||||
private var expandedButton: Button? = null
|
|
||||||
private var previousAction: Int? = null
|
|
||||||
private var isExpanded = false
|
|
||||||
var delegate: NewConversationButtonSetViewDelegate? = null
|
|
||||||
|
|
||||||
// region Convenience
|
|
||||||
private val sessionButtonExpandedPosition: PointF get() { return PointF(width.toFloat() / 2 - sessionButton.expandedSize / 2 - sessionButton.shadowMargin, 0.0f) }
|
|
||||||
private val sessionTooltipExpandedPosition: PointF get() {
|
|
||||||
val x = sessionButtonExpandedPosition.x + sessionButton.width / 2 - sessionTooltip.width / 2
|
|
||||||
val y = sessionButtonExpandedPosition.y + sessionButton.height - sessionTooltip.height / 2
|
|
||||||
return PointF(x, y)
|
|
||||||
}
|
|
||||||
private val closedGroupButtonExpandedPosition: PointF get() { return PointF(width.toFloat() - closedGroupButton.expandedSize - 2 * closedGroupButton.shadowMargin, height.toFloat() - bottomMargin - closedGroupButton.expandedSize - 2 * closedGroupButton.shadowMargin) }
|
|
||||||
private val closedGroupTooltipExpandedPosition: PointF get() {
|
|
||||||
val x = closedGroupButtonExpandedPosition.x + closedGroupButton.width / 2 - closedGroupTooltip.width / 2
|
|
||||||
val y = closedGroupButtonExpandedPosition.y + closedGroupButton.height - closedGroupTooltip.height / 2
|
|
||||||
return PointF(x, y)
|
|
||||||
}
|
|
||||||
private val openGroupButtonExpandedPosition: PointF get() { return PointF(0.0f, height.toFloat() - bottomMargin - openGroupButton.expandedSize - 2 * openGroupButton.shadowMargin) }
|
|
||||||
private val openGroupTooltipExpandedPosition: PointF get() {
|
|
||||||
val x = openGroupButtonExpandedPosition.x + openGroupButton.width / 2 - openGroupTooltip.width / 2
|
|
||||||
val y = openGroupButtonExpandedPosition.y + openGroupButton.height - openGroupTooltip.height / 2
|
|
||||||
return PointF(x, y)
|
|
||||||
}
|
|
||||||
private val buttonRestPosition: PointF get() { return PointF(width.toFloat() / 2 - mainButton.expandedSize / 2 - mainButton.shadowMargin, height.toFloat() - bottomMargin - mainButton.expandedSize - 2 * mainButton.shadowMargin) }
|
|
||||||
private fun tooltipRestPosition(viewWidth: Int): PointF {
|
|
||||||
return PointF(width.toFloat() / 2 - viewWidth / 2, height.toFloat() - bottomMargin)
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Settings
|
|
||||||
private val minDragDistance by lazy { toPx(40, resources).toFloat() }
|
|
||||||
private val maxDragDistance by lazy { toPx(56, resources).toFloat() }
|
|
||||||
private val dragMargin by lazy { toPx(16, resources).toFloat() }
|
|
||||||
private val bottomMargin by lazy { resources.getDimension(R.dimen.new_conversation_button_bottom_offset) }
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Components
|
|
||||||
private val mainButton by lazy { Button(context, true, R.drawable.ic_plus) }
|
|
||||||
private val sessionButton by lazy { Button(context, false, R.drawable.ic_message) }
|
|
||||||
private val sessionTooltip by lazy {
|
|
||||||
TextView(context).apply {
|
|
||||||
setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f)
|
|
||||||
typeface = Typeface.DEFAULT_BOLD
|
|
||||||
setText(R.string.NewConversationButton_SessionTooltip)
|
|
||||||
isAllCaps = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private val closedGroupButton by lazy { Button(context, false, R.drawable.ic_group) }
|
|
||||||
private val closedGroupTooltip by lazy {
|
|
||||||
TextView(context).apply {
|
|
||||||
setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f)
|
|
||||||
typeface = Typeface.DEFAULT_BOLD
|
|
||||||
setText(R.string.NewConversationButton_ClosedGroupTooltip)
|
|
||||||
isAllCaps = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private val openGroupButton by lazy { Button(context, false, R.drawable.ic_globe) }
|
|
||||||
private val openGroupTooltip by lazy {
|
|
||||||
TextView(context).apply {
|
|
||||||
setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f)
|
|
||||||
typeface = Typeface.DEFAULT_BOLD
|
|
||||||
setText(R.string.NewConversationButton_OpenGroupTooltip)
|
|
||||||
isAllCaps = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Button
|
|
||||||
class Button : RelativeLayout {
|
|
||||||
@DrawableRes private var iconID = 0
|
|
||||||
private var isMain = false
|
|
||||||
|
|
||||||
fun getIconID() = iconID
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val animationDuration = 250.toLong()
|
|
||||||
}
|
|
||||||
|
|
||||||
val expandedSize by lazy { resources.getDimension(R.dimen.new_conversation_button_expanded_size) }
|
|
||||||
val collapsedSize by lazy { resources.getDimension(R.dimen.new_conversation_button_collapsed_size) }
|
|
||||||
val shadowMargin by lazy { toPx(6, resources).toFloat() }
|
|
||||||
private val expandedImageViewPosition by lazy { PointF(shadowMargin, shadowMargin) }
|
|
||||||
private val collapsedImageViewPosition by lazy { PointF(shadowMargin + (expandedSize - collapsedSize) / 2, shadowMargin + (expandedSize - collapsedSize) / 2) }
|
|
||||||
|
|
||||||
private val imageView by lazy {
|
|
||||||
val result = NewConversationButtonImageView(context)
|
|
||||||
val size = collapsedSize.toInt()
|
|
||||||
result.layoutParams = LayoutParams(size, size)
|
|
||||||
result.setBackgroundResource(R.drawable.new_conversation_button_background)
|
|
||||||
@ColorRes val backgroundColorID = if (isMain) R.color.accent else R.color.new_conversation_button_collapsed_background
|
|
||||||
@ColorRes val shadowColorID = if (isMain) {
|
|
||||||
R.color.new_conversation_button_shadow
|
|
||||||
} else {
|
|
||||||
if (UiModeUtilities.isDayUiMode(context)) R.color.transparent_black_30 else R.color.black
|
|
||||||
}
|
|
||||||
result.mainColor = resources.getColorWithID(backgroundColorID, context.theme)
|
|
||||||
result.sessionShadowColor = resources.getColorWithID(shadowColorID, context.theme)
|
|
||||||
result.scaleType = ImageView.ScaleType.CENTER
|
|
||||||
result.setImageResource(iconID)
|
|
||||||
result.imageTintList = if (isMain) {
|
|
||||||
ColorStateList.valueOf(resources.getColorWithID(android.R.color.white, context.theme))
|
|
||||||
} else {
|
|
||||||
ColorStateList.valueOf(resources.getColorWithID(R.color.text, context.theme))
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(context: Context) : super(context) { throw IllegalAccessException("Use Button(context:iconID:) instead.") }
|
|
||||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { throw IllegalAccessException("Use Button(context:iconID:) instead.") }
|
|
||||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { throw IllegalAccessException("Use Button(context:iconID:) instead.") }
|
|
||||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) { throw IllegalAccessException("Use Button(context:iconID:) instead.") }
|
|
||||||
|
|
||||||
constructor(context: Context, isMain: Boolean, @DrawableRes iconID: Int) : super(context) {
|
|
||||||
this.iconID = iconID
|
|
||||||
this.isMain = isMain
|
|
||||||
disableClipping()
|
|
||||||
val size = resources.getDimension(R.dimen.new_conversation_button_expanded_size).toInt() + 2 * shadowMargin.toInt()
|
|
||||||
val layoutParams = LayoutParams(size, size)
|
|
||||||
this.layoutParams = layoutParams
|
|
||||||
addView(imageView)
|
|
||||||
imageView.x = collapsedImageViewPosition.x
|
|
||||||
imageView.y = collapsedImageViewPosition.y
|
|
||||||
gravity = Gravity.TOP or Gravity.LEFT // Intentionally not Gravity.START
|
|
||||||
}
|
|
||||||
|
|
||||||
fun expand() {
|
|
||||||
GlowViewUtilities.animateColorChange(context, imageView, R.color.new_conversation_button_collapsed_background, R.color.accent)
|
|
||||||
@ColorRes val startShadowColorID = if (UiModeUtilities.isDayUiMode(context)) R.color.transparent_black_30 else R.color.black
|
|
||||||
GlowViewUtilities.animateShadowColorChange(context, imageView, startShadowColorID, R.color.new_conversation_button_shadow)
|
|
||||||
imageView.animateSizeChange(R.dimen.new_conversation_button_collapsed_size, R.dimen.new_conversation_button_expanded_size, animationDuration)
|
|
||||||
animateImageViewPositionChange(collapsedImageViewPosition, expandedImageViewPosition)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun collapse() {
|
|
||||||
GlowViewUtilities.animateColorChange(context, imageView, R.color.accent, R.color.new_conversation_button_collapsed_background)
|
|
||||||
@ColorRes val endShadowColorID = if (UiModeUtilities.isDayUiMode(context)) R.color.transparent_black_30 else R.color.black
|
|
||||||
GlowViewUtilities.animateShadowColorChange(context, imageView, R.color.new_conversation_button_shadow, endShadowColorID)
|
|
||||||
imageView.animateSizeChange(R.dimen.new_conversation_button_expanded_size, R.dimen.new_conversation_button_collapsed_size, animationDuration)
|
|
||||||
animateImageViewPositionChange(expandedImageViewPosition, collapsedImageViewPosition)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun animateImageViewPositionChange(startPosition: PointF, endPosition: PointF) {
|
|
||||||
val animation = ValueAnimator.ofObject(PointFEvaluator(), startPosition, endPosition)
|
|
||||||
animation.duration = animationDuration
|
|
||||||
animation.addUpdateListener { animator ->
|
|
||||||
val point = animator.animatedValue as PointF
|
|
||||||
imageView.x = point.x
|
|
||||||
imageView.y = point.y
|
|
||||||
}
|
|
||||||
animation.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun animatePositionChange(startPosition: PointF, endPosition: PointF) {
|
|
||||||
val animation = ValueAnimator.ofObject(PointFEvaluator(), startPosition, endPosition)
|
|
||||||
animation.duration = animationDuration
|
|
||||||
animation.addUpdateListener { animator ->
|
|
||||||
val point = animator.animatedValue as PointF
|
|
||||||
x = point.x
|
|
||||||
y = point.y
|
|
||||||
}
|
|
||||||
animation.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun animateAlphaChange(startAlpha: Float, endAlpha: Float) {
|
|
||||||
val animation = ValueAnimator.ofObject(FloatEvaluator(), startAlpha, endAlpha)
|
|
||||||
animation.duration = animationDuration
|
|
||||||
animation.addUpdateListener { animator ->
|
|
||||||
alpha = animator.animatedValue as Float
|
|
||||||
}
|
|
||||||
animation.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Lifecycle
|
|
||||||
constructor(context: Context) : super(context) { setUpViewHierarchy() }
|
|
||||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { setUpViewHierarchy() }
|
|
||||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { setUpViewHierarchy() }
|
|
||||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) { setUpViewHierarchy() }
|
|
||||||
|
|
||||||
private fun setUpViewHierarchy() {
|
|
||||||
disableClipping()
|
|
||||||
isHapticFeedbackEnabled = true
|
|
||||||
// Set up session button
|
|
||||||
addView(sessionButton)
|
|
||||||
addView(sessionTooltip)
|
|
||||||
sessionButton.alpha = 0.0f
|
|
||||||
sessionTooltip.alpha = 0.0f
|
|
||||||
val sessionButtonLayoutParams = sessionButton.layoutParams as LayoutParams
|
|
||||||
sessionButtonLayoutParams.addRule(CENTER_IN_PARENT, TRUE)
|
|
||||||
sessionButtonLayoutParams.addRule(ALIGN_PARENT_BOTTOM, TRUE)
|
|
||||||
sessionButtonLayoutParams.bottomMargin = bottomMargin.toInt()
|
|
||||||
// Set up closed group button
|
|
||||||
addView(closedGroupButton)
|
|
||||||
addView(closedGroupTooltip)
|
|
||||||
closedGroupButton.alpha = 0.0f
|
|
||||||
closedGroupTooltip.alpha = 0.0f
|
|
||||||
val closedGroupButtonLayoutParams = closedGroupButton.layoutParams as LayoutParams
|
|
||||||
closedGroupButtonLayoutParams.addRule(CENTER_IN_PARENT, TRUE)
|
|
||||||
closedGroupButtonLayoutParams.addRule(ALIGN_PARENT_BOTTOM, TRUE)
|
|
||||||
closedGroupButtonLayoutParams.bottomMargin = bottomMargin.toInt()
|
|
||||||
// Set up open group button
|
|
||||||
addView(openGroupButton)
|
|
||||||
addView(openGroupTooltip)
|
|
||||||
openGroupButton.alpha = 0.0f
|
|
||||||
openGroupTooltip.alpha = 0.0f
|
|
||||||
val openGroupButtonLayoutParams = openGroupButton.layoutParams as LayoutParams
|
|
||||||
openGroupButtonLayoutParams.addRule(CENTER_IN_PARENT, TRUE)
|
|
||||||
openGroupButtonLayoutParams.addRule(ALIGN_PARENT_BOTTOM, TRUE)
|
|
||||||
openGroupButtonLayoutParams.bottomMargin = bottomMargin.toInt()
|
|
||||||
// Set up main button
|
|
||||||
addView(mainButton)
|
|
||||||
val mainButtonLayoutParams = mainButton.layoutParams as LayoutParams
|
|
||||||
mainButtonLayoutParams.addRule(CENTER_IN_PARENT, TRUE)
|
|
||||||
mainButtonLayoutParams.addRule(ALIGN_PARENT_BOTTOM, TRUE)
|
|
||||||
mainButtonLayoutParams.bottomMargin = bottomMargin.toInt()
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Interaction
|
|
||||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
|
||||||
val touch = PointF(event.x, event.y)
|
|
||||||
val allButtons = listOf( mainButton, sessionButton, closedGroupButton, openGroupButton )
|
|
||||||
val buttonsExcludingMainButton = listOf( sessionButton, closedGroupButton, openGroupButton )
|
|
||||||
if (allButtons.none { it.contains(touch) }) { return false }
|
|
||||||
when (event.action) {
|
|
||||||
MotionEvent.ACTION_DOWN -> {
|
|
||||||
if (isExpanded) {
|
|
||||||
if (mainButton.contains(touch)) { collapse() }
|
|
||||||
} else {
|
|
||||||
isExpanded = true
|
|
||||||
expand()
|
|
||||||
}
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
|
|
||||||
} else {
|
|
||||||
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MotionEvent.ACTION_MOVE -> {
|
|
||||||
mainButton.x = touch.x - mainButton.expandedSize / 2
|
|
||||||
mainButton.y = touch.y - mainButton.expandedSize / 2
|
|
||||||
mainButton.alpha = 1 - (PointF(mainButton.x, mainButton.y).distanceTo(buttonRestPosition) / maxDragDistance)
|
|
||||||
val buttonToExpand = buttonsExcludingMainButton.firstOrNull { button ->
|
|
||||||
var hasUserDraggedBeyondButton = false
|
|
||||||
if (button == openGroupButton && touch.isLeftOf(openGroupButton, dragMargin)) { hasUserDraggedBeyondButton = true }
|
|
||||||
if (button == sessionButton && touch.isAbove(sessionButton, dragMargin)) { hasUserDraggedBeyondButton = true }
|
|
||||||
if (button == closedGroupButton && touch.isRightOf(closedGroupButton, dragMargin)) { hasUserDraggedBeyondButton = true }
|
|
||||||
button.contains(touch) || hasUserDraggedBeyondButton
|
|
||||||
}
|
|
||||||
if (buttonToExpand != null) {
|
|
||||||
if (buttonToExpand == expandedButton) { return true }
|
|
||||||
expandedButton?.collapse()
|
|
||||||
buttonToExpand.expand()
|
|
||||||
this.expandedButton = buttonToExpand
|
|
||||||
} else {
|
|
||||||
expandedButton?.collapse()
|
|
||||||
this.expandedButton = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
|
||||||
val mainButtonCenter = PointF(width.toFloat() / 2, height.toFloat() - bottomMargin - mainButton.expandedSize / 2)
|
|
||||||
val distanceFromMainButtonCenter = touch.distanceTo(mainButtonCenter)
|
|
||||||
fun collapse() {
|
|
||||||
isExpanded = false
|
|
||||||
this.collapse()
|
|
||||||
}
|
|
||||||
if (distanceFromMainButtonCenter > (minDragDistance + mainButton.collapsedSize / 2)) {
|
|
||||||
if (sessionButton.contains(touch) || touch.isAbove(sessionButton, dragMargin)) { delegate?.createNewPrivateChat(); collapse() }
|
|
||||||
else if (closedGroupButton.contains(touch) || touch.isRightOf(closedGroupButton, dragMargin)) { delegate?.createNewClosedGroup(); collapse() }
|
|
||||||
else if (openGroupButton.contains(touch) || touch.isLeftOf(openGroupButton, dragMargin)) { delegate?.joinOpenGroup(); collapse() }
|
|
||||||
else { collapse() }
|
|
||||||
} else {
|
|
||||||
val currentPosition = PointF(mainButton.x, mainButton.y)
|
|
||||||
mainButton.animatePositionChange(currentPosition, buttonRestPosition)
|
|
||||||
val endAlpha = 1.0f
|
|
||||||
mainButton.animateAlphaChange(mainButton.alpha, endAlpha)
|
|
||||||
expandedButton?.collapse()
|
|
||||||
this.expandedButton = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
previousAction = event.action
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun expand() {
|
|
||||||
val buttonsExcludingMainButton = listOf( sessionButton, closedGroupButton, openGroupButton )
|
|
||||||
val allTooltips = listOf(sessionTooltip, closedGroupTooltip, openGroupTooltip)
|
|
||||||
|
|
||||||
sessionButton.animatePositionChange(buttonRestPosition, sessionButtonExpandedPosition)
|
|
||||||
sessionTooltip.animatePositionChange(tooltipRestPosition(sessionTooltip.width), sessionTooltipExpandedPosition)
|
|
||||||
closedGroupButton.animatePositionChange(buttonRestPosition, closedGroupButtonExpandedPosition)
|
|
||||||
closedGroupTooltip.animatePositionChange(tooltipRestPosition(closedGroupTooltip.width), closedGroupTooltipExpandedPosition)
|
|
||||||
openGroupButton.animatePositionChange(buttonRestPosition, openGroupButtonExpandedPosition)
|
|
||||||
openGroupTooltip.animatePositionChange(tooltipRestPosition(openGroupTooltip.width), openGroupTooltipExpandedPosition)
|
|
||||||
buttonsExcludingMainButton.forEach { it.animateAlphaChange(0.0f, 1.0f) }
|
|
||||||
allTooltips.forEach { it.animateAlphaChange(0.0f, 1.0f) }
|
|
||||||
postDelayed({ isExpanded = true }, Button.animationDuration)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun collapse() {
|
|
||||||
val allButtons = listOf( mainButton, sessionButton, closedGroupButton, openGroupButton )
|
|
||||||
val allTooltips = listOf(sessionTooltip, closedGroupTooltip, openGroupTooltip)
|
|
||||||
|
|
||||||
allButtons.forEach {
|
|
||||||
val currentPosition = PointF(it.x, it.y)
|
|
||||||
it.animatePositionChange(currentPosition, buttonRestPosition)
|
|
||||||
val endAlpha = if (it == mainButton) 1.0f else 0.0f
|
|
||||||
it.animateAlphaChange(it.alpha, endAlpha)
|
|
||||||
}
|
|
||||||
allTooltips.forEach {
|
|
||||||
it.animateAlphaChange(1.0f, 0.0f)
|
|
||||||
it.animatePositionChange(PointF(it.x, it.y), tooltipRestPosition(it.width))
|
|
||||||
}
|
|
||||||
postDelayed({ isExpanded = false }, Button.animationDuration)
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
fun View.animatePositionChange(startPosition: PointF, endPosition: PointF) {
|
|
||||||
val animation = ValueAnimator.ofObject(PointFEvaluator(), startPosition, endPosition)
|
|
||||||
animation.duration = Button.animationDuration
|
|
||||||
animation.addUpdateListener { animator ->
|
|
||||||
val point = animator.animatedValue as PointF
|
|
||||||
x = point.x
|
|
||||||
y = point.y
|
|
||||||
}
|
|
||||||
animation.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun View.animateAlphaChange(startAlpha: Float, endAlpha: Float) {
|
|
||||||
val animation = ValueAnimator.ofObject(FloatEvaluator(), startAlpha, endAlpha)
|
|
||||||
animation.duration = Button.animationDuration
|
|
||||||
animation.addUpdateListener { animator ->
|
|
||||||
alpha = animator.animatedValue as Float
|
|
||||||
}
|
|
||||||
animation.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// region Delegate
|
|
||||||
interface NewConversationButtonSetViewDelegate {
|
|
||||||
|
|
||||||
fun joinOpenGroup()
|
|
||||||
fun createNewPrivateChat()
|
|
||||||
fun createNewClosedGroup()
|
|
||||||
}
|
|
||||||
// endregion
|
|
@ -6,6 +6,7 @@ import android.view.LayoutInflater
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import network.loki.messenger.databinding.FragmentScanQrCodeBinding
|
import network.loki.messenger.databinding.FragmentScanQrCodeBinding
|
||||||
import org.thoughtcrime.securesms.qr.ScanListener
|
import org.thoughtcrime.securesms.qr.ScanListener
|
||||||
@ -30,6 +31,7 @@ class ScanQRCodeFragment : Fragment() {
|
|||||||
else -> binding.overlayView.orientation = LinearLayout.VERTICAL
|
else -> binding.overlayView.orientation = LinearLayout.VERTICAL
|
||||||
}
|
}
|
||||||
binding.messageTextView.text = message
|
binding.messageTextView.text = message
|
||||||
|
binding.messageTextView.isVisible = message.isNotEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
|
@ -11,7 +11,6 @@ import androidx.fragment.app.Fragment
|
|||||||
import com.tbruyelle.rxpermissions2.RxPermissions
|
import com.tbruyelle.rxpermissions2.RxPermissions
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.thoughtcrime.securesms.qr.ScanListener
|
import org.thoughtcrime.securesms.qr.ScanListener
|
||||||
import org.thoughtcrime.securesms.util.ScanQRCodeFragment
|
|
||||||
|
|
||||||
class ScanQRCodeWrapperFragment : Fragment(), ScanQRCodePlaceholderFragmentDelegate, ScanListener {
|
class ScanQRCodeWrapperFragment : Fragment(), ScanQRCodePlaceholderFragmentDelegate, ScanListener {
|
||||||
|
|
||||||
@ -80,14 +79,14 @@ class ScanQRCodeWrapperFragment : Fragment(), ScanQRCodePlaceholderFragmentDeleg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onQrDataFound(string: String) {
|
override fun onQrDataFound(data: String) {
|
||||||
activity?.runOnUiThread {
|
activity?.runOnUiThread {
|
||||||
delegate?.handleQRCodeScanned(string)
|
delegate?.handleQRCodeScanned(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ScanQRCodeWrapperFragmentDelegate {
|
fun interface ScanQRCodeWrapperFragmentDelegate {
|
||||||
|
|
||||||
fun handleQRCodeScanned(string: String)
|
fun handleQRCodeScanned(string: String)
|
||||||
}
|
}
|
@ -4,10 +4,12 @@ import android.animation.Animator
|
|||||||
import android.animation.AnimatorListenerAdapter
|
import android.animation.AnimatorListenerAdapter
|
||||||
import android.animation.FloatEvaluator
|
import android.animation.FloatEvaluator
|
||||||
import android.animation.ValueAnimator
|
import android.animation.ValueAnimator
|
||||||
|
import android.content.Context
|
||||||
import android.graphics.PointF
|
import android.graphics.PointF
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import androidx.annotation.DimenRes
|
import androidx.annotation.DimenRes
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
|
||||||
fun View.contains(point: PointF): Boolean {
|
fun View.contains(point: PointF): Boolean {
|
||||||
return hitRect.contains(point.x.toInt(), point.y.toInt())
|
return hitRect.contains(point.x.toInt(), point.y.toInt())
|
||||||
@ -52,3 +54,8 @@ fun View.fadeOut(duration: Long = 150) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun View.hideKeyboard() {
|
||||||
|
val imm = this.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
imm.hideSoftInputFromWindow(this.windowToken, 0)
|
||||||
|
}
|
||||||
|
10
app/src/main/res/drawable/ic_baseline_close_24.xml
Normal file
10
app/src/main/res/drawable/ic_baseline_close_24.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z" />
|
||||||
|
</vector>
|
@ -1,88 +0,0 @@
|
|||||||
<?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"
|
|
||||||
android:id="@+id/contentView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:gravity="center_horizontal"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="1px"
|
|
||||||
android:background="?android:dividerHorizontal"
|
|
||||||
android:elevation="1dp" />
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
style="@style/SmallSessionEditText"
|
|
||||||
android:id="@+id/chatURLEditText"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="64dp"
|
|
||||||
android:layout_marginLeft="@dimen/large_spacing"
|
|
||||||
android:layout_marginTop="@dimen/large_spacing"
|
|
||||||
android:layout_marginRight="@dimen/large_spacing"
|
|
||||||
android:inputType="textWebEmailAddress|textMultiLine"
|
|
||||||
android:paddingTop="0dp"
|
|
||||||
android:paddingBottom="0dp"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:maxLines="3"
|
|
||||||
android:hint="@string/fragment_enter_chat_url_edit_text_hint" />
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:id="@+id/defaultRoomsLoaderContainer"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="80dp"
|
|
||||||
android:layout_marginTop="56dp"
|
|
||||||
android:visibility="gone">
|
|
||||||
|
|
||||||
<com.github.ybq.android.spinkit.SpinKitView
|
|
||||||
android:id="@+id/defaultRoomsLoader"
|
|
||||||
style="@style/SpinKitView.Large.ThreeBounce"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:SpinKit_Color="@color/text"
|
|
||||||
android:visibility="gone"
|
|
||||||
android:layout_centerInParent="true" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:visibility="gone"
|
|
||||||
android:id="@+id/defaultRoomsContainer"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_marginVertical="16dp"
|
|
||||||
android:textSize="18sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:paddingHorizontal="24dp"
|
|
||||||
android:text="@string/activity_join_public_chat_join_rooms"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"/>
|
|
||||||
|
|
||||||
<GridLayout
|
|
||||||
android:id="@+id/defaultRoomsGridLayout"
|
|
||||||
android:columnCount="2"
|
|
||||||
android:paddingHorizontal="16dp"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
style="@style/Widget.Session.Button.Common.ProminentOutline"
|
|
||||||
android:id="@+id/joinPublicChatButton"
|
|
||||||
android:layout_width="196dp"
|
|
||||||
android:layout_height="@dimen/medium_button_height"
|
|
||||||
android:layout_marginBottom="@dimen/medium_spacing"
|
|
||||||
android:text="@string/next" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
@ -1,115 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:id="@+id/contentView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:gravity="center_horizontal"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="1px"
|
|
||||||
android:background="?android:dividerHorizontal"
|
|
||||||
android:elevation="1dp" />
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
style="@style/SmallSessionEditText"
|
|
||||||
android:id="@+id/publicKeyEditText"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="64dp"
|
|
||||||
android:layout_marginLeft="@dimen/large_spacing"
|
|
||||||
android:layout_marginTop="@dimen/large_spacing"
|
|
||||||
android:layout_marginRight="@dimen/large_spacing"
|
|
||||||
android:paddingTop="0dp"
|
|
||||||
android:paddingBottom="0dp"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:inputType="textMultiLine"
|
|
||||||
android:maxLines="3"
|
|
||||||
android:imeOptions="actionDone"
|
|
||||||
android:hint="@string/fragment_enter_public_key_edit_text_hint" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginLeft="@dimen/large_spacing"
|
|
||||||
android:layout_marginTop="@dimen/medium_spacing"
|
|
||||||
android:layout_marginRight="@dimen/large_spacing"
|
|
||||||
android:textSize="@dimen/very_small_font_size"
|
|
||||||
android:textColor="@color/text"
|
|
||||||
android:alpha="0.6"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:text="@string/fragment_enter_public_key_explanation" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/optionalContentContainer"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.LabeledSeparatorView
|
|
||||||
android:id="@+id/separatorView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="32dp"
|
|
||||||
android:layout_marginLeft="@dimen/large_spacing"
|
|
||||||
android:layout_marginTop="@dimen/large_spacing"
|
|
||||||
android:layout_marginRight="@dimen/large_spacing" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/publicKeyTextView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginLeft="@dimen/large_spacing"
|
|
||||||
android:layout_marginTop="@dimen/large_spacing"
|
|
||||||
android:layout_marginRight="@dimen/large_spacing"
|
|
||||||
android:textSize="@dimen/medium_font_size"
|
|
||||||
android:textColor="@color/text"
|
|
||||||
android:fontFamily="@font/space_mono_regular"
|
|
||||||
android:textAlignment="center"
|
|
||||||
tools:text="05987d601943c267879be41830888066c6a024cbdc9a548d06813924bf3372ea78" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginLeft="@dimen/large_spacing"
|
|
||||||
android:layout_marginTop="@dimen/large_spacing"
|
|
||||||
android:layout_marginRight="@dimen/large_spacing"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<Button
|
|
||||||
style="@style/Widget.Session.Button.Common.UnimportantFilled"
|
|
||||||
android:id="@+id/copyButton"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="@dimen/medium_button_height"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="@string/copy" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
style="@style/Widget.Session.Button.Common.UnimportantFilled"
|
|
||||||
android:id="@+id/shareButton"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="@dimen/medium_button_height"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_marginStart="@dimen/medium_spacing"
|
|
||||||
android:text="@string/share" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/spacer"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
style="@style/Widget.Session.Button.Common.ProminentOutline"
|
|
||||||
android:id="@+id/createPrivateChatButton"
|
|
||||||
android:layout_width="196dp"
|
|
||||||
android:layout_height="@dimen/medium_button_height"
|
|
||||||
android:layout_marginBottom="@dimen/medium_spacing"
|
|
||||||
android:text="@string/next" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
@ -1,86 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<RelativeLayout
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<ScrollView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" >
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/mainContentContainer"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
style="@style/SmallSessionEditText"
|
|
||||||
android:id="@+id/nameEditText"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="64dp"
|
|
||||||
android:layout_marginLeft="@dimen/large_spacing"
|
|
||||||
android:layout_marginTop="@dimen/medium_spacing"
|
|
||||||
android:layout_marginRight="@dimen/large_spacing"
|
|
||||||
android:layout_marginBottom="@dimen/medium_spacing"
|
|
||||||
android:hint="@string/activity_create_closed_group_edit_text_hint" />
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="1px"
|
|
||||||
android:background="?android:dividerHorizontal" />
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/recyclerView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</ScrollView>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/emptyStateContainer"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_horizontal"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_centerInParent="true">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textSize="@dimen/medium_font_size"
|
|
||||||
android:textColor="@color/text"
|
|
||||||
android:text="@string/activity_create_closed_group_empty_state_message" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
style="@style/Widget.Session.Button.Common.ProminentOutline"
|
|
||||||
android:id="@+id/createNewPrivateChatButton"
|
|
||||||
android:layout_width="196dp"
|
|
||||||
android:layout_height="@dimen/medium_button_height"
|
|
||||||
android:layout_marginTop="@dimen/medium_spacing"
|
|
||||||
android:text="@string/activity_create_closed_group_empty_state_button_title" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:id="@+id/loaderContainer"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:background="#A4000000"
|
|
||||||
android:visibility="gone"
|
|
||||||
android:alpha="0">
|
|
||||||
|
|
||||||
<com.github.ybq.android.spinkit.SpinKitView
|
|
||||||
style="@style/SpinKitView.Large.ThreeBounce"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:layout_centerInParent="true"
|
|
||||||
app:SpinKit_Color="@android:color/white" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
@ -1,40 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<RelativeLayout
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:id="@+id/rootLayout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<androidx.viewpager.widget.ViewPager
|
|
||||||
android:id="@+id/viewPager"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" >
|
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabLayout
|
|
||||||
style="@style/Widget.Session.TabLayout"
|
|
||||||
android:id="@+id/tabLayout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/tab_bar_height" />
|
|
||||||
|
|
||||||
</androidx.viewpager.widget.ViewPager>
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:id="@+id/loader"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:background="#A4000000"
|
|
||||||
android:visibility="gone"
|
|
||||||
android:alpha="0">
|
|
||||||
|
|
||||||
<com.github.ybq.android.spinkit.SpinKitView
|
|
||||||
style="@style/SpinKitView.Large.ThreeBounce"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:layout_centerInParent="true"
|
|
||||||
app:SpinKit_Color="@android:color/white" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
@ -157,12 +157,16 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.home.NewConversationButtonSetView
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
android:id="@+id/newConversationButtonSet"
|
android:id="@+id/newConversationButton"
|
||||||
android:layout_width="276dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="236dp"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentBottom="true"
|
android:layout_alignParentBottom="true"
|
||||||
android:layout_centerHorizontal="true" />
|
android:layout_centerHorizontal="true"
|
||||||
|
android:background="@drawable/new_conversation_button_background"
|
||||||
|
android:layout_marginBottom="@dimen/new_conversation_button_bottom_offset"
|
||||||
|
android:src="@drawable/ic_plus"
|
||||||
|
app:tint="@color/white" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
|
23
app/src/main/res/layout/contact_section_header.xml
Normal file
23
app/src/main/res/layout/contact_section_header.xml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/bottom_sheet_background"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingVertical="@dimen/very_small_spacing"
|
||||||
|
android:paddingHorizontal="@dimen/large_spacing"
|
||||||
|
android:textColor="@color/text"
|
||||||
|
tools:text="A" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1px"
|
||||||
|
android:background="?dividerHorizontal" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
@ -7,11 +7,10 @@
|
|||||||
style="?attr/chipStyle"
|
style="?attr/chipStyle"
|
||||||
app:chipStartPadding="4dp"
|
app:chipStartPadding="4dp"
|
||||||
app:chipBackgroundColor="@color/open_group_chip_color"
|
app:chipBackgroundColor="@color/open_group_chip_color"
|
||||||
android:layout_columnWeight="1"
|
|
||||||
tools:text="Main Group"
|
tools:text="Main Group"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
tools:layout_width="wrap_content"
|
tools:layout_width="wrap_content"
|
||||||
app:chipMinTouchTargetSize="0dp"
|
app:chipMinTouchTargetSize="0dp"
|
||||||
android:layout_margin="4dp"
|
android:layout_margin="4dp"
|
||||||
android:layout_width="0dp"
|
android:layout_width="156dp"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content" />
|
163
app/src/main/res/layout/fragment_create_group.xml
Normal file
163
app/src/main/res/layout/fragment_create_group.xml
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
<?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/rootLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/bottom_sheet_background">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/titleText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/setting_button_height"
|
||||||
|
android:gravity="center_horizontal|center_vertical"
|
||||||
|
android:text="@string/activity_create_group_title"
|
||||||
|
android:textColor="@color/text"
|
||||||
|
android:textSize="@dimen/very_large_font_size"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/backButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/medium_spacing"
|
||||||
|
android:clickable="true"
|
||||||
|
android:contentDescription="@string/new_conversation_dialog_back_button_content_description"
|
||||||
|
android:focusable="true"
|
||||||
|
android:src="@drawable/ic_arrow_left_24"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/titleText"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/titleText" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/closeButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="@dimen/medium_spacing"
|
||||||
|
android:clickable="true"
|
||||||
|
android:contentDescription="@string/new_conversation_dialog_close_button_content_description"
|
||||||
|
android:focusable="true"
|
||||||
|
android:src="@drawable/ic_baseline_close_24"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/titleText"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/titleText" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/nameEditText"
|
||||||
|
style="@style/SmallSessionEditText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="64dp"
|
||||||
|
android:layout_marginLeft="@dimen/large_spacing"
|
||||||
|
android:layout_marginTop="@dimen/medium_spacing"
|
||||||
|
android:layout_marginRight="@dimen/large_spacing"
|
||||||
|
android:layout_marginBottom="@dimen/medium_spacing"
|
||||||
|
android:hint="@string/activity_create_closed_group_edit_text_hint"
|
||||||
|
android:maxLength="30"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/titleText" />
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.keyboard.emoji.KeyboardPageSearchView
|
||||||
|
android:id="@+id/contactSearch"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginTop="@dimen/medium_spacing"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/nameEditText"
|
||||||
|
app:search_hint="@string/search_contacts_hint"
|
||||||
|
app:show_always="true" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginTop="@dimen/small_spacing"
|
||||||
|
android:layout_marginBottom="@dimen/large_spacing"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/createClosedGroupButton"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/contactSearch"
|
||||||
|
tools:itemCount="5"
|
||||||
|
tools:listitem="@layout/view_user" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/createClosedGroupButton"
|
||||||
|
style="@style/Widget.Session.Button.Common.ProminentOutline"
|
||||||
|
android:layout_width="196dp"
|
||||||
|
android:layout_height="@dimen/medium_button_height"
|
||||||
|
android:layout_marginVertical="@dimen/large_spacing"
|
||||||
|
android:text="@string/activity_create_group_create_button_title"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/recyclerView"/>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Group
|
||||||
|
android:id="@+id/mainContentGroup"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:constraint_referenced_ids="nameEditText, contactSearch, recyclerView, createClosedGroupButton"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/emptyStateMessageTextView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/activity_create_closed_group_empty_state_message"
|
||||||
|
android:textColor="@color/text"
|
||||||
|
android:textSize="@dimen/medium_font_size"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/createNewPrivateChatButton"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/titleText" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/createNewPrivateChatButton"
|
||||||
|
style="@style/Widget.Session.Button.Common.ProminentOutline"
|
||||||
|
android:layout_width="196dp"
|
||||||
|
android:layout_height="@dimen/medium_button_height"
|
||||||
|
android:layout_marginVertical="@dimen/medium_spacing"
|
||||||
|
android:text="@string/activity_create_closed_group_empty_state_button_title"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/emptyStateMessageTextView" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Group
|
||||||
|
android:id="@+id/emptyStateGroup"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:constraint_referenced_ids="emptyStateMessageTextView, createNewPrivateChatButton"
|
||||||
|
tools:visibility="gone" />
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/loaderContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:alpha="0"
|
||||||
|
android:background="#A4000000"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<com.github.ybq.android.spinkit.SpinKitView
|
||||||
|
style="@style/SpinKitView.Large.ThreeBounce"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
app:SpinKit_Color="@android:color/white" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,88 +0,0 @@
|
|||||||
<?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"
|
|
||||||
android:id="@+id/contentView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:gravity="center_horizontal"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="1px"
|
|
||||||
android:background="?android:dividerHorizontal"
|
|
||||||
android:elevation="1dp" />
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
style="@style/SmallSessionEditText"
|
|
||||||
android:id="@+id/chatURLEditText"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="64dp"
|
|
||||||
android:layout_marginLeft="@dimen/large_spacing"
|
|
||||||
android:layout_marginTop="@dimen/large_spacing"
|
|
||||||
android:layout_marginRight="@dimen/large_spacing"
|
|
||||||
android:inputType="textWebEmailAddress|textMultiLine"
|
|
||||||
android:paddingTop="0dp"
|
|
||||||
android:paddingBottom="0dp"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:maxLines="3"
|
|
||||||
android:hint="@string/fragment_enter_chat_url_edit_text_hint" />
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:id="@+id/defaultRoomsLoaderContainer"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="80dp"
|
|
||||||
android:layout_marginTop="56dp"
|
|
||||||
android:visibility="gone">
|
|
||||||
|
|
||||||
<com.github.ybq.android.spinkit.SpinKitView
|
|
||||||
android:id="@+id/defaultRoomsLoader"
|
|
||||||
style="@style/SpinKitView.Large.ThreeBounce"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:SpinKit_Color="@color/text"
|
|
||||||
android:visibility="gone"
|
|
||||||
android:layout_centerInParent="true" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:visibility="gone"
|
|
||||||
android:id="@+id/defaultRoomsContainer"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_marginVertical="16dp"
|
|
||||||
android:textSize="18sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:paddingHorizontal="24dp"
|
|
||||||
android:text="@string/activity_join_public_chat_join_rooms"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"/>
|
|
||||||
|
|
||||||
<GridLayout
|
|
||||||
android:id="@+id/defaultRoomsGridLayout"
|
|
||||||
android:columnCount="2"
|
|
||||||
android:paddingHorizontal="16dp"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
style="@style/Widget.Session.Button.Common.ProminentOutline"
|
|
||||||
android:id="@+id/joinPublicChatButton"
|
|
||||||
android:layout_width="196dp"
|
|
||||||
android:layout_height="@dimen/medium_button_height"
|
|
||||||
android:layout_marginBottom="@dimen/medium_spacing"
|
|
||||||
android:text="@string/next" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
103
app/src/main/res/layout/fragment_enter_community_url.xml
Normal file
103
app/src/main/res/layout/fragment_enter_community_url.xml
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
<?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/mainContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/divider"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1px"
|
||||||
|
android:background="?android:dividerHorizontal"
|
||||||
|
android:elevation="1dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/communityUrlEditText"
|
||||||
|
style="@style/SmallSessionEditText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="64dp"
|
||||||
|
android:layout_marginHorizontal="@dimen/large_spacing"
|
||||||
|
android:layout_marginTop="@dimen/large_spacing"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:hint="@string/fragment_enter_community_url_edit_text_hint"
|
||||||
|
android:inputType="textWebEmailAddress|textMultiLine"
|
||||||
|
android:maxLines="3"
|
||||||
|
android:paddingTop="0dp"
|
||||||
|
android:paddingBottom="0dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/divider" />
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/defaultRoomsLoaderContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="80dp"
|
||||||
|
android:layout_marginTop="56dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/communityUrlEditText">
|
||||||
|
|
||||||
|
<com.github.ybq.android.spinkit.SpinKitView
|
||||||
|
android:id="@+id/defaultRoomsLoader"
|
||||||
|
style="@style/SpinKitView.Large.ThreeBounce"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:SpinKit_Color="@color/text" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/defaultRoomsContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/joinCommunityButton"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/communityUrlEditText"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginVertical="16dp"
|
||||||
|
android:paddingHorizontal="24dp"
|
||||||
|
android:text="@string/activity_join_public_chat_join_rooms"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<com.google.android.flexbox.FlexboxLayout
|
||||||
|
android:id="@+id/defaultRoomsFlexboxLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
app:alignItems="center"
|
||||||
|
app:flexDirection="row"
|
||||||
|
app:flexWrap="wrap"
|
||||||
|
app:justifyContent="center" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/joinCommunityButton"
|
||||||
|
style="@style/Widget.Session.Button.Common.ProminentOutline"
|
||||||
|
android:layout_width="196dp"
|
||||||
|
android:layout_height="@dimen/medium_button_height"
|
||||||
|
android:layout_marginVertical="@dimen/large_spacing"
|
||||||
|
android:text="@string/fragment_enter_community_url_join_button_title"
|
||||||
|
android:enabled="false"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,115 +1,144 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout
|
<org.thoughtcrime.securesms.components.NestedScrollableHost xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
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"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/contentView"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:gravity="center_horizontal"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<View
|
<ScrollView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1px"
|
android:layout_height="match_parent"
|
||||||
android:background="?android:dividerHorizontal"
|
android:fillViewport="true">
|
||||||
android:elevation="1dp" />
|
|
||||||
|
|
||||||
<EditText
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
style="@style/SmallSessionEditText"
|
|
||||||
android:id="@+id/publicKeyEditText"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="64dp"
|
|
||||||
android:layout_marginLeft="@dimen/large_spacing"
|
|
||||||
android:layout_marginTop="@dimen/large_spacing"
|
|
||||||
android:layout_marginRight="@dimen/large_spacing"
|
|
||||||
android:paddingTop="0dp"
|
|
||||||
android:paddingBottom="0dp"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:inputType="textMultiLine"
|
|
||||||
android:maxLines="3"
|
|
||||||
android:imeOptions="actionDone"
|
|
||||||
android:hint="@string/fragment_enter_public_key_edit_text_hint" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginLeft="@dimen/large_spacing"
|
|
||||||
android:layout_marginTop="@dimen/medium_spacing"
|
|
||||||
android:layout_marginRight="@dimen/large_spacing"
|
|
||||||
android:textSize="@dimen/very_small_font_size"
|
|
||||||
android:textColor="@color/text"
|
|
||||||
android:alpha="0.6"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:text="@string/fragment_enter_public_key_explanation" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/optionalContentContainer"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.LabeledSeparatorView
|
|
||||||
android:id="@+id/separatorView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="32dp"
|
|
||||||
android:layout_marginLeft="@dimen/large_spacing"
|
|
||||||
android:layout_marginTop="@dimen/large_spacing"
|
|
||||||
android:layout_marginRight="@dimen/large_spacing" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/publicKeyTextView"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginLeft="@dimen/large_spacing"
|
android:id="@+id/mainContainer">
|
||||||
android:layout_marginTop="@dimen/large_spacing"
|
|
||||||
android:layout_marginRight="@dimen/large_spacing"
|
|
||||||
android:textSize="@dimen/medium_font_size"
|
|
||||||
android:textColor="@color/text"
|
|
||||||
android:fontFamily="@font/space_mono_regular"
|
|
||||||
android:textAlignment="center"
|
|
||||||
tools:text="05987d601943c267879be41830888066c6a024cbdc9a548d06813924bf3372ea78" />
|
|
||||||
|
|
||||||
<LinearLayout
|
<View
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/divider"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_marginLeft="@dimen/large_spacing"
|
android:layout_height="1px"
|
||||||
android:layout_marginTop="@dimen/large_spacing"
|
android:background="?android:dividerHorizontal"
|
||||||
android:layout_marginRight="@dimen/large_spacing"
|
android:elevation="1dp"
|
||||||
android:orientation="horizontal">
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/publicKeyEditText"
|
||||||
|
style="@style/SmallSessionEditText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="64dp"
|
||||||
|
android:layout_marginHorizontal="@dimen/large_spacing"
|
||||||
|
android:layout_marginTop="@dimen/large_spacing"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:hint="@string/fragment_enter_public_key_edit_text_hint"
|
||||||
|
android:imeOptions="actionDone"
|
||||||
|
android:inputType="textMultiLine"
|
||||||
|
android:maxLines="3"
|
||||||
|
android:paddingTop="0dp"
|
||||||
|
android:paddingBottom="0dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/divider" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/promptTextView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="@dimen/large_spacing"
|
||||||
|
android:layout_marginTop="@dimen/medium_spacing"
|
||||||
|
android:alpha="0.6"
|
||||||
|
android:text="@string/fragment_enter_public_key_prompt"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textColor="@color/text"
|
||||||
|
android:textSize="@dimen/very_small_font_size"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/publicKeyEditText" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/optionalContentContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/createPrivateChatButton"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/promptTextView">
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.components.LabeledSeparatorView
|
||||||
|
android:id="@+id/separatorView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_marginLeft="@dimen/large_spacing"
|
||||||
|
android:layout_marginTop="@dimen/large_spacing"
|
||||||
|
android:layout_marginRight="@dimen/large_spacing" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/qrCodeImageView"
|
||||||
|
android:layout_width="228dp"
|
||||||
|
android:layout_height="228dp"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_marginTop="@dimen/medium_spacing"
|
||||||
|
tools:src="@drawable/ic_qr_code_24" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/publicKeyTextView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="@dimen/large_spacing"
|
||||||
|
android:layout_marginTop="@dimen/large_spacing"
|
||||||
|
android:fontFamily="@font/space_mono_regular"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textColor="@color/text"
|
||||||
|
android:textSize="@dimen/medium_font_size"
|
||||||
|
tools:text="05987d601943c267879be41830888066c6a024cbdc9a548d06813924bf3372ea78" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="@dimen/large_spacing"
|
||||||
|
android:layout_marginVertical="@dimen/large_spacing"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/copyButton"
|
||||||
|
style="@style/Widget.Session.Button.Common.ProminentOutline"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="@dimen/medium_button_height"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/copy" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/shareButton"
|
||||||
|
style="@style/Widget.Session.Button.Common.ProminentOutline"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="@dimen/medium_button_height"
|
||||||
|
android:layout_marginStart="@dimen/medium_spacing"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/share" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
style="@style/Widget.Session.Button.Common.UnimportantFilled"
|
android:id="@+id/createPrivateChatButton"
|
||||||
android:id="@+id/copyButton"
|
style="@style/Widget.Session.Button.Common.ProminentOutline"
|
||||||
android:layout_width="0dp"
|
android:layout_width="196dp"
|
||||||
android:layout_height="@dimen/medium_button_height"
|
android:layout_height="@dimen/medium_button_height"
|
||||||
android:layout_weight="1"
|
android:layout_marginVertical="@dimen/medium_spacing"
|
||||||
android:text="@string/copy" />
|
android:text="@string/next"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<Button
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
style="@style/Widget.Session.Button.Common.UnimportantFilled"
|
|
||||||
android:id="@+id/shareButton"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="@dimen/medium_button_height"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_marginStart="@dimen/medium_spacing"
|
|
||||||
android:text="@string/share" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
</ScrollView>
|
||||||
|
|
||||||
</LinearLayout>
|
</org.thoughtcrime.securesms.components.NestedScrollableHost>
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/spacer"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
style="@style/Widget.Session.Button.Common.ProminentOutline"
|
|
||||||
android:id="@+id/createPrivateChatButton"
|
|
||||||
android:layout_width="196dp"
|
|
||||||
android:layout_height="@dimen/medium_button_height"
|
|
||||||
android:layout_marginBottom="@dimen/medium_spacing"
|
|
||||||
android:text="@string/next" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
87
app/src/main/res/layout/fragment_join_community.xml
Normal file
87
app/src/main/res/layout/fragment_join_community.xml
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<?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"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/bottom_sheet_background">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/titleText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/setting_button_height"
|
||||||
|
android:gravity="center_horizontal|center_vertical"
|
||||||
|
android:text="@string/dialog_join_community_title"
|
||||||
|
android:textColor="@color/text"
|
||||||
|
android:textSize="@dimen/very_large_font_size"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/backButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/medium_spacing"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:src="@drawable/ic_arrow_left_24"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/titleText"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/titleText"
|
||||||
|
android:contentDescription="@string/new_conversation_dialog_back_button_content_description" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/closeButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="@dimen/medium_spacing"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:src="@drawable/ic_baseline_close_24"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/titleText"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/titleText"
|
||||||
|
android:contentDescription="@string/new_conversation_dialog_close_button_content_description" />
|
||||||
|
|
||||||
|
<com.google.android.material.tabs.TabLayout
|
||||||
|
android:id="@+id/tabLayout"
|
||||||
|
style="@style/Widget.Session.TabLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/tab_bar_height"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/titleText" />
|
||||||
|
|
||||||
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
|
android:id="@+id/viewPager"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/tabLayout" />
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/loader"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:alpha="0"
|
||||||
|
android:background="#A4000000"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent">
|
||||||
|
|
||||||
|
<com.github.ybq.android.spinkit.SpinKitView
|
||||||
|
style="@style/SpinKitView.Large.ThreeBounce"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
app:SpinKit_Color="@android:color/white" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
5
app/src/main/res/layout/fragment_new_conversation.xml
Normal file
5
app/src/main/res/layout/fragment_new_conversation.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/new_conversation_fragment_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
158
app/src/main/res/layout/fragment_new_conversation_home.xml
Normal file
158
app/src/main/res/layout/fragment_new_conversation_home.xml
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<androidx.core.widget.NestedScrollView 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:background="@color/bottom_sheet_background">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/constraintLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingBottom="@dimen/medium_spacing">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/titleText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_horizontal|center_vertical"
|
||||||
|
android:paddingTop="@dimen/large_spacing"
|
||||||
|
android:paddingBottom="@dimen/very_large_spacing"
|
||||||
|
android:text="@string/dialog_new_conversation_title"
|
||||||
|
android:textColor="@color/text"
|
||||||
|
android:textSize="@dimen/very_large_font_size"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/closeButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/large_spacing"
|
||||||
|
android:clickable="true"
|
||||||
|
android:contentDescription="@string/new_conversation_dialog_close_button_content_description"
|
||||||
|
android:focusable="true"
|
||||||
|
android:src="@drawable/ic_baseline_close_24"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/title_divider"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/title_divider"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1px"
|
||||||
|
android:layout_marginBottom="1dp"
|
||||||
|
android:background="?android:dividerHorizontal"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/titleText"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/titleText" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/createPrivateChatButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/setting_button_height"
|
||||||
|
android:background="@color/bottom_sheet_item_background"
|
||||||
|
android:drawablePadding="@dimen/large_spacing"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingHorizontal="@dimen/large_spacing"
|
||||||
|
android:text="@string/dialog_new_message_title"
|
||||||
|
android:textColor="@color/text"
|
||||||
|
android:textSize="@dimen/medium_font_size"
|
||||||
|
app:drawableStartCompat="@drawable/ic_message"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/new_message_divider"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/title_divider" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/new_message_divider"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1px"
|
||||||
|
android:layout_marginStart="@dimen/massive_spacing"
|
||||||
|
android:background="?android:dividerHorizontal"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/createPrivateChatButton"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/createPrivateChatButton" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/createClosedGroupButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/setting_button_height"
|
||||||
|
android:background="@color/bottom_sheet_item_background"
|
||||||
|
android:drawablePadding="@dimen/large_spacing"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingHorizontal="@dimen/large_spacing"
|
||||||
|
android:text="@string/activity_create_group_title"
|
||||||
|
android:textColor="@color/text"
|
||||||
|
android:textSize="@dimen/medium_font_size"
|
||||||
|
app:drawableStartCompat="@drawable/ic_group"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/create_group_divider"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/new_message_divider" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/create_group_divider"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1px"
|
||||||
|
android:layout_marginStart="@dimen/massive_spacing"
|
||||||
|
android:background="?android:dividerHorizontal"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/createClosedGroupButton"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/createClosedGroupButton" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/joinCommunityButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/setting_button_height"
|
||||||
|
android:background="@color/bottom_sheet_item_background"
|
||||||
|
android:drawablePadding="@dimen/large_spacing"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingHorizontal="@dimen/large_spacing"
|
||||||
|
android:text="@string/dialog_join_community_title"
|
||||||
|
android:textColor="@color/text"
|
||||||
|
android:textSize="@dimen/medium_font_size"
|
||||||
|
app:drawableStartCompat="@drawable/ic_globe"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/create_group_divider" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/join_community_divider"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1px"
|
||||||
|
android:layout_marginBottom="1dp"
|
||||||
|
android:background="?android:dividerHorizontal"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/joinCommunityButton"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/joinCommunityButton" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/contactsText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingHorizontal="@dimen/large_spacing"
|
||||||
|
android:paddingTop="@dimen/medium_spacing"
|
||||||
|
android:paddingBottom="@dimen/small_spacing"
|
||||||
|
android:text="@string/new_conversation_contacts_title"
|
||||||
|
android:textColor="@color/text"
|
||||||
|
android:textSize="@dimen/medium_font_size"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/join_community_divider" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/contactsRecyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/contactsText"
|
||||||
|
tools:itemCount="6"
|
||||||
|
tools:listitem="@layout/view_contact" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
87
app/src/main/res/layout/fragment_new_message.xml
Normal file
87
app/src/main/res/layout/fragment_new_message.xml
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<?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"
|
||||||
|
android:id="@+id/rootLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/bottom_sheet_background">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/titleText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/setting_button_height"
|
||||||
|
android:gravity="center_horizontal|center_vertical"
|
||||||
|
android:text="@string/dialog_new_message_title"
|
||||||
|
android:textColor="@color/text"
|
||||||
|
android:textSize="@dimen/very_large_font_size"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/backButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/medium_spacing"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:src="@drawable/ic_arrow_left_24"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/titleText"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/titleText"
|
||||||
|
android:contentDescription="@string/new_conversation_dialog_back_button_content_description" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/closeButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="@dimen/medium_spacing"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:src="@drawable/ic_baseline_close_24"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/titleText"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/titleText"
|
||||||
|
android:contentDescription="@string/new_conversation_dialog_close_button_content_description" />
|
||||||
|
|
||||||
|
<com.google.android.material.tabs.TabLayout
|
||||||
|
android:id="@+id/tabLayout"
|
||||||
|
style="@style/Widget.Session.TabLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/tab_bar_height"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/titleText" />
|
||||||
|
|
||||||
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
|
android:id="@+id/viewPager"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/tabLayout" />
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/loader"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:alpha="0"
|
||||||
|
android:background="#A4000000"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/viewPager">
|
||||||
|
|
||||||
|
<com.github.ybq.android.spinkit.SpinKitView
|
||||||
|
style="@style/SpinKitView.Large.ThreeBounce"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
app:SpinKit_Color="@android:color/white" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<View xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_columnWeight="1"
|
|
||||||
android:layout_height="0dp"/>
|
|
43
app/src/main/res/layout/view_contact.xml
Normal file
43
app/src/main/res/layout/view_contact.xml
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/bottom_sheet_item_background"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/contentView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingHorizontal="@dimen/large_spacing"
|
||||||
|
android:paddingVertical="@dimen/small_spacing">
|
||||||
|
|
||||||
|
<include layout="@layout/view_profile_picture"
|
||||||
|
android:id="@+id/profilePictureView"
|
||||||
|
android:layout_width="@dimen/small_profile_picture_size"
|
||||||
|
android:layout_height="@dimen/small_profile_picture_size" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/nameTextView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/medium_spacing"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textAlignment="viewStart"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:textSize="@dimen/medium_font_size"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_weight="1"
|
||||||
|
tools:text="Spiderman" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1px"
|
||||||
|
android:background="?android:dividerHorizontal" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
@ -34,6 +34,9 @@
|
|||||||
<color name="conversation_pinned_background">#F0F0F0</color>
|
<color name="conversation_pinned_background">#F0F0F0</color>
|
||||||
<color name="conversation_pinned_icon">#606060</color>
|
<color name="conversation_pinned_icon">#606060</color>
|
||||||
|
|
||||||
|
<color name="bottom_sheet_background">#F9F9F9</color>
|
||||||
|
<color name="bottom_sheet_item_background">@color/white</color>
|
||||||
|
|
||||||
<color name="default_background_start">#ffffff</color>
|
<color name="default_background_start">#ffffff</color>
|
||||||
<color name="default_background_end">#fcfcfc</color>
|
<color name="default_background_end">#fcfcfc</color>
|
||||||
<color name="action_bar_background">#fcfcfc</color>
|
<color name="action_bar_background">#fcfcfc</color>
|
||||||
|
@ -46,6 +46,9 @@
|
|||||||
<color name="call_action_foreground_highlighted">#171717</color>
|
<color name="call_action_foreground_highlighted">#171717</color>
|
||||||
<color name="call_background">#171717</color>
|
<color name="call_background">#171717</color>
|
||||||
|
|
||||||
|
<color name="bottom_sheet_background">#1B1B1B</color>
|
||||||
|
<color name="bottom_sheet_item_background">#1B1B1B</color>
|
||||||
|
|
||||||
<array name="profile_picture_placeholder_colors">
|
<array name="profile_picture_placeholder_colors">
|
||||||
<item>#5ff8b0</item>
|
<item>#5ff8b0</item>
|
||||||
<item>#26cdb9</item>
|
<item>#26cdb9</item>
|
||||||
|
@ -961,4 +961,19 @@
|
|||||||
<item quantity="one">And %1$d other has reacted %2$s to this message</item>
|
<item quantity="one">And %1$d other has reacted %2$s to this message</item>
|
||||||
<item quantity="other">And %1$d others have reacted %2$s to this message</item>
|
<item quantity="other">And %1$d others have reacted %2$s to this message</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
|
||||||
|
<string name="dialog_new_conversation_title">New Conversation</string>
|
||||||
|
<string name="dialog_new_message_title">New Message</string>
|
||||||
|
<string name="activity_create_group_title">Create Group</string>
|
||||||
|
<string name="dialog_join_community_title">Join Community</string>
|
||||||
|
<string name="new_conversation_contacts_title">Contacts</string>
|
||||||
|
<string name="new_conversation_unknown_contacts_section_title">Unknown</string>
|
||||||
|
<string name="fragment_enter_public_key_prompt">Start a new conversation by entering someone\'s Session ID or share your Session ID with them.</string>
|
||||||
|
<string name="activity_create_group_create_button_title">Create</string>
|
||||||
|
<string name="search_contacts_hint">Search contacts</string>
|
||||||
|
<string name="activity_join_public_chat_enter_community_url_tab_title">Community URL</string>
|
||||||
|
<string name="fragment_enter_community_url_edit_text_hint">Enter Community URL</string>
|
||||||
|
<string name="fragment_enter_community_url_join_button_title">Join</string>
|
||||||
|
<string name="new_conversation_dialog_back_button_content_description">Navigate Back</string>
|
||||||
|
<string name="new_conversation_dialog_close_button_content_description">Close Dialog</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -50,7 +50,6 @@
|
|||||||
<style name="Widget.Session.TabLayout" parent="Widget.Design.TabLayout">
|
<style name="Widget.Session.TabLayout" parent="Widget.Design.TabLayout">
|
||||||
<item name="elevation">1dp</item>
|
<item name="elevation">1dp</item>
|
||||||
<item name="tabIndicatorColor">?colorAccent</item>
|
<item name="tabIndicatorColor">?colorAccent</item>
|
||||||
<item name="tabSelectedTextColor">?colorAccent</item>>
|
|
||||||
<item name="tabIndicatorHeight">@dimen/accent_line_thickness</item>
|
<item name="tabIndicatorHeight">@dimen/accent_line_thickness</item>
|
||||||
<item name="tabRippleColor">@color/cell_selected</item>
|
<item name="tabRippleColor">@color/cell_selected</item>
|
||||||
<item name="tabTextAppearance">@style/TextAppearance.Session.Tab</item>
|
<item name="tabTextAppearance">@style/TextAppearance.Session.Tab</item>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user