diff --git a/app/build.gradle.kts b/app/build.gradle.kts index fbcb8b236714ac4f0a07171afcd4bbfdbc7c9863..f9a4c52a00e32f7e4684776b80f79db6927e1f31 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -137,6 +137,7 @@ dependencies { // UI implementation("me.zhanghai.android.materialprogressbar", "library", "1.4.2") implementation("com.afollestad.material-dialogs", "core", "0.9.6.0") + implementation("me.saket", "better-link-movement-method", "2.1.0") implementation(project(":slidingpanel")) // Quality Assurance diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferListAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferListAdapter.kt index de653dc1ebb0cc21fc8d52d49c5e5634b8f9b1b5..ee95141591bc048b821deb4e479d8e3f3de02711 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferListAdapter.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferListAdapter.kt @@ -47,12 +47,10 @@ class BufferListAdapter( collapsedNetworks.onNext(collapsedNetworks.value.orEmpty() + networkId) } - fun toggleSelection(buffer: BufferId) { - if (selectedBuffer.value == buffer) { - selectedBuffer.onNext(-1) - } else { - selectedBuffer.onNext(buffer) - } + fun toggleSelection(buffer: BufferId): Boolean { + val next = if (selectedBuffer.value == buffer) -1 else buffer + selectedBuffer.onNext(next) + return next != -1 } fun unselectAll() { diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt index df45c9a343af0c0faaa18557ee0e245ee3ee218b..879f3f25f91dbaaa6d4b52d2d8203abadbd5d399 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt @@ -325,6 +325,8 @@ class BufferViewConfigFragment : ServiceBoundFragment() { if (actionMode == null) { chatListToolbar.startActionMode(actionModeCallback) } - listAdapter.toggleSelection(it) + if (!listAdapter.toggleSelection(it)) { + actionMode?.finish() + } } } diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/FormattedMessage.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/FormattedMessage.kt deleted file mode 100644 index 8b3495d704333b43504483c1f48b289309a86901..0000000000000000000000000000000000000000 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/FormattedMessage.kt +++ /dev/null @@ -1,8 +0,0 @@ -package de.kuschku.quasseldroid.ui.chat.messages - -class FormattedMessage( - val id: Int, - val time: CharSequence, - val content: CharSequence, - val markerline: Boolean -) \ No newline at end of file diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageAdapter.kt index 53e801e5b8d62b7e5f09531fe9e0e02d5a929a98..e1b60e6b4421a188c07a3cb241a14b6675929a49 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageAdapter.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageAdapter.kt @@ -2,19 +2,32 @@ package de.kuschku.quasseldroid.ui.chat.messages import android.arch.paging.PagedListAdapter import android.support.v7.util.DiffUtil +import android.support.v7.widget.RecyclerView import android.util.LruCache import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup +import android.widget.TextView +import butterknife.BindView +import butterknife.ButterKnife import de.kuschku.libquassel.protocol.Message_Flag import de.kuschku.libquassel.protocol.Message_Flags import de.kuschku.libquassel.protocol.Message_Type import de.kuschku.libquassel.protocol.Message_Types import de.kuschku.libquassel.util.hasFlag +import de.kuschku.quasseldroid.R +import de.kuschku.quasseldroid.persistence.QuasselDatabase import de.kuschku.quasseldroid.util.helper.getOrPut +import de.kuschku.quasseldroid.util.helper.visibleIf +import de.kuschku.quasseldroid.viewmodel.data.FormattedMessage +import me.saket.bettermovementmethod.BetterLinkMovementMethod class MessageAdapter( - private val messageRenderer: MessageRenderer -) : PagedListAdapter<DisplayMessage, QuasselMessageViewHolder>( + private val messageRenderer: MessageRenderer, + private val clickListener: ((FormattedMessage) -> Unit)? = null, + private val selectionListener: ((FormattedMessage) -> Unit)? = null, + private val expansionListener: ((QuasselDatabase.DatabaseMessage) -> Unit)? = null +) : PagedListAdapter<DisplayMessage, MessageAdapter.QuasselMessageViewHolder>( object : DiffUtil.ItemCallback<DisplayMessage>() { override fun areItemsTheSame(oldItem: DisplayMessage, newItem: DisplayMessage) = oldItem.content.messageId == newItem.content.messageId @@ -35,7 +48,8 @@ class MessageAdapter( holder, messageCache.getOrPut(it.tag) { messageRenderer.render(holder.itemView.context, it) - } + }, + it.content ) } } @@ -68,7 +82,10 @@ class MessageAdapter( messageRenderer.layout(messageType, hasHighlight), parent, false - ) + ), + clickListener, + selectionListener, + expansionListener ) messageRenderer.init(viewHolder, messageType, hasHighlight) return viewHolder @@ -79,5 +96,58 @@ class MessageAdapter( } else { null } + + class QuasselMessageViewHolder( + itemView: View, + clickListener: ((FormattedMessage) -> Unit)? = null, + selectionListener: ((FormattedMessage) -> Unit)? = null, + expansionListener: ((QuasselDatabase.DatabaseMessage) -> Unit)? = null + ) : RecyclerView.ViewHolder(itemView) { + @BindView(R.id.time) + lateinit var time: TextView + + @BindView(R.id.content) + lateinit var content: TextView + + @BindView(R.id.markerline) + lateinit var markerline: View + + private var message: FormattedMessage? = null + + private val localClickListener = View.OnClickListener { + message?.let { + clickListener?.invoke(it) + } + } + + private val localLongClickListener = View.OnLongClickListener { + message?.let { + selectionListener?.invoke(it) + } + true + } + + init { + ButterKnife.bind(this, itemView) + + content.movementMethod = BetterLinkMovementMethod.getInstance() + + itemView.setOnClickListener(localClickListener) + content.setOnClickListener(localClickListener) + + itemView.setOnLongClickListener(localLongClickListener) + content.setOnLongClickListener(localLongClickListener) + } + + fun bind(message: FormattedMessage) { + this.message = message + + time.text = message.time + content.text = message.content + markerline.visibleIf(message.isMarkerLine) + + this.itemView.isSelected = message.isSelected + } + } } diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt index c7565fce76dfaaadd1577ef251b5be06e220183d..e6a55ceef714c35d98d5aa400963834639663570 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt @@ -4,13 +4,14 @@ import android.arch.lifecycle.Observer import android.arch.lifecycle.ViewModelProviders import android.arch.paging.LivePagedListBuilder import android.arch.paging.PagedList +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Intent import android.os.Bundle import android.support.design.widget.FloatingActionButton import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup +import android.view.* import butterknife.BindView import butterknife.ButterKnife import de.kuschku.libquassel.protocol.BufferId @@ -23,6 +24,7 @@ import de.kuschku.quasseldroid.settings.AppearanceSettings import de.kuschku.quasseldroid.settings.BacklogSettings import de.kuschku.quasseldroid.util.helper.* import de.kuschku.quasseldroid.util.service.ServiceBoundFragment +import de.kuschku.quasseldroid.util.ui.SpanFormatter import de.kuschku.quasseldroid.viewmodel.QuasselViewModel import javax.inject.Inject @@ -53,6 +55,69 @@ class MessageListFragment : ServiceBoundFragment() { private var lastBuffer: BufferId? = null private var previousMessageId: MsgId? = null + private var actionMode: ActionMode? = null + + private val actionModeCallback = object : ActionMode.Callback { + override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?) = when (item?.itemId) { + R.id.action_copy -> { + val data = viewModel.selectedMessages.value.values.sortedBy { + it.id + }.joinToString("\n") { + SpanFormatter.format( + getString(R.string.message_format_copy), + it.time, + it.content + ) + } + + val clipboard = requireActivity().systemService<ClipboardManager>() + val clip = ClipData.newPlainText(null, data) + clipboard.primaryClip = clip + actionMode?.finish() + true + } + R.id.action_share -> { + val data = viewModel.selectedMessages.value.values.sortedBy { + it.id + }.joinToString("\n") { + SpanFormatter.format( + getString(R.string.message_format_copy), + it.time, + it.content + ) + } + + val intent = Intent(Intent.ACTION_SEND) + intent.type = "text/plain" + intent.putExtra(Intent.EXTRA_TEXT, data) + requireContext().startActivity( + Intent.createChooser( + intent, + requireContext().getString(R.string.label_share) + ) + ) + actionMode?.finish() + true + } + else -> false + } + + override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean { + actionMode = mode + mode?.menuInflater?.inflate(R.menu.context_messages, menu) + return true + } + + override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean { + return false + } + + override fun onDestroyActionMode(mode: ActionMode?) { + actionMode = null + viewModel.selectedMessages.onNext(emptyMap()) + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewModel = ViewModelProviders.of(activity!!)[QuasselViewModel::class.java] @@ -75,7 +140,24 @@ class MessageListFragment : ServiceBoundFragment() { linearLayoutManager = LinearLayoutManager(context) linearLayoutManager.reverseLayout = true - adapter = MessageAdapter(messageRenderer) + adapter = MessageAdapter( + messageRenderer, + { msg -> + if (actionMode != null) { + if (!viewModel.selectedMessagesToggle(msg.id, msg)) { + actionMode?.finish() + } + } + }, + { msg -> + if (actionMode == null) { + activity?.startActionMode(actionModeCallback) + } + if (!viewModel.selectedMessagesToggle(msg.id, msg)) { + actionMode?.finish() + } + } + ) messageList.adapter = adapter messageList.layoutManager = linearLayoutManager messageList.itemAnimator = null diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageRenderer.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageRenderer.kt index 65179bc540a863e27eb5acf6fbbae1e7e39b533d..5ebf18cea402a93e81b93e3bcf73fc6aff86c479 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageRenderer.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageRenderer.kt @@ -3,15 +3,19 @@ package de.kuschku.quasseldroid.ui.chat.messages import android.content.Context import android.support.annotation.LayoutRes import de.kuschku.libquassel.protocol.Message_Type +import de.kuschku.quasseldroid.persistence.QuasselDatabase +import de.kuschku.quasseldroid.viewmodel.data.FormattedMessage interface MessageRenderer { @LayoutRes fun layout(type: Message_Type?, hasHighlight: Boolean): Int - fun bind(holder: QuasselMessageViewHolder, message: FormattedMessage) + fun bind(holder: MessageAdapter.QuasselMessageViewHolder, message: FormattedMessage, + original: QuasselDatabase.DatabaseMessage) + fun render(context: Context, message: DisplayMessage): FormattedMessage - fun init(viewHolder: QuasselMessageViewHolder, + fun init(viewHolder: MessageAdapter.QuasselMessageViewHolder, messageType: Message_Type?, hasHighlight: Boolean) { } diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageRenderer.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageRenderer.kt index 9716c95ab4fef30299d2c16067777974e9f635ba..5aff45b926bc88f70f1786acd95f323532df9e80 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageRenderer.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageRenderer.kt @@ -14,14 +14,15 @@ import de.kuschku.libquassel.protocol.Message_Flag import de.kuschku.libquassel.protocol.Message_Type import de.kuschku.libquassel.util.hasFlag import de.kuschku.quasseldroid.R +import de.kuschku.quasseldroid.persistence.QuasselDatabase import de.kuschku.quasseldroid.settings.AppearanceSettings import de.kuschku.quasseldroid.settings.AppearanceSettings.ColorizeNicknamesMode import de.kuschku.quasseldroid.settings.AppearanceSettings.ShowPrefixMode import de.kuschku.quasseldroid.util.helper.styledAttributes -import de.kuschku.quasseldroid.util.helper.visibleIf import de.kuschku.quasseldroid.util.irc.format.IrcFormatDeserializer import de.kuschku.quasseldroid.util.quassel.IrcUserUtils import de.kuschku.quasseldroid.util.ui.SpanFormatter +import de.kuschku.quasseldroid.viewmodel.data.FormattedMessage import org.intellij.lang.annotations.Language import org.threeten.bp.ZoneId import org.threeten.bp.format.DateTimeFormatter @@ -61,7 +62,7 @@ class QuasselMessageRenderer @Inject constructor( else -> R.layout.widget_chatmessage_placeholder } - override fun init(viewHolder: QuasselMessageViewHolder, + override fun init(viewHolder: MessageAdapter.QuasselMessageViewHolder, messageType: Message_Type?, hasHighlight: Boolean) { if (hasHighlight) { @@ -86,11 +87,8 @@ class QuasselMessageRenderer @Inject constructor( viewHolder.content.setTextSize(TypedValue.COMPLEX_UNIT_SP, textSize) } - override fun bind(holder: QuasselMessageViewHolder, message: FormattedMessage) { - holder.time.text = message.time - holder.content.text = message.content - holder.markerline.visibleIf(message.markerline) - } + override fun bind(holder: MessageAdapter.QuasselMessageViewHolder, message: FormattedMessage, + original: QuasselDatabase.DatabaseMessage) = holder.bind(message) override fun render(context: Context, message: DisplayMessage): FormattedMessage { @@ -117,7 +115,9 @@ class QuasselMessageRenderer @Inject constructor( formatNick(message.content.sender, self, highlight, false), formatContent(context, message.content.content, highlight) ), - message.isMarkerLine + isMarkerLine = message.isMarkerLine, + isExpanded = message.isExpanded, + isSelected = message.isSelected ) Message_Type.Action -> FormattedMessage( message.content.messageId, @@ -128,7 +128,9 @@ class QuasselMessageRenderer @Inject constructor( formatNick(message.content.sender, self, highlight, false), formatContent(context, message.content.content, highlight) ), - message.isMarkerLine + isMarkerLine = message.isMarkerLine, + isExpanded = message.isExpanded, + isSelected = message.isSelected ) Message_Type.Notice -> FormattedMessage( message.content.messageId, @@ -139,7 +141,9 @@ class QuasselMessageRenderer @Inject constructor( formatNick(message.content.sender, self, highlight, false), formatContent(context, message.content.content, highlight) ), - message.isMarkerLine + isMarkerLine = message.isMarkerLine, + isExpanded = message.isExpanded, + isSelected = message.isSelected ) Message_Type.Nick -> { val nickSelf = message.content.sender == message.content.content || self @@ -161,7 +165,9 @@ class QuasselMessageRenderer @Inject constructor( formatNick(message.content.content, nickSelf, highlight, false) ) }, - message.isMarkerLine + isMarkerLine = message.isMarkerLine, + isExpanded = message.isExpanded, + isSelected = message.isSelected ) } Message_Type.Mode -> FormattedMessage( @@ -173,7 +179,9 @@ class QuasselMessageRenderer @Inject constructor( formatPrefix(message.content.senderPrefixes, highlight), formatNick(message.content.sender, self, highlight, false) ), - message.isMarkerLine + isMarkerLine = message.isMarkerLine, + isExpanded = message.isExpanded, + isSelected = message.isSelected ) Message_Type.Join -> FormattedMessage( message.content.messageId, @@ -184,7 +192,9 @@ class QuasselMessageRenderer @Inject constructor( formatNick(message.content.sender, self, highlight, true), message.content.content ), - message.isMarkerLine + isMarkerLine = message.isMarkerLine, + isExpanded = message.isExpanded, + isSelected = message.isSelected ) Message_Type.Part -> FormattedMessage( message.content.messageId, @@ -203,7 +213,9 @@ class QuasselMessageRenderer @Inject constructor( formatContent(context, message.content.content, highlight) ) }, - message.isMarkerLine + isMarkerLine = message.isMarkerLine, + isExpanded = message.isExpanded, + isSelected = message.isSelected ) Message_Type.Quit -> FormattedMessage( message.content.messageId, @@ -222,7 +234,9 @@ class QuasselMessageRenderer @Inject constructor( formatContent(context, message.content.content, highlight) ) }, - message.isMarkerLine + isMarkerLine = message.isMarkerLine, + isExpanded = message.isExpanded, + isSelected = message.isSelected ) Message_Type.Kick -> { val (user, reason) = message.content.content.split(' ', limit = 2) + listOf("", "") @@ -245,7 +259,9 @@ class QuasselMessageRenderer @Inject constructor( formatContent(context, reason, highlight) ) }, - message.isMarkerLine + isMarkerLine = message.isMarkerLine, + isExpanded = message.isExpanded, + isSelected = message.isSelected ) } Message_Type.Kill -> { @@ -269,7 +285,9 @@ class QuasselMessageRenderer @Inject constructor( formatContent(context, reason, highlight) ) }, - message.isMarkerLine + isMarkerLine = message.isMarkerLine, + isExpanded = message.isExpanded, + isSelected = message.isSelected ) } Message_Type.NetsplitJoin -> { @@ -282,7 +300,9 @@ class QuasselMessageRenderer @Inject constructor( context.resources.getQuantityString( R.plurals.message_netsplit_join, usersAffected, server1, server2, usersAffected ), - message.isMarkerLine + isMarkerLine = message.isMarkerLine, + isExpanded = message.isExpanded, + isSelected = message.isSelected ) } Message_Type.NetsplitQuit -> { @@ -295,7 +315,9 @@ class QuasselMessageRenderer @Inject constructor( context.resources.getQuantityString( R.plurals.message_netsplit_quit, usersAffected, server1, server2, usersAffected ), - message.isMarkerLine + isMarkerLine = message.isMarkerLine, + isExpanded = message.isExpanded, + isSelected = message.isSelected ) } Message_Type.Server, @@ -304,13 +326,17 @@ class QuasselMessageRenderer @Inject constructor( message.content.messageId, timeFormatter.format(message.content.time.atZone(zoneId)), formatContent(context, message.content.content, highlight), - message.isMarkerLine + isMarkerLine = message.isMarkerLine, + isExpanded = message.isExpanded, + isSelected = message.isSelected ) Message_Type.Topic -> FormattedMessage( message.content.messageId, timeFormatter.format(message.content.time.atZone(zoneId)), formatContent(context, message.content.content, highlight), - message.isMarkerLine + isMarkerLine = message.isMarkerLine, + isExpanded = message.isExpanded, + isSelected = message.isSelected ) else -> FormattedMessage( message.content.messageId, @@ -322,7 +348,9 @@ class QuasselMessageRenderer @Inject constructor( formatNick(message.content.sender, self, highlight, true), message.content.content ), - message.isMarkerLine + isMarkerLine = message.isMarkerLine, + isExpanded = message.isExpanded, + isSelected = message.isSelected ) } } diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageViewHolder.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageViewHolder.kt deleted file mode 100644 index ec37dd3b596c1a244ab250e56ec61231dce4e233..0000000000000000000000000000000000000000 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageViewHolder.kt +++ /dev/null @@ -1,25 +0,0 @@ -package de.kuschku.quasseldroid.ui.chat.messages - -import android.support.v7.widget.RecyclerView -import android.text.method.LinkMovementMethod -import android.view.View -import android.widget.TextView -import butterknife.BindView -import butterknife.ButterKnife -import de.kuschku.quasseldroid.R - -class QuasselMessageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - @BindView(R.id.time) - lateinit var time: TextView - - @BindView(R.id.content) - lateinit var content: TextView - - @BindView(R.id.markerline) - lateinit var markerline: View - - init { - ButterKnife.bind(this, itemView) - content.movementMethod = LinkMovementMethod.getInstance() - } -} \ No newline at end of file diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/settings/crash/CrashAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/settings/crash/CrashAdapter.kt new file mode 100644 index 0000000000000000000000000000000000000000..e65d8d47c4dbbcc45bc88c5b52ff608a9cc917fd --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/settings/crash/CrashAdapter.kt @@ -0,0 +1,81 @@ +package de.kuschku.quasseldroid.ui.settings.crash + +import android.content.Intent +import android.support.v7.recyclerview.extensions.ListAdapter +import android.support.v7.util.DiffUtil +import android.support.v7.widget.RecyclerView +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import butterknife.BindView +import butterknife.ButterKnife +import de.kuschku.malheur.data.Report +import de.kuschku.quasseldroid.R +import org.threeten.bp.Instant +import org.threeten.bp.ZoneId +import org.threeten.bp.format.DateTimeFormatter + +class CrashAdapter : ListAdapter<Pair<Report, String>, CrashAdapter.CrashViewHolder>( + object : DiffUtil.ItemCallback<Pair<Report, String>>() { + override fun areItemsTheSame(oldItem: Pair<Report, String>?, newItem: Pair<Report, String>?) = + oldItem?.second == newItem?.second + + override fun areContentsTheSame(oldItem: Pair<Report, String>?, + newItem: Pair<Report, String>?) = + oldItem == newItem + } +) { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = CrashViewHolder( + LayoutInflater.from(parent.context).inflate(R.layout.widget_crash, parent, false) + ) + + override fun onBindViewHolder(holder: CrashViewHolder, position: Int) { + val (report, data) = getItem(position) + holder.bind(report, data) + } + + class CrashViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + @BindView(R.id.crash_time) + lateinit var crashTime: TextView + + @BindView(R.id.version_name) + lateinit var versionName: TextView + + @BindView(R.id.error) + lateinit var error: TextView + + var item: Report? = null + var data: String? = null + + private val dateTimeFormatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss") + + init { + ButterKnife.bind(this, itemView) + itemView.setOnClickListener { + data?.let { + val intent = Intent(Intent.ACTION_SEND) + intent.type = "application/json" + intent.putExtra(Intent.EXTRA_TEXT, it) + itemView.context.startActivity( + Intent.createChooser( + intent, + itemView.context.getString(R.string.label_share_crashreport) + ) + ) + } + } + } + + fun bind(item: Report, data: String) { + this.item = item + this.data = data + + this.crashTime.text = item.environment?.crashTime?.let { + dateTimeFormatter.format(Instant.ofEpochMilli(it).atZone(ZoneId.systemDefault())) + } + this.versionName.text = "${item.application?.versionName}" + this.error.text = "${item.crash?.exception?.lines()?.firstOrNull()}" + } + } +} \ No newline at end of file diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/settings/crash/CrashSettingsFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/settings/crash/CrashSettingsFragment.kt index 8b6435a233dabd84b53519b4e2c9f7cf38ffc7e9..341a591a84fd58e5813c1d6d8f0905a38f499bea 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/settings/crash/CrashSettingsFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/settings/crash/CrashSettingsFragment.kt @@ -1,18 +1,14 @@ package de.kuschku.quasseldroid.ui.settings.crash import android.content.Context -import android.content.Intent import android.os.Bundle import android.os.Handler import android.os.HandlerThread import android.support.v4.view.ViewCompat -import android.support.v7.recyclerview.extensions.ListAdapter -import android.support.v7.util.DiffUtil import android.support.v7.widget.DividerItemDecoration import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView import android.view.* -import android.widget.TextView import butterknife.BindView import butterknife.ButterKnife import com.google.gson.Gson @@ -21,9 +17,6 @@ import de.kuschku.malheur.data.Report import de.kuschku.quasseldroid.R import de.kuschku.quasseldroid.util.helper.fromJson import de.kuschku.quasseldroid.util.service.ServiceBoundFragment -import org.threeten.bp.Instant -import org.threeten.bp.ZoneId -import org.threeten.bp.format.DateTimeFormatter import java.io.File class CrashSettingsFragment : ServiceBoundFragment() { @@ -85,70 +78,6 @@ class CrashSettingsFragment : ServiceBoundFragment() { return view } - class CrashAdapter : ListAdapter<Pair<Report, String>, CrashAdapter.CrashViewHolder>( - object : DiffUtil.ItemCallback<Pair<Report, String>>() { - override fun areItemsTheSame(oldItem: Pair<Report, String>?, newItem: Pair<Report, String>?) = - oldItem?.second == newItem?.second - - override fun areContentsTheSame(oldItem: Pair<Report, String>?, - newItem: Pair<Report, String>?) = - oldItem == newItem - } - ) { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = CrashViewHolder( - LayoutInflater.from(parent.context).inflate(R.layout.widget_crash, parent, false) - ) - - override fun onBindViewHolder(holder: CrashViewHolder, position: Int) { - val (report, data) = getItem(position) - holder.bind(report, data) - } - - class CrashViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - @BindView(R.id.crash_time) - lateinit var crashTime: TextView - - @BindView(R.id.version_name) - lateinit var versionName: TextView - - @BindView(R.id.error) - lateinit var error: TextView - - var item: Report? = null - var data: String? = null - - private val dateTimeFormatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss") - - init { - ButterKnife.bind(this, itemView) - itemView.setOnClickListener { - data?.let { - val intent = Intent(Intent.ACTION_SEND) - intent.type = "application/json" - intent.putExtra(Intent.EXTRA_TEXT, it) - itemView.context.startActivity( - Intent.createChooser( - intent, - itemView.context.getString(R.string.label_share_crashreport) - ) - ) - } - } - } - - fun bind(item: Report, data: String) { - this.item = item - this.data = data - - this.crashTime.text = item.environment?.crashTime?.let { - dateTimeFormatter.format(Instant.ofEpochMilli(it).atZone(ZoneId.systemDefault())) - } - this.versionName.text = "${item.application?.versionName}" - this.error.text = "${item.crash?.exception?.lines()?.firstOrNull()}" - } - } - } - override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) { inflater?.inflate(R.menu.activity_crashes, menu) super.onCreateOptionsMenu(menu, inflater) diff --git a/app/src/main/res/layout/widget_chatmessage_action.xml b/app/src/main/res/layout/widget_chatmessage_action.xml index 7006f8f56e6968aeb9f06f92a2a5a4aeffbb5f2f..3cb90f5c9d8ccf8147788d8186cad22dd972daae 100644 --- a/app/src/main/res/layout/widget_chatmessage_action.xml +++ b/app/src/main/res/layout/widget_chatmessage_action.xml @@ -3,6 +3,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" + android:background="?attr/backgroundMenuItem" android:orientation="vertical" android:textAppearance="?android:attr/textAppearanceListItemSmall"> @@ -33,7 +34,6 @@ android:layout_height="wrap_content" android:layout_weight="1" android:textColor="?attr/colorForegroundAction" - android:textIsSelectable="true" android:textStyle="italic" tools:text="@sample/messages.json/data/message" /> </LinearLayout> diff --git a/app/src/main/res/layout/widget_chatmessage_error.xml b/app/src/main/res/layout/widget_chatmessage_error.xml index 462ec5cc8d930438ac90dd3d88c144bcf0e77d7b..d1f1e2ec32ba30ec8ea4b728d92fc8a45ecbc380 100644 --- a/app/src/main/res/layout/widget_chatmessage_error.xml +++ b/app/src/main/res/layout/widget_chatmessage_error.xml @@ -3,6 +3,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" + android:background="?attr/backgroundMenuItem" android:orientation="vertical" android:textAppearance="?android:attr/textAppearanceListItemSmall"> @@ -33,7 +34,6 @@ android:layout_height="wrap_content" android:layout_weight="1" android:textColor="?attr/colorForegroundError" - android:textIsSelectable="true" tools:text="@sample/messages.json/data/message" /> </LinearLayout> diff --git a/app/src/main/res/layout/widget_chatmessage_info.xml b/app/src/main/res/layout/widget_chatmessage_info.xml index 87de55c2f0bf7080febacd656f2a06997a9d27cd..42182a387149d51ec26adf47923771e1a9f0c315 100644 --- a/app/src/main/res/layout/widget_chatmessage_info.xml +++ b/app/src/main/res/layout/widget_chatmessage_info.xml @@ -3,6 +3,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" + android:background="?attr/backgroundMenuItem" android:orientation="vertical" android:textAppearance="?android:attr/textAppearanceListItemSmall"> @@ -33,7 +34,6 @@ android:layout_height="wrap_content" android:layout_weight="1" android:textColor="?attr/colorForegroundSecondary" - android:textIsSelectable="true" android:textStyle="italic" tools:text="@sample/messages.json/data/message" /> </LinearLayout> diff --git a/app/src/main/res/layout/widget_chatmessage_notice.xml b/app/src/main/res/layout/widget_chatmessage_notice.xml index f17215680930826b6b2bd97dfb4476ea7cacfe2e..87979a516fe0c2d64afa88abe2a172f02ff820c7 100644 --- a/app/src/main/res/layout/widget_chatmessage_notice.xml +++ b/app/src/main/res/layout/widget_chatmessage_notice.xml @@ -3,6 +3,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" + android:background="?attr/backgroundMenuItem" android:orientation="vertical" android:textAppearance="?android:attr/textAppearanceListItemSmall"> @@ -33,7 +34,6 @@ android:layout_height="wrap_content" android:layout_weight="1" android:textColor="?attr/colorForegroundNotice" - android:textIsSelectable="true" tools:text="@sample/messages.json/data/message" /> </LinearLayout> diff --git a/app/src/main/res/layout/widget_chatmessage_plain.xml b/app/src/main/res/layout/widget_chatmessage_plain.xml index 4b321468116795fb942f2a004ef024ca2ccca5d9..451a8535e3407b8fddd2ff6582743f7c89fdb107 100644 --- a/app/src/main/res/layout/widget_chatmessage_plain.xml +++ b/app/src/main/res/layout/widget_chatmessage_plain.xml @@ -3,6 +3,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" + android:background="?attr/backgroundMenuItem" android:orientation="vertical" android:textAppearance="?android:attr/textAppearanceListItemSmall"> @@ -33,7 +34,6 @@ android:layout_height="wrap_content" android:layout_weight="1" android:textColor="?attr/colorForeground" - android:textIsSelectable="true" tools:text="@sample/messages.json/data/message" /> </LinearLayout> diff --git a/app/src/main/res/layout/widget_chatmessage_server.xml b/app/src/main/res/layout/widget_chatmessage_server.xml index 3a63850b6c5c3184506234cef6f8f16f7fcecf55..7ab995cb83252a7786460729ad325ae8c9d91b5e 100644 --- a/app/src/main/res/layout/widget_chatmessage_server.xml +++ b/app/src/main/res/layout/widget_chatmessage_server.xml @@ -3,6 +3,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" + android:background="?attr/backgroundMenuItem" android:orientation="vertical" android:textAppearance="?android:attr/textAppearanceListItemSmall"> @@ -33,7 +34,6 @@ android:layout_height="wrap_content" android:layout_weight="1" android:textColor="?attr/colorForegroundSecondary" - android:textIsSelectable="true" tools:text="@sample/messages.json/data/message" /> </LinearLayout> diff --git a/app/src/main/res/menu/context_messages.xml b/app/src/main/res/menu/context_messages.xml new file mode 100644 index 0000000000000000000000000000000000000000..937ca54b1aa02d2e2318cc3fe43a94d40ae5479a --- /dev/null +++ b/app/src/main/res/menu/context_messages.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:id="@+id/action_copy" + android:title="@string/label_copy" /> + <item + android:id="@+id/action_share" + android:title="@string/label_share" /> +</menu> \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 921550714cc807620a4dbf07df17f4957192fd1f..05a6f9bc48bab60f69b66da1f26344671dc6ad8e 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -13,6 +13,7 @@ <string name="label_close">Schließen</string> <string name="label_connect">Verbinden</string> <string name="label_contributors">Mitwirkende</string> + <string name="label_copy">Kopieren</string> <string name="label_crashes">Absturzberichte</string> <string name="label_delete">Löschen</string> <string name="label_delete_all">Alle Löschen</string> @@ -33,6 +34,7 @@ <string name="label_save">Speichern</string> <string name="label_select_multiple">Auswählen</string> <string name="label_settings">Einstellungen</string> + <string name="label_share">Teilen</string> <string name="label_share_crashreport">Absturzbericht Teilen</string> <string name="label_show_hidden">Alle anzeigen</string> <string name="label_unhide">Nicht mehr ausblenden</string> diff --git a/app/src/main/res/values-de/strings_messages.xml b/app/src/main/res/values-de/strings_messages.xml index 1d781ce461e3913587da2e154abe1102f8b3e364..aa54ae050c94fb9fdb664f4a36198563c7676cc5 100644 --- a/app/src/main/res/values-de/strings_messages.xml +++ b/app/src/main/res/values-de/strings_messages.xml @@ -6,6 +6,9 @@ <string name="message_type_nick">Benutzernamensänderungen</string> <string name="message_type_mode">Modiänderungen</string> <string name="message_type_topic">Themenänderungen</string> + + <string name="message_format_copy">[%1$s] %2$s</string> + <string name="message_format_plain">%1$s%2$s: %3$s</string> <string name="message_format_action">* %1$s%2$s %3$s</string> <string name="message_format_notice">[%1$s%2$s] %3$s</string> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ff29dc0a000fb450af3b1338110f42b11d9777ad..a25b5e0715b3aba4e2bb2a1a9bda72fe089eabe2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -13,6 +13,7 @@ <string name="label_close">Close</string> <string name="label_connect">Connect</string> <string name="label_contributors">Contributors</string> + <string name="label_copy">Copy</string> <string name="label_crashes">Crashes</string> <string name="label_delete">Delete</string> <string name="label_delete_all">Delete All</string> @@ -33,6 +34,7 @@ <string name="label_save">Save</string> <string name="label_select_multiple">Select</string> <string name="label_settings">Settings</string> + <string name="label_share">Share</string> <string name="label_share_crashreport">Share Crashreport</string> <string name="label_show_hidden">Show Hidden</string> <string name="label_unhide">Make Visible</string> diff --git a/app/src/main/res/values/strings_messages.xml b/app/src/main/res/values/strings_messages.xml index 802cb0a7156cd59954798ffb8535d9a6665c1ac6..e32ff0ed4d93480ef1692cd205dbe83e4866e6fa 100644 --- a/app/src/main/res/values/strings_messages.xml +++ b/app/src/main/res/values/strings_messages.xml @@ -15,6 +15,8 @@ <item>@string/message_type_topic</item> </string-array> + <string name="message_format_copy">[%1$s] %2$s</string> + <string name="message_format_plain">%1$s%2$s: %3$s</string> <string name="message_format_action">* %1$s%2$s %3$s</string> <string name="message_format_notice">[%1$s%2$s] %3$s</string> diff --git a/viewmodel/build.gradle.kts b/viewmodel/build.gradle.kts index 28548b70aa1e2433a102cac5d4e339714f67fbc1..9f832911d0f4e1e87659e086b2e03d60c7aa5eb2 100644 --- a/viewmodel/build.gradle.kts +++ b/viewmodel/build.gradle.kts @@ -37,6 +37,7 @@ dependencies { implementation("org.jetbrains", "annotations", "16.0.1") // Quassel + implementation(project(":persistence")) implementation(project(":lib")) { exclude(group = "org.threeten", module = "threetenbp") } diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt index f7088caf186775f06ccad71fc50450e21ba439a7..0667ecb3c9bb357793a85c334c9853994a4a28b2 100644 --- a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt +++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt @@ -28,7 +28,14 @@ import java.util.concurrent.TimeUnit class QuasselViewModel : ViewModel() { val backendWrapper = BehaviorSubject.createDefault(Observable.empty<Optional<Backend>>()) - val selectedMessages = BehaviorSubject.createDefault(emptyList<MsgId>()) + val selectedMessages = BehaviorSubject.createDefault(emptyMap<MsgId, FormattedMessage>()) + fun selectedMessagesToggle(key: MsgId, value: FormattedMessage): Boolean { + val set = selectedMessages.value.orEmpty() + val result = if (set.containsKey(key)) set - key else set + Pair(key, value) + selectedMessages.onNext(result) + return result.isNotEmpty() + } + val expandedMessages = BehaviorSubject.createDefault(emptyList<MsgId>()) val buffer = BehaviorSubject.createDefault(-1) diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/FormattedMessage.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/FormattedMessage.kt new file mode 100644 index 0000000000000000000000000000000000000000..27f674a99e2a3010de2a15cbe45d5393a2444adc --- /dev/null +++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/FormattedMessage.kt @@ -0,0 +1,10 @@ +package de.kuschku.quasseldroid.viewmodel.data + +class FormattedMessage( + val id: Int, + val time: CharSequence, + val content: CharSequence, + val isSelected: Boolean, + val isExpanded: Boolean, + val isMarkerLine: Boolean +) \ No newline at end of file