Improve conversation view
This commit is contained in:
parent
8004865e01
commit
46e5bb7ece
@ -0,0 +1,188 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Christian Basler
|
||||||
|
*
|
||||||
|
* 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 ch.dissem.apps.abit
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.annotation.IdRes
|
||||||
|
import android.support.v4.app.Fragment
|
||||||
|
import android.support.v7.widget.LinearLayoutManager
|
||||||
|
import android.support.v7.widget.RecyclerView
|
||||||
|
import android.view.*
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import ch.dissem.apps.abit.adapter.ConversationAdapter
|
||||||
|
import ch.dissem.apps.abit.service.Singleton
|
||||||
|
import ch.dissem.apps.abit.util.Assets
|
||||||
|
import ch.dissem.apps.abit.util.Drawables
|
||||||
|
import ch.dissem.apps.abit.util.Strings.prepareMessageExtract
|
||||||
|
import ch.dissem.bitmessage.entity.Conversation
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
|
import com.mikepenz.google_material_typeface_library.GoogleMaterial
|
||||||
|
import kotlinx.android.synthetic.main.fragment_conversation_detail.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A fragment representing a single Message detail screen.
|
||||||
|
* This fragment is either contained in a [MainActivity]
|
||||||
|
* in two-pane mode (on tablets) or a [MessageDetailActivity]
|
||||||
|
* on handsets.
|
||||||
|
*/
|
||||||
|
class ConversationDetailFragment : Fragment() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The content this fragment is presenting.
|
||||||
|
*/
|
||||||
|
private var item: Conversation? = null
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
arguments?.let { arguments ->
|
||||||
|
if (arguments.containsKey(ARG_ITEM)) {
|
||||||
|
// Load the dummy content specified by the fragment
|
||||||
|
// arguments. In a real-world scenario, use a Loader
|
||||||
|
// to load content from a content provider.
|
||||||
|
item = arguments.getSerializable(ARG_ITEM) as Conversation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setHasOptionsMenu(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View =
|
||||||
|
inflater.inflate(R.layout.fragment_conversation_detail, container, false)
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
val ctx = activity ?: throw IllegalStateException("Fragment is not attached to an activity")
|
||||||
|
|
||||||
|
// Show the dummy content as text in a TextView.
|
||||||
|
item?.let { item ->
|
||||||
|
subject.text = item.subject
|
||||||
|
avatar.setImageDrawable(MultiIdenticon(item.participants))
|
||||||
|
messages.adapter = ConversationAdapter(ctx, this@ConversationDetailFragment, item)
|
||||||
|
messages.layoutManager = LinearLayoutManager(activity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
|
inflater.inflate(R.menu.conversation, menu)
|
||||||
|
activity?.let { activity ->
|
||||||
|
Drawables.addIcon(activity, menu, R.id.delete, GoogleMaterial.Icon.gmd_delete)
|
||||||
|
Drawables.addIcon(activity, menu, R.id.archive, GoogleMaterial.Icon.gmd_archive)
|
||||||
|
}
|
||||||
|
super.onCreateOptionsMenu(menu, inflater)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
|
||||||
|
val messageRepo = Singleton.getMessageRepository(
|
||||||
|
context ?: throw IllegalStateException("No context available")
|
||||||
|
)
|
||||||
|
item?.let { item ->
|
||||||
|
when (menuItem.itemId) {
|
||||||
|
R.id.delete -> {
|
||||||
|
item.messages.forEach {
|
||||||
|
Singleton.labeler.delete(it)
|
||||||
|
messageRepo.remove(it)
|
||||||
|
}
|
||||||
|
MainActivity.apply { updateUnread() }
|
||||||
|
activity?.onBackPressed()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
R.id.archive -> {
|
||||||
|
item.messages.forEach {
|
||||||
|
Singleton.labeler.archive(it)
|
||||||
|
messageRepo.save(it)
|
||||||
|
|
||||||
|
}
|
||||||
|
MainActivity.apply { updateUnread() }
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
else -> return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RelatedMessageAdapter internal constructor(
|
||||||
|
private val ctx: Context,
|
||||||
|
private val messages: List<Plaintext>
|
||||||
|
) : RecyclerView.Adapter<RelatedMessageAdapter.ViewHolder>() {
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(
|
||||||
|
parent: ViewGroup,
|
||||||
|
viewType: Int
|
||||||
|
): RelatedMessageAdapter.ViewHolder {
|
||||||
|
val context = parent.context
|
||||||
|
val inflater = LayoutInflater.from(context)
|
||||||
|
|
||||||
|
// Inflate the custom layout
|
||||||
|
val contactView = inflater.inflate(R.layout.item_message_minimized, parent, false)
|
||||||
|
|
||||||
|
// Return a new holder instance
|
||||||
|
return ViewHolder(contactView)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Involves populating data into the item through holder
|
||||||
|
override fun onBindViewHolder(viewHolder: RelatedMessageAdapter.ViewHolder, position: Int) {
|
||||||
|
// Get the data model based on position
|
||||||
|
val message = messages[position]
|
||||||
|
|
||||||
|
viewHolder.avatar.setImageDrawable(Identicon(message.from))
|
||||||
|
viewHolder.status.setImageResource(Assets.getStatusDrawable(message.status))
|
||||||
|
viewHolder.sender.text = message.from.toString()
|
||||||
|
viewHolder.extract.text = prepareMessageExtract(message.text)
|
||||||
|
viewHolder.item = message
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the total count of items in the list
|
||||||
|
override fun getItemCount() = messages.size
|
||||||
|
|
||||||
|
internal inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
internal val avatar = itemView.findViewById<ImageView>(R.id.avatar)
|
||||||
|
internal val status = itemView.findViewById<ImageView>(R.id.status)
|
||||||
|
internal val sender = itemView.findViewById<TextView>(R.id.sender)
|
||||||
|
internal val extract = itemView.findViewById<TextView>(R.id.text)
|
||||||
|
internal var item: Plaintext? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
itemView.setOnClickListener {
|
||||||
|
if (ctx is MainActivity) {
|
||||||
|
item?.let { ctx.onItemSelected(it) }
|
||||||
|
} else {
|
||||||
|
val detailIntent = Intent(ctx, MessageDetailActivity::class.java)
|
||||||
|
detailIntent.putExtra(MessageDetailFragment.ARG_ITEM, item)
|
||||||
|
ctx.startActivity(detailIntent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* The fragment argument representing the item ID that this fragment
|
||||||
|
* represents.
|
||||||
|
*/
|
||||||
|
const val ARG_ITEM = "item"
|
||||||
|
}
|
||||||
|
}
|
@ -203,9 +203,7 @@ class ConversationListFragment : Fragment(), ListHolder<Label> {
|
|||||||
val position = recycler_view.getChildAdapterPosition(v)
|
val position = recycler_view.getChildAdapterPosition(v)
|
||||||
adapter.setSelectedPosition(position)
|
adapter.setSelectedPosition(position)
|
||||||
if (position != RecyclerView.NO_POSITION) {
|
if (position != RecyclerView.NO_POSITION) {
|
||||||
adapter.getItem(position).messages.firstOrNull()?.let {
|
MainActivity.apply { onItemSelected(adapter.getItem(position)) }
|
||||||
MainActivity.apply { onItemSelected(it) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,7 +124,7 @@ class MultiIdenticon(input: List<BitmessageAddress>, @ColorInt val backgroundCol
|
|||||||
color = backgroundColor
|
color = backgroundColor
|
||||||
}
|
}
|
||||||
|
|
||||||
val identicons = input.map { Identicon(it) }.take(4)
|
private val identicons = input.map { Identicon(it) }.take(4)
|
||||||
|
|
||||||
override fun draw(canvas: Canvas) {
|
override fun draw(canvas: Canvas) {
|
||||||
val width = canvas.width.toFloat()
|
val width = canvas.width.toFloat()
|
||||||
@ -132,7 +132,7 @@ class MultiIdenticon(input: List<BitmessageAddress>, @ColorInt val backgroundCol
|
|||||||
|
|
||||||
when (identicons.size) {
|
when (identicons.size) {
|
||||||
0 -> canvas.drawCircle(width / 2, height / 2, width / 2, paint)
|
0 -> canvas.drawCircle(width / 2, height / 2, width / 2, paint)
|
||||||
1 -> identicons.first().draw(canvas)
|
1 -> identicons.first().draw(canvas, 0f, 0f, width, height)
|
||||||
2 -> {
|
2 -> {
|
||||||
canvas.drawCircle(width / 2, height / 2, width / 2, paint)
|
canvas.drawCircle(width / 2, height / 2, width / 2, paint)
|
||||||
val w = width / 2
|
val w = width / 2
|
||||||
|
@ -32,9 +32,13 @@ import ch.dissem.apps.abit.repository.AndroidLabelRepository.Companion.LABEL_ARC
|
|||||||
import ch.dissem.apps.abit.service.Singleton
|
import ch.dissem.apps.abit.service.Singleton
|
||||||
import ch.dissem.apps.abit.service.Singleton.currentLabel
|
import ch.dissem.apps.abit.service.Singleton.currentLabel
|
||||||
import ch.dissem.apps.abit.synchronization.SyncAdapter
|
import ch.dissem.apps.abit.synchronization.SyncAdapter
|
||||||
import ch.dissem.apps.abit.util.*
|
import ch.dissem.apps.abit.util.NetworkUtils
|
||||||
|
import ch.dissem.apps.abit.util.Preferences
|
||||||
|
import ch.dissem.apps.abit.util.getColor
|
||||||
|
import ch.dissem.apps.abit.util.getIcon
|
||||||
import ch.dissem.bitmessage.BitmessageContext
|
import ch.dissem.bitmessage.BitmessageContext
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
|
import ch.dissem.bitmessage.entity.Conversation
|
||||||
import ch.dissem.bitmessage.entity.Plaintext
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
import ch.dissem.bitmessage.entity.valueobject.Label
|
import ch.dissem.bitmessage.entity.valueobject.Label
|
||||||
import com.github.amlcurran.showcaseview.ShowcaseView
|
import com.github.amlcurran.showcaseview.ShowcaseView
|
||||||
@ -458,6 +462,13 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
|
|||||||
// adding or replacing the detail fragment using a
|
// adding or replacing the detail fragment using a
|
||||||
// fragment transaction.
|
// fragment transaction.
|
||||||
val fragment = when (item) {
|
val fragment = when (item) {
|
||||||
|
is Conversation -> {
|
||||||
|
ConversationDetailFragment().apply {
|
||||||
|
arguments = Bundle().apply {
|
||||||
|
putSerializable(ConversationDetailFragment.ARG_ITEM, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
is Plaintext -> {
|
is Plaintext -> {
|
||||||
if (item.labels.any { it.type == Label.Type.DRAFT }) {
|
if (item.labels.any { it.type == Label.Type.DRAFT }) {
|
||||||
ComposeMessageFragment().apply {
|
ComposeMessageFragment().apply {
|
||||||
@ -489,6 +500,11 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
|
|||||||
// In single-pane mode, simply start the detail activity
|
// In single-pane mode, simply start the detail activity
|
||||||
// for the selected item ID.
|
// for the selected item ID.
|
||||||
val detailIntent = when (item) {
|
val detailIntent = when (item) {
|
||||||
|
is Conversation -> {
|
||||||
|
Intent(this, MessageDetailActivity::class.java).apply {
|
||||||
|
putExtra(ConversationDetailFragment.ARG_ITEM, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
is Plaintext -> {
|
is Plaintext -> {
|
||||||
if (item.labels.any { it.type == Label.Type.DRAFT }) {
|
if (item.labels.any { it.type == Label.Type.DRAFT }) {
|
||||||
Intent(this, ComposeMessageActivity::class.java).apply {
|
Intent(this, ComposeMessageActivity::class.java).apply {
|
||||||
|
@ -4,6 +4,7 @@ import android.content.Intent
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v4.app.NavUtils
|
import android.support.v4.app.NavUtils
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import ch.dissem.bitmessage.entity.Conversation
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -33,13 +34,17 @@ class MessageDetailActivity : DetailActivity() {
|
|||||||
// Create the detail fragment and add it to the activity
|
// Create the detail fragment and add it to the activity
|
||||||
// using a fragment transaction.
|
// using a fragment transaction.
|
||||||
val arguments = Bundle()
|
val arguments = Bundle()
|
||||||
arguments.putSerializable(MessageDetailFragment.ARG_ITEM,
|
val item = intent.getSerializableExtra(MessageDetailFragment.ARG_ITEM)
|
||||||
intent.getSerializableExtra(MessageDetailFragment.ARG_ITEM))
|
arguments.putSerializable(MessageDetailFragment.ARG_ITEM, item)
|
||||||
val fragment = MessageDetailFragment()
|
val fragment = if (item is Conversation) {
|
||||||
|
ConversationDetailFragment()
|
||||||
|
} else {
|
||||||
|
MessageDetailFragment()
|
||||||
|
}
|
||||||
fragment.arguments = arguments
|
fragment.arguments = arguments
|
||||||
supportFragmentManager.beginTransaction()
|
supportFragmentManager.beginTransaction()
|
||||||
.add(R.id.content, fragment)
|
.add(R.id.content, fragment)
|
||||||
.commit()
|
.commit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,152 @@
|
|||||||
|
package ch.dissem.apps.abit.adapter
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.support.v4.app.Fragment
|
||||||
|
import android.support.v7.widget.GridLayoutManager
|
||||||
|
import android.support.v7.widget.PopupMenu
|
||||||
|
import android.support.v7.widget.RecyclerView
|
||||||
|
import android.text.util.Linkify
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import ch.dissem.apps.abit.*
|
||||||
|
import ch.dissem.apps.abit.service.Singleton
|
||||||
|
import ch.dissem.apps.abit.util.Assets
|
||||||
|
import ch.dissem.apps.abit.util.Constants
|
||||||
|
import ch.dissem.bitmessage.entity.Conversation
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.Label
|
||||||
|
import ch.dissem.bitmessage.ports.MessageRepository
|
||||||
|
|
||||||
|
|
||||||
|
class ConversationAdapter internal constructor(
|
||||||
|
ctx: Context,
|
||||||
|
private val parent: Fragment,
|
||||||
|
private val conversation: Conversation
|
||||||
|
) : RecyclerView.Adapter<ConversationAdapter.ViewHolder>() {
|
||||||
|
|
||||||
|
private val messageRepo = Singleton.getMessageRepository(ctx)
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(
|
||||||
|
parent: ViewGroup,
|
||||||
|
viewType: Int
|
||||||
|
): ConversationAdapter.ViewHolder {
|
||||||
|
val context = parent.context
|
||||||
|
val inflater = LayoutInflater.from(context)
|
||||||
|
|
||||||
|
// Inflate the custom layout
|
||||||
|
val messageView = inflater.inflate(R.layout.item_message_detail, parent, false)
|
||||||
|
|
||||||
|
// Return a new holder instance
|
||||||
|
return ViewHolder(messageView, this.parent, messageRepo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Involves populating data into the item through holder
|
||||||
|
override fun onBindViewHolder(viewHolder: ConversationAdapter.ViewHolder, position: Int) {
|
||||||
|
// Get the data model based on position
|
||||||
|
val message = conversation.messages[position]
|
||||||
|
|
||||||
|
viewHolder.apply {
|
||||||
|
item = message
|
||||||
|
avatar.setImageDrawable(Identicon(message.from))
|
||||||
|
sender.text = message.from.toString()
|
||||||
|
val senderClickListener: (View) -> Unit = {
|
||||||
|
MainActivity.apply {
|
||||||
|
onItemSelected(message.from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
avatar.setOnClickListener(senderClickListener)
|
||||||
|
sender.setOnClickListener(senderClickListener)
|
||||||
|
|
||||||
|
recipient.text = message.to.toString()
|
||||||
|
status.setImageResource(Assets.getStatusDrawable(message.status))
|
||||||
|
text.text = message.text
|
||||||
|
|
||||||
|
Linkify.addLinks(text, Linkify.WEB_URLS)
|
||||||
|
Linkify.addLinks(text,
|
||||||
|
Constants.BITMESSAGE_ADDRESS_PATTERN,
|
||||||
|
Constants.BITMESSAGE_URL_SCHEMA, null,
|
||||||
|
Linkify.TransformFilter { match, _ -> match.group() }
|
||||||
|
)
|
||||||
|
|
||||||
|
labelAdapter.labels = message.labels.toList()
|
||||||
|
|
||||||
|
// FIXME: I think that's not quite correct
|
||||||
|
if (message.isUnread()) {
|
||||||
|
Singleton.labeler.markAsRead(message)
|
||||||
|
messageRepo.save(message)
|
||||||
|
MainActivity.apply { updateUnread() }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = conversation.messages.size
|
||||||
|
|
||||||
|
class ViewHolder(
|
||||||
|
itemView: View,
|
||||||
|
parent: Fragment,
|
||||||
|
messageRepo: MessageRepository
|
||||||
|
) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
var item: Plaintext? = null
|
||||||
|
val avatar = itemView.findViewById<ImageView>(R.id.avatar)!!
|
||||||
|
val sender = itemView.findViewById<TextView>(R.id.sender)!!
|
||||||
|
val recipient = itemView.findViewById<TextView>(R.id.recipient)!!
|
||||||
|
val status = itemView.findViewById<ImageView>(R.id.status)!!
|
||||||
|
val menu = itemView.findViewById<ImageView>(R.id.menu)!!.also { view ->
|
||||||
|
view.setOnClickListener {
|
||||||
|
val popup = PopupMenu(itemView.context, view)
|
||||||
|
popup.menuInflater.inflate(R.menu.message, popup.menu)
|
||||||
|
popup.setOnMenuItemClickListener {
|
||||||
|
item?.let { item ->
|
||||||
|
when (it.itemId) {
|
||||||
|
R.id.reply -> {
|
||||||
|
ComposeMessageActivity.launchReplyTo(parent, item)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.delete -> {
|
||||||
|
if (MessageDetailFragment.isInTrash(item)) {
|
||||||
|
Singleton.labeler.delete(item)
|
||||||
|
messageRepo.remove(item)
|
||||||
|
} else {
|
||||||
|
Singleton.labeler.delete(item)
|
||||||
|
messageRepo.save(item)
|
||||||
|
}
|
||||||
|
MainActivity.apply {
|
||||||
|
updateUnread()
|
||||||
|
onBackPressed()
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.mark_unread -> {
|
||||||
|
Singleton.labeler.markAsUnread(item)
|
||||||
|
messageRepo.save(item)
|
||||||
|
MainActivity.apply { updateUnread() }
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.archive -> {
|
||||||
|
Singleton.labeler.archive(item)
|
||||||
|
messageRepo.save(item)
|
||||||
|
MainActivity.apply { updateUnread() }
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
} ?: false
|
||||||
|
}
|
||||||
|
popup.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val text = itemView.findViewById<TextView>(R.id.text)!!.apply {
|
||||||
|
linksClickable = true
|
||||||
|
setTextIsSelectable(true)
|
||||||
|
}
|
||||||
|
val labelAdapter = LabelAdapter(itemView.context, emptySet<Label>())
|
||||||
|
val labels = itemView.findViewById<RecyclerView>(R.id.labels)!!.apply {
|
||||||
|
adapter = labelAdapter
|
||||||
|
layoutManager = GridLayoutManager(itemView.context, 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -46,15 +46,14 @@ class LabelAdapter internal constructor(private val ctx: Context, labels: Collec
|
|||||||
override fun getItemCount() = labels.size
|
override fun getItemCount() = labels.size
|
||||||
|
|
||||||
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
val background = itemView
|
|
||||||
var icon = itemView.findViewById<IconicsImageView>(R.id.icon)!!
|
var icon = itemView.findViewById<IconicsImageView>(R.id.icon)!!
|
||||||
var label = itemView.findViewById<TextView>(R.id.label)!!
|
var label = itemView.findViewById<TextView>(R.id.label)!!
|
||||||
|
|
||||||
fun setBackground(@ColorInt color: Int) {
|
fun setBackground(@ColorInt color: Int) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
background.backgroundTintList = ColorStateList.valueOf(color)
|
itemView.backgroundTintList = ColorStateList.valueOf(color)
|
||||||
} else {
|
} else {
|
||||||
background.backgroundColor = color
|
itemView.backgroundColor = color
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
9
app/src/main/res/drawable/ic_menu.xml
Normal file
9
app/src/main/res/drawable/ic_menu.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:viewportWidth="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000"
|
||||||
|
android:pathData="M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z" />
|
||||||
|
</vector>
|
59
app/src/main/res/layout/fragment_conversation_detail.xml
Normal file
59
app/src/main/res/layout/fragment_conversation_detail.xml
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
|
android:focusableInTouchMode="true"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="64dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/subject"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_toStartOf="@+id/avatar"
|
||||||
|
android:elegantTextHeight="false"
|
||||||
|
android:enabled="false"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
|
tools:ignore="UnusedAttribute"
|
||||||
|
tools:text="Subject" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/avatar"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_margin="10dp"
|
||||||
|
android:src="@color/colorAccent"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/divider"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="2dip"
|
||||||
|
android:layout_below="@id/subject"
|
||||||
|
android:background="@color/divider" />
|
||||||
|
|
||||||
|
<android.support.v7.widget.RecyclerView
|
||||||
|
android:id="@+id/messages"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/divider"
|
||||||
|
android:animateLayoutChanges="true"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginRight="16dp"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
</ScrollView>
|
103
app/src/main/res/layout/item_message_detail.xml
Normal file
103
app/src/main/res/layout/item_message_detail.xml
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
<?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:fitsSystemWindows="true"
|
||||||
|
android:focusableInTouchMode="true"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/header"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/avatar"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:src="@color/colorAccent"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sender"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_alignTop="@+id/avatar"
|
||||||
|
android:layout_toEndOf="@+id/avatar"
|
||||||
|
android:layout_toStartOf="@+id/status"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingEnd="0dp"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="Sender" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/recipient"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_alignBottom="@+id/avatar"
|
||||||
|
android:layout_toEndOf="@+id/avatar"
|
||||||
|
android:layout_toStartOf="@+id/status"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingEnd="0dp"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
tools:text="Recipient" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/status"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_toStartOf="@+id/menu"
|
||||||
|
android:paddingBottom="8dp"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:tint="@color/colorAccent"
|
||||||
|
tools:ignore="ContentDescription"
|
||||||
|
tools:src="@drawable/ic_notification_proof_of_work" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/menu"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:contentDescription="@string/context_menu"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:src="@drawable/ic_menu" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/body"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:textIsSelectable="true"
|
||||||
|
tools:text="Message Body" />
|
||||||
|
|
||||||
|
<android.support.v7.widget.RecyclerView
|
||||||
|
android:id="@+id/labels"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="16dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/divider"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="2dip"
|
||||||
|
android:background="@color/divider" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
15
app/src/main/res/menu/conversation.xml
Normal file
15
app/src/main/res/menu/conversation.xml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item
|
||||||
|
android:id="@+id/delete"
|
||||||
|
app:showAsAction="ifRoom"
|
||||||
|
android:icon="@drawable/ic_action_delete"
|
||||||
|
android:title="@string/delete"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/archive"
|
||||||
|
app:showAsAction="ifRoom"
|
||||||
|
android:icon="@drawable/ic_action_archive"
|
||||||
|
android:title="@string/archive"/>
|
||||||
|
</menu>
|
@ -136,4 +136,5 @@ As an alternative you could configure a trusted node in the settings, but as of
|
|||||||
<string name="broadcasts">Broadcasts</string>
|
<string name="broadcasts">Broadcasts</string>
|
||||||
<string name="encoding_simple">simple</string>
|
<string name="encoding_simple">simple</string>
|
||||||
<string name="encoding_extended">extended</string>
|
<string name="encoding_extended">extended</string>
|
||||||
|
<string name="context_menu">actions</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
Reference in New Issue
Block a user