diff --git a/res/drawable/prominent_outline_button_medium_background.xml b/res/drawable/prominent_outline_button_medium_background.xml
index 183ed83282..f595e4f38b 100644
--- a/res/drawable/prominent_outline_button_medium_background.xml
+++ b/res/drawable/prominent_outline_button_medium_background.xml
@@ -7,5 +7,5 @@
-
+
\ No newline at end of file
diff --git a/res/layout-sw400dp/activity_pn_mode.xml b/res/layout-sw400dp/activity_pn_mode.xml
index 649ef31eae..66431151b8 100644
--- a/res/layout-sw400dp/activity_pn_mode.xml
+++ b/res/layout-sw400dp/activity_pn_mode.xml
@@ -1,6 +1,7 @@
@@ -30,7 +31,7 @@
android:textColor="@color/text"
android:text="There are two ways Session can notify you of new messages." />
-
-
+
-
-
+
+ android:layout_height="match_parent"
+ android:clipChildren="false">
+ android:layout_margin="@dimen/large_spacing"
+ android:clipChildren="false">
@@ -30,7 +31,7 @@
android:textColor="@color/text"
android:text="There are two ways Session can notify you of new messages." />
-
-
+
-
-
+
+
+
+
+
+ tools:visibility="visible" />
+ tools:visibility="visible" />
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/view_fake_chat.xml b/res/layout/view_fake_chat.xml
index 5680186dc2..654cbd86bf 100644
--- a/res/layout/view_fake_chat.xml
+++ b/res/layout/view_fake_chat.xml
@@ -5,6 +5,7 @@
android:layout_height="wrap_content"
android:paddingLeft="@dimen/very_large_spacing"
android:paddingRight="@dimen/very_large_spacing"
+ android:paddingBottom="@dimen/medium_spacing"
android:orientation="vertical">
#333132
- #0AFFFFFF
+ #0A000000
#99FFFFFF
\ No newline at end of file
diff --git a/res/values-notnight-v21/styles.xml b/res/values-notnight-v21/styles.xml
index 6bcd395e17..2cb9066705 100644
--- a/res/values-notnight-v21/styles.xml
+++ b/res/values-notnight-v21/styles.xml
@@ -11,4 +11,14 @@
- @android:color/white
+
+
+
+
\ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
index c9356d308b..a7ff67af2f 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -25,6 +25,7 @@
#141414
#99FFFFFF
#1F1F1F
+ #077C44
#1B1B1B
#212121
#FFCE3A
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 1c0282a999..083cdd33b8 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -29,7 +29,7 @@
8dp
224dp
10dp
- 234dp
+ 250dp
56dp
12dp
4dp
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1dbc88c0e7..9899172b5f 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1864,5 +1864,6 @@
Attachment
Voice Message
+ Details
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 478bdc22b4..10c311d33a 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -184,10 +184,12 @@
diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java
index 02a68f3e2c..40ca0e4391 100644
--- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java
@@ -160,6 +160,7 @@ import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol;
import org.thoughtcrime.securesms.loki.protocol.SessionManagementProtocol;
import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities;
import org.thoughtcrime.securesms.loki.views.MentionCandidateSelectionView;
+import org.thoughtcrime.securesms.loki.views.ProfilePictureView;
import org.thoughtcrime.securesms.loki.views.SessionRestoreBannerView;
import org.thoughtcrime.securesms.mediasend.Media;
import org.thoughtcrime.securesms.mediasend.MediaSendActivity;
@@ -234,6 +235,7 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
+import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
@@ -300,6 +302,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private AnimatingToggle buttonToggle;
private SendButton sendButton;
private ImageButton attachButton;
+ private ProfilePictureView profilePictureView;
private TextView titleTextView;
private TextView charactersLeft;
private ConversationFragment fragment;
@@ -527,6 +530,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
composeText.setTransport(sendButton.getSelectedTransport());
updateTitleTextView(recipient);
+ updateProfilePicture();
updateSubtitleTextView();
setActionBarColor(recipient.getColor());
updateInputUI(recipient, isSecureText, isDefaultSms);
@@ -621,6 +625,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
recipient = Recipient.from(this, data.getParcelableExtra(GroupCreateActivity.GROUP_ADDRESS_EXTRA), true);
recipient.addListener(this);
updateTitleTextView(recipient);
+ updateProfilePicture();
updateSubtitleTextView();
NotificationChannels.updateContactChannelName(this, recipient);
updateInputUI(recipient, isSecureText, isDefaultSms);
@@ -1654,6 +1659,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void initializeViews() {
+ profilePictureView = findViewById(R.id.profilePictureView);
titleTextView = findViewById(R.id.titleTextView);
buttonToggle = ViewUtil.findById(this, R.id.button_toggle);
sendButton = ViewUtil.findById(this, R.id.send_button);
@@ -1872,6 +1878,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
Util.runOnMain(() -> {
Log.i(TAG, "onModifiedRun(): " + recipient.getRegistered());
updateTitleTextView(recipient);
+ updateProfilePicture();
updateSubtitleTextView();
// titleView.setVerified(identityRecords.isVerified());
updateInputUI(recipient, isSecureText, isDefaultSms);
@@ -3100,6 +3107,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
}
+ private void updateProfilePicture() {
+ profilePictureView.glide = GlideApp.with(this);
+ profilePictureView.update(recipient, threadId);
+ }
+
private void updateSubtitleTextView() {
muteIndicatorImageView.setVisibility(View.GONE);
subtitleTextView.setVisibility(View.VISIBLE);
diff --git a/src/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt
index 8731e3dd9a..2764601dcf 100644
--- a/src/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt
+++ b/src/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt
@@ -34,6 +34,7 @@ import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob
import org.thoughtcrime.securesms.loki.dialogs.ConversationOptionsBottomSheet
import org.thoughtcrime.securesms.loki.dialogs.LightThemeFeatureIntroBottomSheet
import org.thoughtcrime.securesms.loki.dialogs.MultiDeviceRemovalBottomSheet
+import org.thoughtcrime.securesms.loki.dialogs.UserDetailsBottomSheet
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol
import org.thoughtcrime.securesms.loki.protocol.SessionResetImplementation
import org.thoughtcrime.securesms.loki.utilities.*
@@ -100,6 +101,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
profileButton.publicKey = publicKey
profileButton.update()
profileButton.setOnClickListener { openSettings() }
+ pathStatusViewContainer.disableClipping()
pathStatusViewContainer.setOnClickListener { showPath() }
// Set up seed reminder view
val isMasterDevice = (TextSecurePreferences.getMasterHexEncodedPublicKey(this) == null)
@@ -271,6 +273,14 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
val thread = view.thread ?: return
val bottomSheet = ConversationOptionsBottomSheet()
bottomSheet.recipient = thread.recipient
+ bottomSheet.onViewDetailsTapped = {
+ bottomSheet.dismiss()
+ val userDetailsBottomSheet = UserDetailsBottomSheet()
+ val bundle = Bundle()
+ bundle.putString("publicKey", thread.recipient.address.toPhoneString())
+ userDetailsBottomSheet.arguments = bundle
+ userDetailsBottomSheet.show(supportFragmentManager, userDetailsBottomSheet.tag)
+ }
bottomSheet.onBlockTapped = {
bottomSheet.dismiss()
if (!thread.recipient.isBlocked) {
diff --git a/src/org/thoughtcrime/securesms/loki/activities/PNModeActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/PNModeActivity.kt
index c36c92b5c7..75102938b9 100644
--- a/src/org/thoughtcrime/securesms/loki/activities/PNModeActivity.kt
+++ b/src/org/thoughtcrime/securesms/loki/activities/PNModeActivity.kt
@@ -1,5 +1,7 @@
package org.thoughtcrime.securesms.loki.activities
+import android.animation.ArgbEvaluator
+import android.animation.ValueAnimator
import android.app.AlertDialog
import android.content.Intent
import android.graphics.drawable.TransitionDrawable
@@ -9,27 +11,36 @@ import androidx.annotation.DrawableRes
import android.view.Menu
import android.view.MenuItem
import android.view.View
-import android.widget.LinearLayout
import android.widget.Toast
+import androidx.annotation.ColorRes
import kotlinx.android.synthetic.main.activity_display_name.registerButton
import kotlinx.android.synthetic.main.activity_pn_mode.*
import network.loki.messenger.R
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.BaseActionBarActivity
+import org.thoughtcrime.securesms.loki.utilities.disableClipping
+import org.thoughtcrime.securesms.loki.utilities.getColorWithID
import org.thoughtcrime.securesms.loki.utilities.setUpActionBarSessionLogo
import org.thoughtcrime.securesms.loki.utilities.show
+import org.thoughtcrime.securesms.loki.views.GlowViewUtilities
+import org.thoughtcrime.securesms.loki.views.PNModeView
import org.thoughtcrime.securesms.util.TextSecurePreferences
class PNModeActivity : BaseActionBarActivity() {
- private var selectedOptionView: LinearLayout? = null
+ private var selectedOptionView: PNModeView? = null
// region Lifecycle
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setUpActionBarSessionLogo()
setContentView(R.layout.activity_pn_mode)
+ contentView.disableClipping()
fcmOptionView.setOnClickListener { toggleFCM() }
+ fcmOptionView.mainColor = resources.getColorWithID(R.color.pn_option_background, theme)
+ fcmOptionView.strokeColor = resources.getColorWithID(R.color.pn_option_border, theme)
backgroundPollingOptionView.setOnClickListener { toggleBackgroundPolling() }
+ backgroundPollingOptionView.mainColor = resources.getColorWithID(R.color.pn_option_background, theme)
+ backgroundPollingOptionView.strokeColor = resources.getColorWithID(R.color.pn_option_border, theme)
registerButton.setOnClickListener { register() }
}
@@ -71,15 +82,23 @@ class PNModeActivity : BaseActionBarActivity() {
when (selectedOptionView) {
null -> {
performTransition(R.drawable.pn_option_background_select_transition, fcmOptionView)
+ GlowViewUtilities.animateShadowColorChange(this, fcmOptionView, R.color.transparent, R.color.accent)
+ animateStrokeColorChange(fcmOptionView, R.color.pn_option_border, R.color.accent)
selectedOptionView = fcmOptionView
}
fcmOptionView -> {
performTransition(R.drawable.pn_option_background_deselect_transition, fcmOptionView)
+ GlowViewUtilities.animateShadowColorChange(this, fcmOptionView, R.color.accent, R.color.transparent)
+ animateStrokeColorChange(fcmOptionView, R.color.accent, R.color.pn_option_border)
selectedOptionView = null
}
backgroundPollingOptionView -> {
performTransition(R.drawable.pn_option_background_select_transition, fcmOptionView)
+ GlowViewUtilities.animateShadowColorChange(this, fcmOptionView, R.color.transparent, R.color.accent)
+ animateStrokeColorChange(fcmOptionView, R.color.pn_option_border, R.color.accent)
performTransition(R.drawable.pn_option_background_deselect_transition, backgroundPollingOptionView)
+ GlowViewUtilities.animateShadowColorChange(this, backgroundPollingOptionView, R.color.accent, R.color.transparent)
+ animateStrokeColorChange(backgroundPollingOptionView, R.color.accent, R.color.pn_option_border)
selectedOptionView = fcmOptionView
}
}
@@ -89,20 +108,40 @@ class PNModeActivity : BaseActionBarActivity() {
when (selectedOptionView) {
null -> {
performTransition(R.drawable.pn_option_background_select_transition, backgroundPollingOptionView)
+ GlowViewUtilities.animateShadowColorChange(this, backgroundPollingOptionView, R.color.transparent, R.color.accent)
+ animateStrokeColorChange(backgroundPollingOptionView, R.color.pn_option_border, R.color.accent)
selectedOptionView = backgroundPollingOptionView
}
backgroundPollingOptionView -> {
performTransition(R.drawable.pn_option_background_deselect_transition, backgroundPollingOptionView)
+ GlowViewUtilities.animateShadowColorChange(this, backgroundPollingOptionView, R.color.accent, R.color.transparent)
+ animateStrokeColorChange(backgroundPollingOptionView, R.color.accent, R.color.pn_option_border)
selectedOptionView = null
}
fcmOptionView -> {
performTransition(R.drawable.pn_option_background_select_transition, backgroundPollingOptionView)
+ GlowViewUtilities.animateShadowColorChange(this, backgroundPollingOptionView, R.color.transparent, R.color.accent)
+ animateStrokeColorChange(backgroundPollingOptionView, R.color.pn_option_border, R.color.accent)
performTransition(R.drawable.pn_option_background_deselect_transition, fcmOptionView)
+ GlowViewUtilities.animateShadowColorChange(this, fcmOptionView, R.color.accent, R.color.transparent)
+ animateStrokeColorChange(fcmOptionView, R.color.accent, R.color.pn_option_border)
selectedOptionView = backgroundPollingOptionView
}
}
}
+ private fun animateStrokeColorChange(bubble: PNModeView, @ColorRes startColorID: Int, @ColorRes endColorID: Int) {
+ val startColor = resources.getColorWithID(startColorID, theme)
+ val endColor = resources.getColorWithID(endColorID, theme)
+ val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor)
+ animation.duration = 250
+ animation.addUpdateListener { animator ->
+ val color = animator.animatedValue as Int
+ bubble.strokeColor = color
+ }
+ animation.start()
+ }
+
private fun register() {
if (selectedOptionView == null) {
val dialog = AlertDialog.Builder(this)
@@ -111,7 +150,6 @@ class PNModeActivity : BaseActionBarActivity() {
dialog.create().show()
return
}
- val displayName = TextSecurePreferences.getProfileName(this)
TextSecurePreferences.setHasSeenWelcomeScreen(this, true)
TextSecurePreferences.setPromptedPushRegistration(this, true)
TextSecurePreferences.setIsUsingFCM(this, (selectedOptionView == fcmOptionView))
diff --git a/src/org/thoughtcrime/securesms/loki/activities/PathActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/PathActivity.kt
index 7e3b641b12..fa96d6b2a1 100644
--- a/src/org/thoughtcrime/securesms/loki/activities/PathActivity.kt
+++ b/src/org/thoughtcrime/securesms/loki/activities/PathActivity.kt
@@ -16,10 +16,13 @@ import android.widget.LinearLayout
import android.widget.RelativeLayout
import android.widget.TextView
import android.widget.Toast
+import androidx.annotation.ColorRes
import kotlinx.android.synthetic.main.activity_path.*
import network.loki.messenger.R
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.loki.utilities.*
+import org.thoughtcrime.securesms.loki.views.GlowViewUtilities
+import org.thoughtcrime.securesms.loki.views.PathDotView
import org.whispersystems.signalservice.loki.api.Snode
import org.whispersystems.signalservice.loki.api.onionrequests.OnionRequestAPI
@@ -31,6 +34,7 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
super.onCreate(savedInstanceState, isReady)
setContentView(R.layout.activity_path)
supportActionBar!!.title = resources.getString(R.string.activity_path_title)
+ pathRowsContainer.disableClipping()
learnMoreButton.setOnClickListener { learnMore() }
update(false)
registerObservers()
@@ -111,6 +115,7 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
val mainContainer = LinearLayout(this)
mainContainer.orientation = LinearLayout.HORIZONTAL
mainContainer.gravity = Gravity.CENTER_VERTICAL
+ mainContainer.disableClipping()
val mainContainerLayoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)
mainContainer.layoutParams = mainContainerLayoutParams
val lineView = LineView(this, location, dotAnimationStartDelay, dotAnimationRepeatInterval)
@@ -170,8 +175,9 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
private var dotAnimationRepeatInterval: Long = 0
private val dotView by lazy {
- val result = View(context)
+ val result = PathDotView(context)
result.setBackgroundResource(R.drawable.accent_dot)
+ result.mainColor = resources.getColorWithID(R.color.accent, context.theme)
result
}
@@ -203,6 +209,7 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
}
private fun setUpViewHierarchy() {
+ disableClipping()
val lineView = View(context)
lineView.setBackgroundColor(resources.getColorWithID(R.color.text, context.theme))
val lineViewHeight = when (location) {
@@ -239,10 +246,14 @@ class PathActivity : PassphraseRequiredActionBarActivity() {
private fun expand() {
dotView.animateSizeChange(R.dimen.path_row_dot_size, R.dimen.path_row_expanded_dot_size)
+ @ColorRes val startColorID = if (UiModeUtilities.isDayUiMode(context)) R.color.transparent_black_30 else R.color.black
+ GlowViewUtilities.animateShadowColorChange(context, dotView, startColorID, R.color.accent)
}
private fun collapse() {
dotView.animateSizeChange(R.dimen.path_row_expanded_dot_size, R.dimen.path_row_dot_size)
+ @ColorRes val endColorID = if (UiModeUtilities.isDayUiMode(context)) R.color.transparent_black_30 else R.color.black
+ GlowViewUtilities.animateShadowColorChange(context, dotView, R.color.accent, endColorID)
}
}
// endregion
diff --git a/src/org/thoughtcrime/securesms/loki/dialogs/ConversationOptionsBottomSheet.kt b/src/org/thoughtcrime/securesms/loki/dialogs/ConversationOptionsBottomSheet.kt
index 828a67fa87..f803e0fced 100644
--- a/src/org/thoughtcrime/securesms/loki/dialogs/ConversationOptionsBottomSheet.kt
+++ b/src/org/thoughtcrime/securesms/loki/dialogs/ConversationOptionsBottomSheet.kt
@@ -20,6 +20,7 @@ public class ConversationOptionsBottomSheet : BottomSheetDialogFragment() {
// if we want to use dialog fragments properly.
lateinit var recipient: Recipient
+ var onViewDetailsTapped: (() -> Unit?)? = null
var onBlockTapped: (() -> Unit)? = null
var onUnblockTapped: (() -> Unit)? = null
var onDeleteTapped: (() -> Unit)? = null
@@ -30,18 +31,16 @@ public class ConversationOptionsBottomSheet : BottomSheetDialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
-
- if (!this::recipient.isInitialized) {
- dismiss()
- return
- }
-
+ if (!this::recipient.isInitialized) { return dismiss() }
if (!recipient.isGroupRecipient && !recipient.isLocalNumber) {
+ detailsTextView.visibility = View.VISIBLE
unblockTextView.visibility = if (recipient.isBlocked) View.VISIBLE else View.GONE
blockTextView.visibility = if (recipient.isBlocked) View.GONE else View.VISIBLE
-
+ detailsTextView.setOnClickListener { onViewDetailsTapped?.invoke() }
blockTextView.setOnClickListener { onBlockTapped?.invoke() }
unblockTextView.setOnClickListener { onUnblockTapped?.invoke() }
+ } else {
+ detailsTextView.visibility = View.GONE
}
deleteTextView.setOnClickListener { onDeleteTapped?.invoke() }
}
diff --git a/src/org/thoughtcrime/securesms/loki/dialogs/UserDetailsBottomSheet.kt b/src/org/thoughtcrime/securesms/loki/dialogs/UserDetailsBottomSheet.kt
new file mode 100644
index 0000000000..3d884f7440
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/loki/dialogs/UserDetailsBottomSheet.kt
@@ -0,0 +1,40 @@
+package org.thoughtcrime.securesms.loki.dialogs
+
+import android.content.ClipData
+import android.content.ClipboardManager
+import android.content.Context
+import android.os.Bundle
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import kotlinx.android.synthetic.main.fragment_user_details_bottom_sheet.*
+import kotlinx.android.synthetic.main.view_conversation.view.*
+import network.loki.messenger.R
+import org.thoughtcrime.securesms.database.DatabaseFactory
+import org.thoughtcrime.securesms.mms.GlideApp
+
+public class UserDetailsBottomSheet : BottomSheetDialogFragment() {
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ return inflater.inflate(R.layout.fragment_user_details_bottom_sheet, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ val publicKey = arguments?.getString("publicKey") ?: return dismiss()
+ profilePictureView.publicKey = publicKey
+ profilePictureView.glide = GlideApp.with(this)
+ profilePictureView.isLarge = true
+ profilePictureView.update()
+ nameTextView.text = DatabaseFactory.getLokiUserDatabase(requireContext()).getDisplayName(publicKey) ?: "Anonymous"
+ publicKeyTextView.text = publicKey
+ copyButton.setOnClickListener {
+ val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
+ val clip = ClipData.newPlainText("Session ID", publicKey)
+ clipboard.setPrimaryClip(clip)
+ Toast.makeText(requireContext(), R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/views/ConversationView.kt b/src/org/thoughtcrime/securesms/loki/views/ConversationView.kt
index 382ac63a84..ebc6c679be 100644
--- a/src/org/thoughtcrime/securesms/loki/views/ConversationView.kt
+++ b/src/org/thoughtcrime/securesms/loki/views/ConversationView.kt
@@ -55,30 +55,8 @@ class ConversationView : LinearLayout {
accentView.setBackgroundResource(R.color.accent)
accentView.visibility = if (thread.unreadCount > 0) View.VISIBLE else View.INVISIBLE
}
- if (thread.recipient.isGroupRecipient) {
- if ("Session Public Chat" == thread.recipient.name) {
- profilePictureView.publicKey = ""
- profilePictureView.additionalPublicKey = null
- profilePictureView.isRSSFeed = true
- } else {
- val users = MentionsManager.shared.userPublicKeyCache[thread.threadId]?.toMutableList() ?: mutableListOf()
- users.remove(TextSecurePreferences.getLocalNumber(context))
- val masterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
- if (masterPublicKey != null) {
- users.remove(masterPublicKey)
- }
- val randomUsers = users.sorted() // Sort to provide a level of stability
- profilePictureView.publicKey = randomUsers.getOrNull(0) ?: ""
- profilePictureView.additionalPublicKey = randomUsers.getOrNull(1) ?: ""
- profilePictureView.isRSSFeed = thread.recipient.name == "Loki News" || thread.recipient.name == "Session Updates"
- }
- } else {
- profilePictureView.publicKey = thread.recipient.address.toString()
- profilePictureView.additionalPublicKey = null
- profilePictureView.isRSSFeed = false
- }
profilePictureView.glide = glide
- profilePictureView.update()
+ profilePictureView.update(thread.recipient, thread.threadId)
val senderDisplayName = if (thread.recipient.isLocalNumber) context.getString(R.string.note_to_self) else if (!thread.recipient.name.isNullOrEmpty()) thread.recipient.name else thread.recipient.address.toString()
btnGroupNameDisplay.text = senderDisplayName
timestampTextView.text = DateUtils.getBriefRelativeTimeSpanString(context, Locale.getDefault(), thread.date)
diff --git a/src/org/thoughtcrime/securesms/loki/views/FakeChatView.kt b/src/org/thoughtcrime/securesms/loki/views/FakeChatView.kt
index 60b0585715..017f55d353 100644
--- a/src/org/thoughtcrime/securesms/loki/views/FakeChatView.kt
+++ b/src/org/thoughtcrime/securesms/loki/views/FakeChatView.kt
@@ -1,14 +1,18 @@
package org.thoughtcrime.securesms.loki.views
+import android.animation.FloatEvaluator
+import android.animation.ValueAnimator
import android.content.Context
import android.content.Context.LAYOUT_INFLATER_SERVICE
import android.os.Handler
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
+import android.widget.LinearLayout
import android.widget.ScrollView
import kotlinx.android.synthetic.main.view_fake_chat.view.*
import network.loki.messenger.R
+import org.thoughtcrime.securesms.loki.utilities.disableClipping
class FakeChatView : ScrollView {
@@ -38,7 +42,8 @@ class FakeChatView : ScrollView {
private fun setUpViewHierarchy() {
val inflater = context.getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater
- val contentView = inflater.inflate(R.layout.view_fake_chat, null)
+ val contentView = inflater.inflate(R.layout.view_fake_chat, null) as LinearLayout
+ contentView.disableClipping()
addView(contentView)
isVerticalScrollBarEnabled = false
}
@@ -47,8 +52,13 @@ class FakeChatView : ScrollView {
// region Animation
fun startAnimating() {
listOf( bubble1, bubble2, bubble3, bubble4, bubble5 ).forEach { it.alpha = 0.0f }
- fun show(view: View) {
- view.animate().alpha(1.0f).setDuration(animationDuration).start()
+ fun show(bubble: View) {
+ val animation = ValueAnimator.ofObject(FloatEvaluator(), 0.0f, 1.0f)
+ animation.duration = animationDuration
+ animation.addUpdateListener { animator ->
+ bubble.alpha = animator.animatedValue as Float
+ }
+ animation.start()
}
Handler().postDelayed({
show(bubble1)
diff --git a/src/org/thoughtcrime/securesms/loki/views/GlowView.kt b/src/org/thoughtcrime/securesms/loki/views/GlowView.kt
new file mode 100644
index 0000000000..e48a67fbc9
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/loki/views/GlowView.kt
@@ -0,0 +1,158 @@
+package org.thoughtcrime.securesms.loki.views
+
+import android.animation.ArgbEvaluator
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.*
+import android.util.AttributeSet
+import android.view.View
+import android.view.ViewOutlineProvider
+import android.widget.LinearLayout
+import androidx.annotation.ColorInt
+import androidx.annotation.ColorRes
+import network.loki.messenger.R
+import org.thoughtcrime.securesms.loki.utilities.getColorWithID
+import org.thoughtcrime.securesms.loki.utilities.toPx
+
+interface GlowView {
+ var mainColor: Int
+ var sessionShadowColor: Int
+}
+
+object GlowViewUtilities {
+
+ fun animateColorChange(context: Context, view: GlowView, @ColorRes startColorID: Int, @ColorRes endColorID: Int) {
+ val startColor = context.resources.getColorWithID(startColorID, context.theme)
+ val endColor = context.resources.getColorWithID(endColorID, context.theme)
+ val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor)
+ animation.duration = 250
+ animation.addUpdateListener { animator ->
+ val color = animator.animatedValue as Int
+ view.mainColor = color
+ }
+ animation.start()
+ }
+
+ fun animateShadowColorChange(context: Context, view: GlowView, @ColorRes startColorID: Int, @ColorRes endColorID: Int) {
+ val startColor = context.resources.getColorWithID(startColorID, context.theme)
+ val endColor = context.resources.getColorWithID(endColorID, context.theme)
+ val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor)
+ animation.duration = 250
+ animation.addUpdateListener { animator ->
+ val color = animator.animatedValue as Int
+ view.sessionShadowColor = color
+ }
+ animation.start()
+ }
+}
+
+class PNModeView : LinearLayout, GlowView {
+ @ColorInt override var mainColor: Int = 0
+ set(newValue) { field = newValue; paint.color = newValue }
+ @ColorInt var strokeColor: Int = 0
+ set(newValue) { field = newValue; strokePaint.color = newValue }
+ @ColorInt override var sessionShadowColor: Int = 0
+ set(newValue) { field = newValue; paint.setShadowLayer(toPx(4, resources).toFloat(), 0.0f, 0.0f, newValue) }
+
+ private val paint: Paint by lazy {
+ val result = Paint()
+ result.style = Paint.Style.FILL
+ result.isAntiAlias = true
+ result
+ }
+
+ private val strokePaint: Paint by lazy {
+ val result = Paint()
+ result.style = Paint.Style.STROKE
+ result.isAntiAlias = true
+ result.strokeWidth = toPx(1, resources).toFloat()
+ result
+ }
+
+ // region Lifecycle
+ constructor(context: Context) : super(context) { }
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { }
+ constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { }
+
+ init {
+ setWillNotDraw(false)
+ }
+ // endregion
+
+ // region Updating
+ override fun onDraw(c: Canvas) {
+ val w = width.toFloat()
+ val h = height.toFloat()
+ val r = resources.getDimension(R.dimen.pn_option_corner_radius)
+ c.drawRoundRect(0.0f, 0.0f, w, h, r, r, paint)
+ c.drawRoundRect(0.0f, 0.0f, w, h, r, r, strokePaint)
+ super.onDraw(c)
+ }
+ // endregion
+}
+
+class NewConversationButtonImageView : androidx.appcompat.widget.AppCompatImageView, GlowView {
+ @ColorInt override var mainColor: Int = 0
+ set(newValue) { field = newValue; paint.color = newValue }
+ @ColorInt override var sessionShadowColor: Int = 0
+ set(newValue) { field = newValue; paint.setShadowLayer(toPx(6, resources).toFloat(), 0.0f, 0.0f, newValue) }
+
+ private val paint: Paint by lazy {
+ val result = Paint()
+ result.style = Paint.Style.FILL
+ result.isAntiAlias = true
+ result
+ }
+
+ // region Lifecycle
+ constructor(context: Context) : super(context) { }
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { }
+ constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { }
+
+ init {
+ setWillNotDraw(false)
+ }
+ // endregion
+
+ // region Updating
+ override fun onDraw(c: Canvas) {
+ val w = width.toFloat()
+ val h = height.toFloat()
+ c.drawCircle(w / 2, h / 2, w / 2, paint)
+ super.onDraw(c)
+ }
+ // endregion
+}
+
+class PathDotView : View, GlowView {
+ @ColorInt override var mainColor: Int = 0
+ set(newValue) { field = newValue; paint.color = newValue }
+ @ColorInt override var sessionShadowColor: Int = 0
+ set(newValue) { field = newValue; paint.setShadowLayer(toPx(4, resources).toFloat(), 0.0f, 0.0f, newValue) }
+
+ private val paint: Paint by lazy {
+ val result = Paint()
+ result.style = Paint.Style.FILL
+ result.isAntiAlias = true
+ result
+ }
+
+ // region Lifecycle
+ constructor(context: Context) : super(context) { }
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { }
+ constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { }
+
+ init {
+ setWillNotDraw(false)
+ }
+ // endregion
+
+ // region Updating
+ override fun onDraw(c: Canvas) {
+ val w = width.toFloat()
+ val h = height.toFloat()
+ c.drawCircle(w / 2, h / 2, w / 2, paint)
+ super.onDraw(c)
+ }
+ // endregion
+}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/views/LabeledSeparatorView.kt b/src/org/thoughtcrime/securesms/loki/views/LabeledSeparatorView.kt
index 028dd34a49..4cbc83dc7b 100644
--- a/src/org/thoughtcrime/securesms/loki/views/LabeledSeparatorView.kt
+++ b/src/org/thoughtcrime/securesms/loki/views/LabeledSeparatorView.kt
@@ -16,7 +16,7 @@ class LabeledSeparatorView : RelativeLayout {
private val path = Path()
- private val paint: Paint by lazy{
+ private val paint: Paint by lazy {
val result = Paint()
result.style = Paint.Style.STROKE
result.color = ThemeUtil.getThemedColor(context, R.attr.dividerHorizontal)
diff --git a/src/org/thoughtcrime/securesms/loki/views/NewConversationButtonSetView.kt b/src/org/thoughtcrime/securesms/loki/views/NewConversationButtonSetView.kt
index e46f82e376..793ae44dea 100644
--- a/src/org/thoughtcrime/securesms/loki/views/NewConversationButtonSetView.kt
+++ b/src/org/thoughtcrime/securesms/loki/views/NewConversationButtonSetView.kt
@@ -7,9 +7,7 @@ import android.animation.ValueAnimator
import android.content.Context
import android.content.Context.VIBRATOR_SERVICE
import android.content.res.ColorStateList
-import android.graphics.Color
import android.graphics.PointF
-import android.graphics.drawable.GradientDrawable
import android.os.Build
import android.os.VibrationEffect
import android.os.VibrationEffect.DEFAULT_AMPLITUDE
@@ -66,24 +64,25 @@ class NewConversationButtonSetView : RelativeLayout {
private val collapsedImageViewPosition by lazy { PointF((expandedSize - collapsedSize) / 2, (expandedSize - collapsedSize) / 2) }
private val imageView by lazy {
- val result = ImageView(context)
+ val result = NewConversationButtonImageView(context)
val size = collapsedSize.toInt()
result.layoutParams = LayoutParams(size, size)
result.setBackgroundResource(R.drawable.new_conversation_button_background)
- val background = result.background as GradientDrawable
- @ColorRes val backgroundColorID = if (isMain)
- R.color.accent else
- R.color.new_conversation_button_collapsed_background
- background.color = ColorStateList.valueOf(resources.getColorWithID(backgroundColorID, context.theme))
+ @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)
- // Always use white icon for the main button.
+ result.imageTintList = if (isMain) {
ColorStateList.valueOf(resources.getColorWithID(android.R.color.white, context.theme))
- else
+ } else {
ColorStateList.valueOf(resources.getColorWithID(R.color.text, context.theme))
-
+ }
result
}
@@ -106,30 +105,21 @@ class NewConversationButtonSetView : RelativeLayout {
}
fun expand() {
- animateImageViewColorChange(R.color.new_conversation_button_collapsed_background, R.color.accent)
+ 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() {
- animateImageViewColorChange(R.color.accent, R.color.new_conversation_button_collapsed_background)
+ 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 animateImageViewColorChange(@ColorRes startColorID: Int, @ColorRes endColorID: Int) {
- val drawable = imageView.background as GradientDrawable
- val startColor = resources.getColorWithID(startColorID, context.theme)
- val endColor = resources.getColorWithID(endColorID, context.theme)
- val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor)
- animation.duration = animationDuration
- animation.addUpdateListener { animator ->
- val color = animator.animatedValue as Int
- drawable.color = ColorStateList.valueOf(color)
- }
- animation.start()
- }
-
private fun animateImageViewPositionChange(startPosition: PointF, endPosition: PointF) {
val animation = ValueAnimator.ofObject(PointFEvaluator(), startPosition, endPosition)
animation.duration = animationDuration
@@ -170,6 +160,7 @@ class NewConversationButtonSetView : RelativeLayout {
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) { setUpViewHierarchy() }
private fun setUpViewHierarchy() {
+ disableClipping()
// Set up session button
addView(sessionButton)
sessionButton.alpha = 0.0f
diff --git a/src/org/thoughtcrime/securesms/loki/views/PathStatusView.kt b/src/org/thoughtcrime/securesms/loki/views/PathStatusView.kt
index 3fd4158b89..e65630c144 100644
--- a/src/org/thoughtcrime/securesms/loki/views/PathStatusView.kt
+++ b/src/org/thoughtcrime/securesms/loki/views/PathStatusView.kt
@@ -4,14 +4,30 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.graphics.Canvas
+import android.graphics.Paint
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import android.util.AttributeSet
import android.view.View
+import androidx.annotation.ColorInt
import network.loki.messenger.R
+import org.thoughtcrime.securesms.loki.utilities.getColorWithID
+import org.thoughtcrime.securesms.loki.utilities.toPx
import org.whispersystems.signalservice.loki.api.onionrequests.OnionRequestAPI
class PathStatusView : View {
private val broadcastReceivers = mutableListOf()
+ @ColorInt var mainColor: Int = 0
+ set(newValue) { field = newValue; paint.color = newValue }
+ @ColorInt var sessionShadowColor: Int = 0
+ set(newValue) { field = newValue; paint.setShadowLayer(toPx(8, resources).toFloat(), 0.0f, 0.0f, newValue) }
+
+ private val paint: Paint by lazy {
+ val result = Paint()
+ result.style = Paint.Style.FILL
+ result.isAntiAlias = true
+ result
+ }
constructor(context: Context) : super(context) {
initialize()
@@ -31,6 +47,7 @@ class PathStatusView : View {
private fun initialize() {
update()
+ setWillNotDraw(false)
}
override fun onAttachedToWindow() {
@@ -70,8 +87,19 @@ class PathStatusView : View {
private fun update() {
if (OnionRequestAPI.paths.count() >= OnionRequestAPI.pathCount) {
setBackgroundResource(R.drawable.accent_dot)
+ mainColor = resources.getColorWithID(R.color.accent, context.theme)
+ sessionShadowColor = resources.getColorWithID(R.color.accent, context.theme)
} else {
setBackgroundResource(R.drawable.paths_building_dot)
+ mainColor = resources.getColorWithID(R.color.paths_building, context.theme)
+ sessionShadowColor = resources.getColorWithID(R.color.paths_building, context.theme)
}
}
+
+ override fun onDraw(c: Canvas) {
+ val w = width.toFloat()
+ val h = height.toFloat()
+ c.drawCircle(w / 2, h / 2, w / 2, paint)
+ super.onDraw(c)
+ }
}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt b/src/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt
index 362f5ff35d..de93c4b81d 100644
--- a/src/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt
+++ b/src/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt
@@ -8,6 +8,7 @@ import android.view.View
import android.widget.ImageView
import android.widget.RelativeLayout
import com.bumptech.glide.load.engine.DiskCacheStrategy
+import kotlinx.android.synthetic.main.view_conversation.view.*
import kotlinx.android.synthetic.main.view_profile_picture.view.*
import network.loki.messenger.R
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto
@@ -16,6 +17,7 @@ import org.thoughtcrime.securesms.loki.todo.JazzIdenticonDrawable
import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.TextSecurePreferences
+import org.whispersystems.signalservice.loki.protocol.mentions.MentionsManager
// TODO: Look into a better way of handling different sizes. Maybe an enum (with associated values) encapsulating the different modes?
@@ -51,6 +53,32 @@ class ProfilePictureView : RelativeLayout {
// endregion
// region Updating
+ fun update(recipient: Recipient, threadID: Long) {
+ if (recipient.isGroupRecipient) {
+ if ("Session Public Chat" == recipient.name) {
+ publicKey = ""
+ additionalPublicKey = null
+ isRSSFeed = true
+ } else {
+ val users = MentionsManager.shared.userPublicKeyCache[threadID]?.toMutableList() ?: mutableListOf()
+ users.remove(TextSecurePreferences.getLocalNumber(context))
+ val masterPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
+ if (masterPublicKey != null) {
+ users.remove(masterPublicKey)
+ }
+ val randomUsers = users.sorted() // Sort to provide a level of stability
+ publicKey = randomUsers.getOrNull(0) ?: ""
+ additionalPublicKey = randomUsers.getOrNull(1) ?: ""
+ isRSSFeed = recipient.name == "Loki News" || recipient.name == "Session Updates"
+ }
+ } else {
+ publicKey = recipient.address.toString()
+ additionalPublicKey = null
+ isRSSFeed = false
+ }
+ update()
+ }
+
fun update() {
val publicKey = publicKey ?: return
val additionalPublicKey = additionalPublicKey