From 46e5bb7ece54db7390d1a8ca2d64aa9afd57b259 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Sun, 18 Mar 2018 07:00:21 +0100 Subject: [PATCH] Improve conversation view --- .../apps/abit/ConversationDetailFragment.kt | 188 ++++++++++++++++++ .../apps/abit/ConversationListFragment.kt | 4 +- .../java/ch/dissem/apps/abit/Identicon.kt | 4 +- .../java/ch/dissem/apps/abit/MainActivity.kt | 18 +- .../dissem/apps/abit/MessageDetailActivity.kt | 15 +- .../apps/abit/adapter/ConversationAdapter.kt | 152 ++++++++++++++ .../dissem/apps/abit/adapter/LabelAdapter.kt | 5 +- app/src/main/res/drawable/ic_menu.xml | 9 + .../layout/fragment_conversation_detail.xml | 59 ++++++ .../main/res/layout/item_message_detail.xml | 103 ++++++++++ app/src/main/res/menu/conversation.xml | 15 ++ app/src/main/res/values/strings.xml | 1 + 12 files changed, 559 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/ch/dissem/apps/abit/ConversationDetailFragment.kt create mode 100644 app/src/main/java/ch/dissem/apps/abit/adapter/ConversationAdapter.kt create mode 100644 app/src/main/res/drawable/ic_menu.xml create mode 100644 app/src/main/res/layout/fragment_conversation_detail.xml create mode 100644 app/src/main/res/layout/item_message_detail.xml create mode 100644 app/src/main/res/menu/conversation.xml diff --git a/app/src/main/java/ch/dissem/apps/abit/ConversationDetailFragment.kt b/app/src/main/java/ch/dissem/apps/abit/ConversationDetailFragment.kt new file mode 100644 index 0000000..f1e202b --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/ConversationDetailFragment.kt @@ -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 + ) : 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" + } +} diff --git a/app/src/main/java/ch/dissem/apps/abit/ConversationListFragment.kt b/app/src/main/java/ch/dissem/apps/abit/ConversationListFragment.kt index 38dbf26..c5a49c7 100644 --- a/app/src/main/java/ch/dissem/apps/abit/ConversationListFragment.kt +++ b/app/src/main/java/ch/dissem/apps/abit/ConversationListFragment.kt @@ -203,9 +203,7 @@ class ConversationListFragment : Fragment(), ListHolder<Label> { val position = recycler_view.getChildAdapterPosition(v) adapter.setSelectedPosition(position) if (position != RecyclerView.NO_POSITION) { - adapter.getItem(position).messages.firstOrNull()?.let { - MainActivity.apply { onItemSelected(it) } - } + MainActivity.apply { onItemSelected(adapter.getItem(position)) } } } } diff --git a/app/src/main/java/ch/dissem/apps/abit/Identicon.kt b/app/src/main/java/ch/dissem/apps/abit/Identicon.kt index 4709b1b..1ec06c9 100644 --- a/app/src/main/java/ch/dissem/apps/abit/Identicon.kt +++ b/app/src/main/java/ch/dissem/apps/abit/Identicon.kt @@ -124,7 +124,7 @@ class MultiIdenticon(input: List<BitmessageAddress>, @ColorInt val backgroundCol color = backgroundColor } - val identicons = input.map { Identicon(it) }.take(4) + private val identicons = input.map { Identicon(it) }.take(4) override fun draw(canvas: Canvas) { val width = canvas.width.toFloat() @@ -132,7 +132,7 @@ class MultiIdenticon(input: List<BitmessageAddress>, @ColorInt val backgroundCol when (identicons.size) { 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 -> { canvas.drawCircle(width / 2, height / 2, width / 2, paint) val w = width / 2 diff --git a/app/src/main/java/ch/dissem/apps/abit/MainActivity.kt b/app/src/main/java/ch/dissem/apps/abit/MainActivity.kt index 7277e88..3a17c41 100644 --- a/app/src/main/java/ch/dissem/apps/abit/MainActivity.kt +++ b/app/src/main/java/ch/dissem/apps/abit/MainActivity.kt @@ -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.currentLabel 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.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.Conversation import ch.dissem.bitmessage.entity.Plaintext import ch.dissem.bitmessage.entity.valueobject.Label import com.github.amlcurran.showcaseview.ShowcaseView @@ -458,6 +462,13 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> { // adding or replacing the detail fragment using a // fragment transaction. val fragment = when (item) { + is Conversation -> { + ConversationDetailFragment().apply { + arguments = Bundle().apply { + putSerializable(ConversationDetailFragment.ARG_ITEM, item) + } + } + } is Plaintext -> { if (item.labels.any { it.type == Label.Type.DRAFT }) { ComposeMessageFragment().apply { @@ -489,6 +500,11 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> { // In single-pane mode, simply start the detail activity // for the selected item ID. val detailIntent = when (item) { + is Conversation -> { + Intent(this, MessageDetailActivity::class.java).apply { + putExtra(ConversationDetailFragment.ARG_ITEM, item) + } + } is Plaintext -> { if (item.labels.any { it.type == Label.Type.DRAFT }) { Intent(this, ComposeMessageActivity::class.java).apply { diff --git a/app/src/main/java/ch/dissem/apps/abit/MessageDetailActivity.kt b/app/src/main/java/ch/dissem/apps/abit/MessageDetailActivity.kt index 62d2cec..bb29b16 100644 --- a/app/src/main/java/ch/dissem/apps/abit/MessageDetailActivity.kt +++ b/app/src/main/java/ch/dissem/apps/abit/MessageDetailActivity.kt @@ -4,6 +4,7 @@ import android.content.Intent import android.os.Bundle import android.support.v4.app.NavUtils 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 // using a fragment transaction. val arguments = Bundle() - arguments.putSerializable(MessageDetailFragment.ARG_ITEM, - intent.getSerializableExtra(MessageDetailFragment.ARG_ITEM)) - val fragment = MessageDetailFragment() + val item = intent.getSerializableExtra(MessageDetailFragment.ARG_ITEM) + arguments.putSerializable(MessageDetailFragment.ARG_ITEM, item) + val fragment = if (item is Conversation) { + ConversationDetailFragment() + } else { + MessageDetailFragment() + } fragment.arguments = arguments supportFragmentManager.beginTransaction() - .add(R.id.content, fragment) - .commit() + .add(R.id.content, fragment) + .commit() } } diff --git a/app/src/main/java/ch/dissem/apps/abit/adapter/ConversationAdapter.kt b/app/src/main/java/ch/dissem/apps/abit/adapter/ConversationAdapter.kt new file mode 100644 index 0000000..b2edc00 --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/adapter/ConversationAdapter.kt @@ -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) + } + } +} diff --git a/app/src/main/java/ch/dissem/apps/abit/adapter/LabelAdapter.kt b/app/src/main/java/ch/dissem/apps/abit/adapter/LabelAdapter.kt index cf0a41f..9d89902 100644 --- a/app/src/main/java/ch/dissem/apps/abit/adapter/LabelAdapter.kt +++ b/app/src/main/java/ch/dissem/apps/abit/adapter/LabelAdapter.kt @@ -46,15 +46,14 @@ class LabelAdapter internal constructor(private val ctx: Context, labels: Collec override fun getItemCount() = labels.size class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - val background = itemView var icon = itemView.findViewById<IconicsImageView>(R.id.icon)!! var label = itemView.findViewById<TextView>(R.id.label)!! fun setBackground(@ColorInt color: Int) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - background.backgroundTintList = ColorStateList.valueOf(color) + itemView.backgroundTintList = ColorStateList.valueOf(color) } else { - background.backgroundColor = color + itemView.backgroundColor = color } } } diff --git a/app/src/main/res/drawable/ic_menu.xml b/app/src/main/res/drawable/ic_menu.xml new file mode 100644 index 0000000..7b81bbb --- /dev/null +++ b/app/src/main/res/drawable/ic_menu.xml @@ -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> diff --git a/app/src/main/res/layout/fragment_conversation_detail.xml b/app/src/main/res/layout/fragment_conversation_detail.xml new file mode 100644 index 0000000..64a8b25 --- /dev/null +++ b/app/src/main/res/layout/fragment_conversation_detail.xml @@ -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> diff --git a/app/src/main/res/layout/item_message_detail.xml b/app/src/main/res/layout/item_message_detail.xml new file mode 100644 index 0000000..203903b --- /dev/null +++ b/app/src/main/res/layout/item_message_detail.xml @@ -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> diff --git a/app/src/main/res/menu/conversation.xml b/app/src/main/res/menu/conversation.xml new file mode 100644 index 0000000..48cbb38 --- /dev/null +++ b/app/src/main/res/menu/conversation.xml @@ -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> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3e0747a..007bb84 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -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="encoding_simple">simple</string> <string name="encoding_extended">extended</string> + <string name="context_menu">actions</string> </resources>