mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-22 07:57:30 +00:00
Hook up home screen logic
This commit is contained in:
parent
95dc4e6590
commit
ded709a58b
@ -27,6 +27,7 @@ apply plugin: 'kotlin-android-extensions'
|
|||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'witness'
|
apply plugin: 'witness'
|
||||||
apply plugin: 'io.fabric'
|
apply plugin: 'io.fabric'
|
||||||
|
apply plugin: 'kotlin-kapt'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
@ -112,6 +113,7 @@ dependencies {
|
|||||||
implementation 'com.github.chrisbanes:PhotoView:2.1.3'
|
implementation 'com.github.chrisbanes:PhotoView:2.1.3'
|
||||||
implementation 'com.github.bumptech.glide:glide:4.5.0'
|
implementation 'com.github.bumptech.glide:glide:4.5.0'
|
||||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.5.0'
|
annotationProcessor 'com.github.bumptech.glide:compiler:4.5.0'
|
||||||
|
kapt 'com.github.bumptech.glide:compiler:4.5.0'
|
||||||
implementation 'com.makeramen:roundedimageview:2.1.0'
|
implementation 'com.makeramen:roundedimageview:2.1.0'
|
||||||
implementation 'com.pnikosis:materialish-progress:1.5'
|
implementation 'com.pnikosis:materialish-progress:1.5'
|
||||||
implementation 'org.greenrobot:eventbus:3.0.0'
|
implementation 'org.greenrobot:eventbus:3.0.0'
|
||||||
|
7
res/drawable/ic_circle_check.xml
Normal file
7
res/drawable/ic_circle_check.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<vector android:height="24dp" android:viewportHeight="15"
|
||||||
|
android:viewportWidth="15" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#00000000"
|
||||||
|
android:pathData="M7.5,7.5m-6.5,0a6.5,6.5 0,1 1,13 0a6.5,6.5 0,1 1,-13 0"
|
||||||
|
android:strokeColor="#FFFFFF" android:strokeWidth="1"/>
|
||||||
|
<path android:fillColor="#FFFFFF" android:pathData="M4.77,7.61c-0.15,-0.15 -0.38,-0.15 -0.53,0c-0.15,0.15 -0.15,0.38 0,0.53l1.88,1.88c0.15,0.15 0.38,0.15 0.53,0l4.13,-4.12c0.15,-0.15 0.15,-0.38 0,-0.53c-0.15,-0.15 -0.38,-0.15 -0.53,0L6.38,9.22L4.77,7.61z"/>
|
||||||
|
</vector>
|
7
res/drawable/ic_circle_dot_dot_dot.xml
Normal file
7
res/drawable/ic_circle_dot_dot_dot.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<vector android:height="24dp" android:viewportHeight="15"
|
||||||
|
android:viewportWidth="15" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#00000000"
|
||||||
|
android:pathData="M7.5,7.5m-6.5,0a6.5,6.5 0,1 1,13 0a6.5,6.5 0,1 1,-13 0"
|
||||||
|
android:strokeColor="#FFFFFF" android:strokeWidth="1"/>
|
||||||
|
<path android:fillColor="#FFFFFF" android:pathData="M4.76,7.47c0,-0.32 0.26,-0.57 0.57,-0.57c0.32,0 0.57,0.25 0.57,0.57c0,0.31 -0.25,0.57 -0.57,0.57C5.02,8.04 4.76,7.78 4.76,7.47zM7.04,7.47c0,-0.32 0.26,-0.57 0.57,-0.57c0.32,0 0.57,0.25 0.57,0.57c0,0.31 -0.25,0.57 -0.57,0.57C7.3,8.04 7.04,7.78 7.04,7.47zM9.32,7.47c0,-0.32 0.26,-0.57 0.57,-0.57c0.32,0 0.57,0.25 0.57,0.57c0,0.31 -0.25,0.57 -0.57,0.57C9.58,8.04 9.32,7.78 9.32,7.47z"/>
|
||||||
|
</vector>
|
5
res/drawable/ic_filled_circle_check.xml
Normal file
5
res/drawable/ic_filled_circle_check.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:viewportHeight="13"
|
||||||
|
android:viewportWidth="13" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FFFFFF" android:pathData="M6.5,6.5m-6.5,0a6.5,6.5 0,1 1,13 0a6.5,6.5 0,1 1,-13 0"/>
|
||||||
|
<path android:fillColor="#231F20" android:pathData="M3.77,6.61c-0.15,-0.15 -0.38,-0.15 -0.53,0c-0.15,0.15 -0.15,0.38 0,0.53l1.88,1.88c0.15,0.15 0.38,0.15 0.53,0L9.78,4.9c0.15,-0.15 0.15,-0.38 0,-0.53c-0.15,-0.15 -0.38,-0.15 -0.53,0L5.38,8.22L3.77,6.61z"/>
|
||||||
|
</vector>
|
@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:background="@drawable/conversation_view_background"
|
android:background="@drawable/conversation_view_background"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:gravity="center_vertical">
|
android:gravity="center_vertical">
|
||||||
@ -70,22 +70,35 @@
|
|||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:gravity="center_vertical">
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
<TextView
|
<RelativeLayout
|
||||||
android:id="@+id/snippetTextView"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
android:maxLines="1"
|
|
||||||
android:ellipsize="end"
|
<TextView
|
||||||
android:textSize="@dimen/medium_font_size"
|
android:id="@+id/snippetTextView"
|
||||||
android:textColor="@color/text"
|
android:layout_width="wrap_content"
|
||||||
android:text="Sorry, gotta go fight crime again" />
|
android:layout_height="wrap_content"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:textSize="@dimen/medium_font_size"
|
||||||
|
android:textColor="@color/text"
|
||||||
|
android:text="Sorry, gotta go fight crime again" />
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.components.TypingIndicatorView
|
||||||
|
android:id="@+id/typingIndicatorView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
app:typingIndicator_tint="@color/text" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1" />
|
android:layout_weight="1" />
|
||||||
|
|
||||||
<View
|
<ImageView
|
||||||
android:id="@+id/statusIndicatorImageView"
|
android:id="@+id/statusIndicatorImageView"
|
||||||
android:layout_width="@dimen/conversation_view_status_indicator_size"
|
android:layout_width="@dimen/conversation_view_status_indicator_size"
|
||||||
android:layout_height="@dimen/conversation_view_status_indicator_size"
|
android:layout_height="@dimen/conversation_view_status_indicator_size"
|
||||||
|
@ -78,7 +78,7 @@ private class CreatePrivateChatActivityAdapter(val activity: CreatePrivateChatAc
|
|||||||
1 -> {
|
1 -> {
|
||||||
val result = ScanQRCodeWrapperFragment()
|
val result = ScanQRCodeWrapperFragment()
|
||||||
result.delegate = activity
|
result.delegate = activity
|
||||||
result.message = "Users can share their QR code by going into their account settings and tapping "Share QR Code""
|
result.message = "Users can share their QR code by going into their account settings and tapping \"Share QR Code\""
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
else -> throw IllegalStateException()
|
else -> throw IllegalStateException()
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package org.thoughtcrime.securesms.loki.redesign.activities
|
package org.thoughtcrime.securesms.loki.redesign.activities
|
||||||
|
|
||||||
|
import android.arch.lifecycle.Observer
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v7.widget.LinearLayoutManager
|
import android.support.v7.widget.LinearLayoutManager
|
||||||
@ -14,9 +15,12 @@ import org.thoughtcrime.securesms.database.DatabaseFactory
|
|||||||
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
||||||
import org.thoughtcrime.securesms.loki.redesign.utilities.push
|
import org.thoughtcrime.securesms.loki.redesign.utilities.push
|
||||||
import org.thoughtcrime.securesms.loki.redesign.views.ConversationView
|
import org.thoughtcrime.securesms.loki.redesign.views.ConversationView
|
||||||
|
import org.thoughtcrime.securesms.mms.GlideApp
|
||||||
|
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
|
|
||||||
class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListener {
|
class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListener {
|
||||||
|
private lateinit var glide: GlideRequests
|
||||||
|
|
||||||
// region Lifecycle
|
// region Lifecycle
|
||||||
constructor() : super()
|
constructor() : super()
|
||||||
@ -27,14 +31,25 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
|
|||||||
setContentView(R.layout.activity_home)
|
setContentView(R.layout.activity_home)
|
||||||
// Set title
|
// Set title
|
||||||
supportActionBar!!.title = "Messages"
|
supportActionBar!!.title = "Messages"
|
||||||
|
// Set up Glide
|
||||||
|
glide = GlideApp.with(this)
|
||||||
// Set up recycler view
|
// Set up recycler view
|
||||||
val cursor = DatabaseFactory.getThreadDatabase(this).conversationList
|
val cursor = DatabaseFactory.getThreadDatabase(this).conversationList
|
||||||
val conversationAdapter = HomeAdapter(this, cursor)
|
val homeAdapter = HomeAdapter(this, cursor)
|
||||||
conversationAdapter.conversationClickListener = this
|
homeAdapter.glide = glide
|
||||||
recyclerView.adapter = conversationAdapter
|
homeAdapter.conversationClickListener = this
|
||||||
|
recyclerView.adapter = homeAdapter
|
||||||
recyclerView.layoutManager = LinearLayoutManager(this)
|
recyclerView.layoutManager = LinearLayoutManager(this)
|
||||||
// Set up new conversation button
|
// Set up new conversation button
|
||||||
newConversationButton.setOnClickListener { createPrivateChat() }
|
newConversationButton.setOnClickListener { createPrivateChat() }
|
||||||
|
// Set up typing observer
|
||||||
|
ApplicationContext.getInstance(this).typingStatusRepository.typingThreads.observe(this, object : Observer<Set<Long>> {
|
||||||
|
|
||||||
|
override fun onChanged(threadIDs: Set<Long>?) {
|
||||||
|
val adapter = recyclerView.adapter as HomeAdapter
|
||||||
|
adapter.typingThreadIDs = threadIDs ?: setOf()
|
||||||
|
}
|
||||||
|
})
|
||||||
// Set up public chats and RSS feeds if needed
|
// Set up public chats and RSS feeds if needed
|
||||||
if (TextSecurePreferences.getLocalNumber(this) != null) {
|
if (TextSecurePreferences.getLocalNumber(this) != null) {
|
||||||
val application = ApplicationContext.getInstance(this)
|
val application = ApplicationContext.getInstance(this)
|
||||||
|
@ -8,9 +8,13 @@ import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter
|
|||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
||||||
import org.thoughtcrime.securesms.loki.redesign.views.ConversationView
|
import org.thoughtcrime.securesms.loki.redesign.views.ConversationView
|
||||||
|
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||||
|
|
||||||
class HomeAdapter(context: Context, cursor: Cursor) : CursorRecyclerViewAdapter<HomeAdapter.ViewHolder>(context, cursor) {
|
class HomeAdapter(context: Context, cursor: Cursor) : CursorRecyclerViewAdapter<HomeAdapter.ViewHolder>(context, cursor) {
|
||||||
private val threadDatabase = DatabaseFactory.getThreadDatabase(context)
|
private val threadDatabase = DatabaseFactory.getThreadDatabase(context)
|
||||||
|
lateinit var glide: GlideRequests
|
||||||
|
var typingThreadIDs = setOf<Long>()
|
||||||
|
set(value) { field = value; notifyDataSetChanged() }
|
||||||
var conversationClickListener: ConversationClickListener? = null
|
var conversationClickListener: ConversationClickListener? = null
|
||||||
|
|
||||||
class ViewHolder(val view: ConversationView) : RecyclerView.ViewHolder(view)
|
class ViewHolder(val view: ConversationView) : RecyclerView.ViewHolder(view)
|
||||||
@ -26,7 +30,9 @@ class HomeAdapter(context: Context, cursor: Cursor) : CursorRecyclerViewAdapter<
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindItemViewHolder(viewHolder: ViewHolder, cursor: Cursor) {
|
override fun onBindItemViewHolder(viewHolder: ViewHolder, cursor: Cursor) {
|
||||||
viewHolder.view.bind(getThread(cursor)!!)
|
val thread = getThread(cursor)!!
|
||||||
|
val isTyping = typingThreadIDs.contains(thread.threadId)
|
||||||
|
viewHolder.view.bind(thread, isTyping, glide)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getThread(cursor: Cursor): ThreadRecord? {
|
private fun getThread(cursor: Cursor): ThreadRecord? {
|
||||||
|
@ -11,6 +11,7 @@ import network.loki.messenger.R
|
|||||||
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
||||||
import org.thoughtcrime.securesms.loki.LokiAPIUtilities.populateUserHexEncodedPublicKeyCacheIfNeeded
|
import org.thoughtcrime.securesms.loki.LokiAPIUtilities.populateUserHexEncodedPublicKeyCacheIfNeeded
|
||||||
import org.thoughtcrime.securesms.loki.MentionUtilities.highlightMentions
|
import org.thoughtcrime.securesms.loki.MentionUtilities.highlightMentions
|
||||||
|
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||||
import org.thoughtcrime.securesms.util.DateUtils
|
import org.thoughtcrime.securesms.util.DateUtils
|
||||||
import org.whispersystems.signalservice.loki.api.LokiAPI
|
import org.whispersystems.signalservice.loki.api.LokiAPI
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -43,7 +44,7 @@ class ConversationView : LinearLayout {
|
|||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region Updating
|
// region Updating
|
||||||
fun bind(thread: ThreadRecord) {
|
fun bind(thread: ThreadRecord, isTyping: Boolean, glide: GlideRequests) {
|
||||||
this.thread = thread
|
this.thread = thread
|
||||||
populateUserHexEncodedPublicKeyCacheIfNeeded(thread.threadId, context) // FIXME: This is a terrible place to do this
|
populateUserHexEncodedPublicKeyCacheIfNeeded(thread.threadId, context) // FIXME: This is a terrible place to do this
|
||||||
unreadMessagesIndicatorView.visibility = if (thread.unreadCount > 0) View.VISIBLE else View.INVISIBLE
|
unreadMessagesIndicatorView.visibility = if (thread.unreadCount > 0) View.VISIBLE else View.INVISIBLE
|
||||||
@ -58,6 +59,7 @@ class ConversationView : LinearLayout {
|
|||||||
profilePictureView.additionalHexEncodedPublicKey = null
|
profilePictureView.additionalHexEncodedPublicKey = null
|
||||||
profilePictureView.isRSSFeed = false
|
profilePictureView.isRSSFeed = false
|
||||||
}
|
}
|
||||||
|
profilePictureView.glide = glide
|
||||||
profilePictureView.update()
|
profilePictureView.update()
|
||||||
val senderDisplayName = if (thread.recipient.isLocalNumber) context.getString(R.string.note_to_self) else thread.recipient.name
|
val senderDisplayName = if (thread.recipient.isLocalNumber) context.getString(R.string.note_to_self) else thread.recipient.name
|
||||||
displayNameTextView.text = senderDisplayName
|
displayNameTextView.text = senderDisplayName
|
||||||
@ -66,6 +68,21 @@ class ConversationView : LinearLayout {
|
|||||||
val snippet = highlightMentions(rawSnippet, thread.threadId, context)
|
val snippet = highlightMentions(rawSnippet, thread.threadId, context)
|
||||||
snippetTextView.text = snippet
|
snippetTextView.text = snippet
|
||||||
snippetTextView.typeface = if (thread.unreadCount > 0) Typeface.DEFAULT_BOLD else Typeface.DEFAULT
|
snippetTextView.typeface = if (thread.unreadCount > 0) Typeface.DEFAULT_BOLD else Typeface.DEFAULT
|
||||||
|
snippetTextView.visibility = if (isTyping) View.GONE else View.VISIBLE
|
||||||
|
if (isTyping) {
|
||||||
|
typingIndicatorView.startAnimation()
|
||||||
|
} else {
|
||||||
|
typingIndicatorView.stopAnimation()
|
||||||
|
}
|
||||||
|
typingIndicatorView.visibility = if (isTyping) View.VISIBLE else View.GONE
|
||||||
|
statusIndicatorImageView.visibility = View.VISIBLE
|
||||||
|
when {
|
||||||
|
!thread.isOutgoing || thread.isVerificationStatusChange -> statusIndicatorImageView.visibility = View.GONE
|
||||||
|
thread.isFailed -> statusIndicatorImageView.setImageResource(R.drawable.ic_error)
|
||||||
|
thread.isPending -> statusIndicatorImageView.setImageResource(R.drawable.ic_circle_dot_dot_dot)
|
||||||
|
thread.isRemoteRead -> statusIndicatorImageView.setImageResource(R.drawable.ic_filled_circle_check)
|
||||||
|
else -> statusIndicatorImageView.setImageResource(R.drawable.ic_circle_check)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
@ -4,11 +4,17 @@ import android.content.Context
|
|||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.widget.ImageView
|
||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import kotlinx.android.synthetic.main.view_profile_picture.view.*
|
import kotlinx.android.synthetic.main.view_profile_picture.view.*
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
|
import org.thoughtcrime.securesms.database.Address
|
||||||
|
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||||
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
|
||||||
class ProfilePictureView : RelativeLayout {
|
class ProfilePictureView : RelativeLayout {
|
||||||
|
lateinit var glide: GlideRequests
|
||||||
var hexEncodedPublicKey: String? = null
|
var hexEncodedPublicKey: String? = null
|
||||||
var additionalHexEncodedPublicKey: String? = null
|
var additionalHexEncodedPublicKey: String? = null
|
||||||
var isRSSFeed = false
|
var isRSSFeed = false
|
||||||
@ -44,6 +50,22 @@ class ProfilePictureView : RelativeLayout {
|
|||||||
doubleModeImageViewContainer.visibility = if (additionalHexEncodedPublicKey != null && !isRSSFeed) View.VISIBLE else View.INVISIBLE
|
doubleModeImageViewContainer.visibility = if (additionalHexEncodedPublicKey != null && !isRSSFeed) View.VISIBLE else View.INVISIBLE
|
||||||
singleModeImageView.visibility = if (additionalHexEncodedPublicKey == null && !isRSSFeed) View.VISIBLE else View.INVISIBLE
|
singleModeImageView.visibility = if (additionalHexEncodedPublicKey == null && !isRSSFeed) View.VISIBLE else View.INVISIBLE
|
||||||
rssTextView.visibility = if (isRSSFeed) View.VISIBLE else View.INVISIBLE
|
rssTextView.visibility = if (isRSSFeed) View.VISIBLE else View.INVISIBLE
|
||||||
|
fun setProfilePictureIfNeeded(imageView: ImageView, hexEncodedPublicKey: String) {
|
||||||
|
glide.clear(imageView)
|
||||||
|
if (hexEncodedPublicKey.isNotEmpty()) {
|
||||||
|
val signalProfilePicture = Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false).contactPhoto
|
||||||
|
if (signalProfilePicture != null) {
|
||||||
|
glide.load(signalProfilePicture).diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop().into(imageView)
|
||||||
|
} else {
|
||||||
|
imageView.setImageDrawable(null)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
imageView.setImageDrawable(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setProfilePictureIfNeeded(singleModeImageView, hexEncodedPublicKey)
|
||||||
|
setProfilePictureIfNeeded(doubleModeImageView1, hexEncodedPublicKey)
|
||||||
|
setProfilePictureIfNeeded(doubleModeImageView2, additionalHexEncodedPublicKey ?: "")
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user