From f6680b543e9bd558d3f9b3931e4623491fcabc72 Mon Sep 17 00:00:00 2001 From: Janne Koschinski <janne@kuschku.de> Date: Sun, 10 Dec 2017 05:00:31 +0100 Subject: [PATCH] First implementation of chat message renderer --- app/build.gradle.kts | 42 +++--- app/src/main/assets/org/threeten/bp/TZDB.dat | Bin 0 -> 100361 bytes .../kuschku/quasseldroid_ng/QuasseldroidNG.kt | 11 +- .../persistence/QuasselDatabase.kt | 9 ++ .../ui/chat/BufferViewConfigFragment.kt | 2 +- .../quasseldroid_ng/ui/chat/ChatActivity.kt | 3 +- .../quasseldroid_ng/ui/chat/MessageAdapter.kt | 36 +++++ .../ui/chat/MessageListFragment.kt | 38 +---- .../ui/chat/MessageRenderer.kt | 13 ++ .../ui/chat/QuasselMessageRenderer.kt | 74 +++++++++ .../ui/chat/QuasselMessageViewHolder.kt | 22 +++ .../quasseldroid_ng/ui/setup/SetupActivity.kt | 4 +- .../accounts/AccountSelectionActivity.kt | 4 +- .../setup/accounts/AccountSelectionSlide.kt | 2 +- .../util/backport/AndroidThreeTenBackport.kt | 38 +++++ .../quasseldroid_ng/util/quassel/CRCUtils.kt | 44 ++++++ .../util/quassel/IrcUserUtils.kt | 53 +++++++ .../util/service/BackendServiceConnection.kt | 52 +++++++ .../util/service/ServiceBoundActivity.kt | 39 ++--- .../util/service/ServiceBoundFragment.kt | 36 +---- .../util/ui/SpanFormatter.java | 140 ++++++++++++++++++ .../res/layout/widget_chatmessage_action.xml | 54 +++++++ .../res/layout/widget_chatmessage_error.xml | 35 +++++ .../res/layout/widget_chatmessage_plain.xml | 33 +++++ .../res/layout/widget_chatmessage_server.xml | 35 +++++ app/src/main/res/values/dimens.xml | 5 +- build.gradle.kts | 4 +- lib/build.gradle.kts | 4 +- malheur/build.gradle.kts | 10 +- 29 files changed, 712 insertions(+), 130 deletions(-) create mode 100644 app/src/main/assets/org/threeten/bp/TZDB.dat create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageAdapter.kt create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageRenderer.kt create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/QuasselMessageRenderer.kt create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/QuasselMessageViewHolder.kt create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/util/backport/AndroidThreeTenBackport.kt create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/util/quassel/CRCUtils.kt create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/util/quassel/IrcUserUtils.kt create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/util/service/BackendServiceConnection.kt create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/util/ui/SpanFormatter.java create mode 100644 app/src/main/res/layout/widget_chatmessage_action.xml create mode 100644 app/src/main/res/layout/widget_chatmessage_error.xml create mode 100644 app/src/main/res/layout/widget_chatmessage_plain.xml create mode 100644 app/src/main/res/layout/widget_chatmessage_server.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index bce3a7a92..c3df58fe4 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -16,7 +16,7 @@ plugins { android { compileSdkVersion(26) - buildToolsVersion("26.0.2") + buildToolsVersion("27.0.2") signingConfigs { val signing = project.rootProject.properties("signing.properties") @@ -88,21 +88,21 @@ android { } } -val appCompatVersion = "26.1.0" -val appArchVersion = "1.0.0-rc1" +val appCompatVersion = "27.0.2" +val appArchVersion = "1.0.0" dependencies { - implementation(kotlin("stdlib", "1.1.61")) + implementation(kotlin("stdlib", "1.2.0")) implementation(appCompat("appcompat-v7")) implementation(appCompat("design")) implementation(appCompat("customtabs")) implementation(appCompat("cardview-v7")) implementation(appCompat("recyclerview-v7")) - implementation("com.android.support.constraint:constraint-layout:1.0.2") + implementation(appCompat("constraint", "constraint-layout", version = "1.0.2")) - implementation("com.github.StephenVinouze.AdvancedRecyclerView:core:1.1.6") + implementation("com.github.StephenVinouze.AdvancedRecyclerView", "core", "1.1.6") - implementation("io.reactivex.rxjava2:rxjava:2.1.3") + implementation("io.reactivex.rxjava2", "rxjava", "2.1.3") implementation(appArch("lifecycle", "extensions")) implementation(appArch("lifecycle", "reactivestreams")) @@ -115,21 +115,25 @@ dependencies { exclude(group = "junit", module = "junit") } - implementation("org.threeten:threetenbp:1.3.6") + implementation("org.threeten", "threetenbp", "1.3.6", classifier = "no-tzdb") - implementation("com.jakewharton:butterknife:8.8.1") - kapt("com.jakewharton:butterknife-compiler:8.8.1") + implementation("com.jakewharton", "butterknife", "8.8.1") + kapt("com.jakewharton", "butterknife-compiler", "8.8.1") - implementation(project(":lib")) + implementation(project(":lib")) { + exclude(group = "org.threeten", module = "threetenbp") + } implementation(project(":malheur")) + debugImplementation("com.squareup.leakcanary", "leakcanary-android", "1.5.1") + testImplementation(appArch("persistence.room", "testing")) - testImplementation("junit:junit:4.12") + testImplementation("junit", "junit", "4.12") - androidTestImplementation("com.android.support.test:runner:1.0.1") - androidTestImplementation("com.android.support.test:rules:1.0.1") + androidTestImplementation("com.android.support.test", "runner", "1.0.1") + androidTestImplementation("com.android.support.test", "rules", "1.0.1") - androidTestImplementation("com.android.support.test.espresso:espresso-core:3.0.1") + androidTestImplementation("com.android.support.test.espresso", "espresso-core", "3.0.1") } tasks.withType(KotlinCompile::class.java) { @@ -168,8 +172,12 @@ fun Project.properties(fileName: String): Properties? { * @param module simple name of the AppCompat module, for example "cardview-v7". * @param version optional desired version, null implies [appCompatVersion]. */ -fun appCompat(module: String, version: String? = null) - = "com.android.support:$module:${version ?: appCompatVersion}" +fun appCompat(module: String, submodule: String? = null, version: String? = null) + = if (submodule != null) { + "com.android.support.$module:$submodule:${version ?: appCompatVersion}" +} else { + "com.android.support:$module:${version ?: appCompatVersion}" +} /** * Builds the dependency notation for the named AppArch [module] at the given [version]. diff --git a/app/src/main/assets/org/threeten/bp/TZDB.dat b/app/src/main/assets/org/threeten/bp/TZDB.dat new file mode 100644 index 0000000000000000000000000000000000000000..5677059dc8b992b60ae88f75a82f14e80a1cbf70 GIT binary patch literal 100361 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 B0fPVl literal 0 HcmV?d00001 diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/QuasseldroidNG.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/QuasseldroidNG.kt index f03aecc1f..6395cd025 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/QuasseldroidNG.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/QuasseldroidNG.kt @@ -5,7 +5,9 @@ import android.content.pm.ShortcutInfo import android.content.pm.ShortcutManager import android.graphics.drawable.Icon import android.os.Build +import com.squareup.leakcanary.LeakCanary import de.kuschku.malheur.CrashHandler +import de.kuschku.quasseldroid_ng.util.backport.AndroidThreeTenBackport import de.kuschku.quasseldroid_ng.util.compatibility.AndroidCompatibilityUtils import de.kuschku.quasseldroid_ng.util.compatibility.AndroidLoggingHandler import de.kuschku.quasseldroid_ng.util.compatibility.AndroidStreamChannelFactory @@ -13,7 +15,12 @@ import de.kuschku.quasseldroid_ng.util.helper.systemService class QuasseldroidNG : Application() { override fun onCreate() { - println("QuasseldroidNG::onCreate") + if (LeakCanary.isInAnalyzerProcess(this)) { + // This process is dedicated to LeakCanary for heap analysis. + // You should not init your app in this process. + return + } + LeakCanary.install(this) CrashHandler.init( application = this, @@ -26,6 +33,8 @@ class QuasseldroidNG : Application() { AndroidLoggingHandler.inject() AndroidStreamChannelFactory.inject() + AndroidThreeTenBackport.init(this) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { systemService<ShortcutManager>().dynamicShortcuts = listOf( ShortcutInfo.Builder(this, "id1") diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselDatabase.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselDatabase.kt index 89d9c7a8e..205530597 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselDatabase.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselDatabase.kt @@ -4,6 +4,7 @@ import android.arch.paging.LivePagedListProvider import android.arch.persistence.room.* import android.content.Context import android.support.annotation.IntRange +import android.support.v7.recyclerview.extensions.DiffCallback import de.kuschku.libquassel.protocol.Message_Flag import de.kuschku.libquassel.protocol.Message_Type import org.threeten.bp.Instant @@ -37,6 +38,14 @@ abstract class QuasselDatabase : RoomDatabase() { type)}, flag=${Message_Flag.of( flag)}, bufferId=$bufferId, sender='$sender', senderPrefixes='$senderPrefixes', content='$content')" } + + object MessageDiffCallback : DiffCallback<DatabaseMessage>() { + override fun areContentsTheSame(oldItem: QuasselDatabase.DatabaseMessage, newItem: QuasselDatabase.DatabaseMessage) + = oldItem == newItem + + override fun areItemsTheSame(oldItem: QuasselDatabase.DatabaseMessage, newItem: QuasselDatabase.DatabaseMessage) + = oldItem.messageId == newItem.messageId + } } @Dao diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferViewConfigFragment.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferViewConfigFragment.kt index bcb8e99ed..5867f9f74 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferViewConfigFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferViewConfigFragment.kt @@ -92,7 +92,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() { chatListSpinner.adapter = adapter chatListSpinner.onItemSelectedListener = itemSelectedListener - chatList.adapter = BufferListAdapter(this, bufferList, handlerThread::post, activity::runOnUiThread, clickListener) + chatList.adapter = BufferListAdapter(this, bufferList, handlerThread::post, activity!!::runOnUiThread, clickListener) chatList.layoutManager = LinearLayoutManager(context) chatList.itemAnimator = DefaultItemAnimator() return view diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt index 17638610c..9b40316e0 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt @@ -62,7 +62,6 @@ class ChatActivity : ServiceBoundActivity() { private lateinit var database: QuasselDatabase override fun onCreate(savedInstanceState: Bundle?) { - println("ChatActivity::onCreate") handler.onCreate() super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) @@ -128,7 +127,7 @@ class ChatActivity : ServiceBoundActivity() { val status = it ?: ConnectionState.DISCONNECTED snackbar?.dismiss() - snackbar = Snackbar.make(window.decorView.rootView, status.name, Snackbar.LENGTH_SHORT) + snackbar = Snackbar.make(findViewById(R.id.contentMessages), status.name, Snackbar.LENGTH_SHORT) snackbar?.show() }) } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageAdapter.kt new file mode 100644 index 000000000..7422ad507 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageAdapter.kt @@ -0,0 +1,36 @@ +package de.kuschku.quasseldroid_ng.ui.chat + +import android.arch.paging.PagedListAdapter +import android.content.Context +import android.view.LayoutInflater +import android.view.ViewGroup +import de.kuschku.libquassel.protocol.Message_Flags +import de.kuschku.libquassel.protocol.Message_Type +import de.kuschku.quasseldroid_ng.persistence.QuasselDatabase + +class MessageAdapter(context: Context) : PagedListAdapter<QuasselDatabase.DatabaseMessage, QuasselMessageViewHolder>(QuasselDatabase.DatabaseMessage.MessageDiffCallback) { + val messageRenderer: MessageRenderer = QuasselMessageRenderer(context) + + override fun onBindViewHolder(holder: QuasselMessageViewHolder, position: Int) { + getItem(position)?.let { messageRenderer.bind(holder, it) } + } + + override fun getItemViewType(position: Int): Int { + return getItem(position)?.type ?: 0 + } + + private fun messageType(viewType: Int): Message_Type? + = Message_Type.of(viewType).enabledValues().firstOrNull() + + private fun messageFlags(viewType: Int): Message_Flags + = Message_Flags.of() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QuasselMessageViewHolder { + return QuasselMessageViewHolder(LayoutInflater.from(parent.context).inflate( + messageRenderer.layout(messageType(viewType), messageFlags(viewType)), + parent, + false + )) + } +} + diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageListFragment.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageListFragment.kt index 3e98bb186..7007b536f 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageListFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageListFragment.kt @@ -4,16 +4,13 @@ import android.arch.lifecycle.LiveData import android.arch.lifecycle.MutableLiveData import android.arch.lifecycle.Observer import android.arch.paging.PagedList -import android.arch.paging.PagedListAdapter import android.os.Bundle -import android.support.v7.recyclerview.extensions.DiffCallback import android.support.v7.widget.DefaultItemAnimator 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.widget.TextView import butterknife.BindView import butterknife.ButterKnife import de.kuschku.libquassel.protocol.BufferId @@ -27,8 +24,6 @@ class MessageListFragment : ServiceBoundFragment() { private lateinit var database: QuasselDatabase - private val adapter = MessageAdapter() - @BindView(R.id.messageList) lateinit var messageList: RecyclerView @@ -36,7 +31,7 @@ class MessageListFragment : ServiceBoundFragment() { val view = inflater.inflate(R.layout.content_messages, container, false) ButterKnife.bind(this, view) - database = QuasselDatabase.Creator.init(context.applicationContext) + database = QuasselDatabase.Creator.init(context!!.applicationContext) val data = currentBuffer.switchMap { it.switchMap { database.message().findByBufferIdPaged(it).create(Int.MAX_VALUE, @@ -49,6 +44,8 @@ class MessageListFragment : ServiceBoundFragment() { } } + val adapter = MessageAdapter(context!!) + data.observe(this, Observer { list -> adapter.setList(list) }) @@ -59,33 +56,4 @@ class MessageListFragment : ServiceBoundFragment() { return view } -} - -class MessageAdapter : PagedListAdapter<QuasselDatabase.DatabaseMessage, MessageViewHolder>(MessageDiffCallback) { - override fun onBindViewHolder(holder: MessageViewHolder?, position: Int) { - holder?.bind(getItem(position)) - } - - override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): MessageViewHolder { - return MessageViewHolder(LayoutInflater.from(parent?.context).inflate(android.R.layout.simple_list_item_1, parent, false)) - } -} - -object MessageDiffCallback : DiffCallback<QuasselDatabase.DatabaseMessage>() { - override fun areContentsTheSame(oldItem: QuasselDatabase.DatabaseMessage, newItem: QuasselDatabase.DatabaseMessage) - = oldItem == newItem - - override fun areItemsTheSame(oldItem: QuasselDatabase.DatabaseMessage, newItem: QuasselDatabase.DatabaseMessage) - = oldItem.messageId == newItem.messageId -} - -class MessageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - fun bind(message: QuasselDatabase.DatabaseMessage?) { - val text = (itemView as TextView) - if (message == null) { - text.text = "null" - } else { - text.text = "[${message.time}] <${message.senderPrefixes}${message.sender}> ${message.content}" - } - } } \ No newline at end of file diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageRenderer.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageRenderer.kt new file mode 100644 index 000000000..4ca12c9fa --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageRenderer.kt @@ -0,0 +1,13 @@ +package de.kuschku.quasseldroid_ng.ui.chat + +import android.support.annotation.LayoutRes +import de.kuschku.libquassel.protocol.Message_Flags +import de.kuschku.libquassel.protocol.Message_Type +import de.kuschku.quasseldroid_ng.persistence.QuasselDatabase + +interface MessageRenderer { + @LayoutRes + fun layout(type: Message_Type?, flags: Message_Flags): Int + + fun bind(holder: QuasselMessageViewHolder, message: QuasselDatabase.DatabaseMessage) +} \ No newline at end of file diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/QuasselMessageRenderer.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/QuasselMessageRenderer.kt new file mode 100644 index 000000000..751e67e35 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/QuasselMessageRenderer.kt @@ -0,0 +1,74 @@ +package de.kuschku.quasseldroid_ng.ui.chat + +import android.content.Context +import android.graphics.Typeface +import android.text.SpannableString +import android.text.format.DateFormat +import android.text.style.ForegroundColorSpan +import android.text.style.StyleSpan +import de.kuschku.libquassel.protocol.Message.MessageType.* +import de.kuschku.libquassel.protocol.Message_Flags +import de.kuschku.libquassel.protocol.Message_Type +import de.kuschku.quasseldroid_ng.R +import de.kuschku.quasseldroid_ng.persistence.QuasselDatabase +import de.kuschku.quasseldroid_ng.util.quassel.IrcUserUtils +import de.kuschku.quasseldroid_ng.util.ui.SpanFormatter +import org.threeten.bp.ZoneId +import org.threeten.bp.format.DateTimeFormatter +import java.text.SimpleDateFormat + +class QuasselMessageRenderer(context: Context) : MessageRenderer { + val timeFormatter = DateTimeFormatter.ofPattern((DateFormat.getTimeFormat(context) as SimpleDateFormat).toLocalizedPattern()) + val senderColors: IntArray + + init { + val typedArray = context.obtainStyledAttributes(intArrayOf( + R.attr.senderColor0, R.attr.senderColor1, R.attr.senderColor2, R.attr.senderColor3, + R.attr.senderColor4, R.attr.senderColor5, R.attr.senderColor6, R.attr.senderColor7, + R.attr.senderColor8, R.attr.senderColor9, R.attr.senderColorA, R.attr.senderColorB, + R.attr.senderColorC, R.attr.senderColorD, R.attr.senderColorE, R.attr.senderColorF + )) + senderColors = IntArray(16) { + typedArray.getColor(it, 0) + } + typedArray.recycle() + } + + override fun layout(type: Message_Type?, flags: Message_Flags) + = when (type) { + Nick, Notice, Mode, Join, Part, Quit, Kick, Kill, Server, Info, DayChange, Topic, NetsplitJoin, + NetsplitQuit, Invite -> R.layout.widget_chatmessage_server + Error -> R.layout.widget_chatmessage_error + Action -> R.layout.widget_chatmessage_action + Plain -> R.layout.widget_chatmessage_plain + else -> R.layout.widget_chatmessage_plain + } + + override fun bind(holder: QuasselMessageViewHolder, message: QuasselDatabase.DatabaseMessage) { + holder.time.text = timeFormatter.format(message.time.atZone(ZoneId.systemDefault())) + holder.content.text = SpanFormatter.format( + "%s: %s", + formatNick(message.sender), + message.content + ) + } + + private fun formatNick(sender: String): CharSequence { + val nick = IrcUserUtils.nick(sender) + val senderColor = IrcUserUtils.senderColor(nick) + val spannableString = SpannableString(nick) + spannableString.setSpan( + ForegroundColorSpan(senderColors[senderColor % senderColors.size]), + 0, + nick.length, + SpannableString.SPAN_INCLUSIVE_EXCLUSIVE + ) + spannableString.setSpan( + StyleSpan(Typeface.BOLD), + 0, + nick.length, + SpannableString.SPAN_INCLUSIVE_EXCLUSIVE + ) + return spannableString + } +} \ No newline at end of file diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/QuasselMessageViewHolder.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/QuasselMessageViewHolder.kt new file mode 100644 index 000000000..cf54d42c9 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/QuasselMessageViewHolder.kt @@ -0,0 +1,22 @@ +package de.kuschku.quasseldroid_ng.ui.chat + +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_ng.R + +class QuasselMessageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + @BindView(R.id.time) + lateinit var time: TextView + + @BindView(R.id.content) + lateinit var content: TextView + + 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_ng/ui/setup/SetupActivity.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/SetupActivity.kt index 5465c5995..9a943d1eb 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/SetupActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/SetupActivity.kt @@ -160,13 +160,13 @@ abstract class SetupActivity : AppCompatActivity() { list.add(fragment) } - override fun instantiateItem(container: ViewGroup?, position: Int): Any { + override fun instantiateItem(container: ViewGroup, position: Int): Any { val fragment = super.instantiateItem(container, position) storeNewFragment(position, fragment as SlideFragment) return fragment } - override fun destroyItem(container: ViewGroup?, position: Int, `object`: Any?) { + override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) { retainedFragments.get(position)?.getData(result) retainedFragments.remove(position) super.destroyItem(container, position, `object`) diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionActivity.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionActivity.kt index ec5e12e1c..cb6891877 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionActivity.kt @@ -8,7 +8,7 @@ import android.os.Bundle import de.kuschku.quasseldroid_ng.Keys import de.kuschku.quasseldroid_ng.ui.chat.ChatActivity import de.kuschku.quasseldroid_ng.ui.setup.SetupActivity -import de.kuschku.quasseldroid_ng.util.helper.editCommit +import de.kuschku.quasseldroid_ng.util.helper.editApply class AccountSelectionActivity : SetupActivity() { companion object { @@ -23,7 +23,7 @@ class AccountSelectionActivity : SetupActivity() { private lateinit var statusPreferences: SharedPreferences override fun onDone(data: Bundle) { - statusPreferences.editCommit { + statusPreferences.editApply { putLong(Keys.Status.selectedAccount, data.getLong(Keys.Status.selectedAccount, -1)) putBoolean(Keys.Status.reconnect, true) } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionSlide.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionSlide.kt index a0198fb1a..86b305133 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionSlide.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionSlide.kt @@ -76,7 +76,7 @@ class AccountSelectionSlide : SlideFragment() { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_CREATE_FIRST && resultCode == Activity.RESULT_CANCELED) { - activity.finish() + activity?.finish() } } } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/backport/AndroidThreeTenBackport.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/backport/AndroidThreeTenBackport.kt new file mode 100644 index 000000000..04019a0f0 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/backport/AndroidThreeTenBackport.kt @@ -0,0 +1,38 @@ +package de.kuschku.quasseldroid_ng.util.backport + +import android.content.Context +import org.threeten.bp.zone.TzdbZoneRulesProvider +import org.threeten.bp.zone.ZoneRulesProvider +import java.io.IOException +import java.io.InputStream +import java.util.concurrent.atomic.AtomicBoolean + + +object AndroidThreeTenBackport { + private val initialized = AtomicBoolean() + + fun init(context: Context) { + if (initialized.getAndSet(true)) { + return + } + + val provider: TzdbZoneRulesProvider + var inputStream: InputStream? = null + try { + inputStream = context.assets.open("org/threeten/bp/TZDB.dat") + provider = TzdbZoneRulesProvider(inputStream) + } catch (e: IOException) { + throw IllegalStateException("TZDB.dat missing from assets.", e) + } finally { + if (inputStream != null) { + try { + inputStream.close() + } catch (ignored: IOException) { + } + + } + } + + ZoneRulesProvider.registerProvider(provider) + } +} \ No newline at end of file diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/quassel/CRCUtils.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/quassel/CRCUtils.kt new file mode 100644 index 000000000..9a734a320 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/quassel/CRCUtils.kt @@ -0,0 +1,44 @@ +package de.kuschku.quasseldroid_ng.util.quassel + +object CRCUtils { + fun qChecksum(data: ByteArray): Int { + var crc = 0xffff + val crcHighBitMask = 0x8000 + + for (b in data) { + val c = reflect(b.toInt(), 8) + var j = 0x80 + while (j > 0) { + var highBit = crc and crcHighBitMask + crc = crc shl 1 + if (c and j > 0) { + highBit = highBit xor crcHighBitMask + } + if (highBit > 0) { + crc = crc xor 0x1021 + } + j = j shr 1 + } + } + + crc = reflect(crc, 16) + crc = crc xor 0xffff + crc = crc and 0xffff + + return crc + } + + private fun reflect(crc: Int, n: Int): Int { + var j = 1 + var crcout = 0 + var i = 1 shl n - 1 + while (i > 0) { + if (crc and i > 0) { + crcout = crcout or j + } + j = j shl 1 + i = i shr 1 + } + return crcout + } +} \ No newline at end of file diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/quassel/IrcUserUtils.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/quassel/IrcUserUtils.kt new file mode 100644 index 000000000..f9a348d9b --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/quassel/IrcUserUtils.kt @@ -0,0 +1,53 @@ +package de.kuschku.quasseldroid_ng.util.quassel + +import java.util.* + +object IrcUserUtils { + fun senderColor(nick: String): Int { + return 0xf and CRCUtils.qChecksum( + nick.trimEnd('_').toLowerCase(Locale.US).toByteArray(Charsets.ISO_8859_1) + ) + } + + fun nick(hostmask: String): String { + return hostmask.substring( + 0, + hostmask.lastIndex('!', hostmask.lastIndex('@')) ?: hostmask.length + ) + } + + fun user(hostmask: String): String { + return hostmask.substring( + hostmask.lastIndex('!', hostmask.lastIndex('@')) ?: 0, + hostmask.lastIndex('@') ?: hostmask.length + ) + } + + fun host(hostmask: String): String { + return hostmask.substring( + hostmask.lastIndex('@') ?: 0 + ) + } + + fun mask(hostmask: String): String { + return hostmask.substring( + hostmask.lastIndex('!', hostmask.lastIndex('@')) ?: 0 + ) + } + + private fun String.firstIndex(char: Char, startIndex: Int? = null, ignoreCase: Boolean = false): Int? { + val lastIndex = indexOf(char, startIndex ?: 0, ignoreCase) + if (lastIndex < 0) + return null + else + return lastIndex + } + + private fun String.lastIndex(char: Char, startIndex: Int? = null, ignoreCase: Boolean = false): Int? { + val lastIndex = lastIndexOf(char, startIndex ?: lastIndex, ignoreCase) + if (lastIndex < 0) + return null + else + return lastIndex + } +} \ No newline at end of file diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/service/BackendServiceConnection.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/service/BackendServiceConnection.kt new file mode 100644 index 000000000..6dff17f6a --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/service/BackendServiceConnection.kt @@ -0,0 +1,52 @@ +package de.kuschku.quasseldroid_ng.util.service + +import android.arch.lifecycle.MutableLiveData +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.os.IBinder +import de.kuschku.libquassel.session.Backend +import de.kuschku.quasseldroid_ng.service.QuasselService + +class BackendServiceConnection : ServiceConnection { + var bound = false + val backend = MutableLiveData<Backend?>() + + var context: Context? = null + + override fun onServiceDisconnected(component: ComponentName?) { + bound = false + when (component) { + ComponentName(context, QuasselService::class.java) -> { + backend.value = null + } + } + } + + override fun onServiceConnected(component: ComponentName?, binder: IBinder?) { + bound = true + when (component) { + ComponentName(context, QuasselService::class.java) -> + if (binder is QuasselService.QuasselBinder) { + backend.value = binder.backend + } + } + } + + fun start(intent: Intent = Intent(context, QuasselService::class.java)) { + context?.startService(intent) + } + + fun bind(intent: Intent = Intent(context, QuasselService::class.java), flags: Int = 0) { + context?.bindService(intent, this, flags) + } + + fun stop(intent: Intent = Intent(context, QuasselService::class.java)) { + context?.stopService(intent) + } + + fun unbind() { + if (bound) context?.unbindService(this) + } +} \ No newline at end of file diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/service/ServiceBoundActivity.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/service/ServiceBoundActivity.kt index 9c8598786..ef9518e58 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/util/service/ServiceBoundActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/service/ServiceBoundActivity.kt @@ -1,49 +1,30 @@ package de.kuschku.quasseldroid_ng.util.service -import android.arch.lifecycle.MutableLiveData -import android.content.ComponentName -import android.content.Intent -import android.content.ServiceConnection +import android.arch.lifecycle.LiveData import android.os.Bundle -import android.os.IBinder import android.support.annotation.ColorRes import android.support.annotation.DrawableRes import android.support.v7.app.AppCompatActivity import de.kuschku.libquassel.session.Backend import de.kuschku.quasseldroid_ng.R -import de.kuschku.quasseldroid_ng.service.QuasselService import de.kuschku.quasseldroid_ng.util.helper.updateRecentsHeaderIfExisting abstract class ServiceBoundActivity : AppCompatActivity() { - protected val backend = MutableLiveData<Backend?>() @DrawableRes protected val icon: Int = R.mipmap.ic_launcher @ColorRes protected val recentsHeaderColor: Int = R.color.colorPrimaryDark - private val connection = object : ServiceConnection { - override fun onServiceDisconnected(component: ComponentName?) { - when (component) { - ComponentName(application, QuasselService::class.java) -> { - backend.value = null - } - } - } + private val connection = BackendServiceConnection() - override fun onServiceConnected(component: ComponentName?, binder: IBinder?) { - when (component) { - ComponentName(application, QuasselService::class.java) -> - if (binder is QuasselService.QuasselBinder) { - backend.value = binder.backend - } - } - } - } + val backend: LiveData<Backend?> + get() = connection.backend override fun onCreate(savedInstanceState: Bundle?) { + connection.context = this setTheme(R.style.Theme_ChatTheme_Quassel_Light) super.onCreate(savedInstanceState) - startService(Intent(this, QuasselService::class.java)) + connection.start() updateRecentsHeader() } @@ -56,17 +37,17 @@ abstract class ServiceBoundActivity : AppCompatActivity() { } override fun onStart() { - bindService(Intent(this, QuasselService::class.java), connection, 0) + connection.bind() super.onStart() } override fun onStop() { super.onStop() - unbindService(connection) + connection.unbind() } protected fun stopService() { - unbindService(connection) - stopService(Intent(this, QuasselService::class.java)) + connection.unbind() + connection.stop() } } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/service/ServiceBoundFragment.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/service/ServiceBoundFragment.kt index 87c260f11..6d42d7227 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/util/service/ServiceBoundFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/service/ServiceBoundFragment.kt @@ -1,49 +1,29 @@ package de.kuschku.quasseldroid_ng.util.service -import android.arch.lifecycle.MutableLiveData -import android.content.ComponentName -import android.content.Intent -import android.content.ServiceConnection +import android.arch.lifecycle.LiveData import android.os.Bundle -import android.os.IBinder import android.support.v4.app.Fragment import de.kuschku.libquassel.session.Backend -import de.kuschku.quasseldroid_ng.service.QuasselService abstract class ServiceBoundFragment : Fragment() { - protected val backend = MutableLiveData<Backend?>() + private var connection = BackendServiceConnection() - private val connection = object : ServiceConnection { - override fun onServiceDisconnected(component: ComponentName?) { - when (component) { - ComponentName(context.applicationContext, QuasselService::class.java) -> { - backend.value = null - } - } - } - - override fun onServiceConnected(component: ComponentName?, binder: IBinder?) { - when (component) { - ComponentName(context.applicationContext, QuasselService::class.java) -> - if (binder is QuasselService.QuasselBinder) { - backend.value = binder.backend - } - } - } - } + val backend: LiveData<Backend?> + get() = connection.backend override fun onCreate(savedInstanceState: Bundle?) { + connection.context = context super.onCreate(savedInstanceState) - context.startService(Intent(context, QuasselService::class.java)) + connection.start() } override fun onStart() { - context.bindService(Intent(context, QuasselService::class.java), connection, 0) + connection.bind() super.onStart() } override fun onStop() { super.onStop() - context.unbindService(connection) + connection.unbind() } } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/ui/SpanFormatter.java b/app/src/main/java/de/kuschku/quasseldroid_ng/util/ui/SpanFormatter.java new file mode 100644 index 000000000..31f342ec7 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/ui/SpanFormatter.java @@ -0,0 +1,140 @@ +/* + * QuasselDroid - Quassel client for Android + * Copyright (C) 2016 Janne Koschinski + * Copyright (C) 2016 Ken Børge Viktil + * Copyright (C) 2016 Magnus Fjell + * Copyright (C) 2016 Martin Sandsmark <martin.sandsmark@kde.org> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* +* Copyright © 2014 George T. Steel +* +* 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 de.kuschku.quasseldroid_ng.util.ui; + +import android.support.annotation.NonNull; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.SpannedString; + +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Provides {@link String#format} style functions that work with {@link Spanned} strings and preserve formatting. + * + * @author George T. Steel + */ +public class SpanFormatter { + private static final Pattern FORMAT_SEQUENCE = Pattern.compile("%([0-9]+\\$|<?)([^a-zA-z%]*)([[a-zA-Z%]&&[^tT]]|[tT][a-zA-Z])"); + + private SpanFormatter() { + } + + /** + * Version of {@link String#format(String, Object...)} that works on {@link Spanned} strings to preserve rich text formatting. + * Both the {@code format} as well as any {@code %s args} can be Spanned and will have their formatting preserved. + * Due to the way {@link Spannable}s work, any argument's spans will can only be included <b>once</b> in the result. + * Any duplicates will appear as text only. + * + * @param format the format string (see {@link java.util.Formatter#format}) + * @param args the list of arguments passed to the formatter. If there are + * more arguments than required by {@code format}, + * additional arguments are ignored. + * @return the formatted string (with spans). + */ + @NonNull + public static SpannedString format(@NonNull CharSequence format, Object... args) { + return format(Locale.getDefault(), format, args); + } + + /** + * Version of {@link String#format(Locale, String, Object...)} that works on {@link Spanned} strings to preserve rich text formatting. + * Both the {@code format} as well as any {@code %s args} can be Spanned and will have their formatting preserved. + * Due to the way {@link Spannable}s work, any argument's spans will can only be included <b>once</b> in the result. + * Any duplicates will appear as text only. + * + * @param locale the locale to apply; {@code null} value means no localization. + * @param format the format string (see {@link java.util.Formatter#format}) + * @param args the list of arguments passed to the formatter. + * @return the formatted string (with spans). + * @see String#format(Locale, String, Object...) + */ + @NonNull + public static SpannedString format(@NonNull Locale locale, @NonNull CharSequence format, Object... args) { + SpannableStringBuilder out = new SpannableStringBuilder(format); + + int i = 0; + int argAt = -1; + + while (i < out.length()) { + Matcher m = FORMAT_SEQUENCE.matcher(out); + if (!m.find(i)) break; + i = m.start(); + int exprEnd = m.end(); + + String argTerm = m.group(1); + String modTerm = m.group(2); + String typeTerm = m.group(3); + + CharSequence cookedArg; + + if (typeTerm.equals("%")) { + cookedArg = "%"; + } else { + int argIdx; + switch (argTerm) { + case "": + argIdx = ++argAt; + break; + case "<": + argIdx = argAt; + break; + default: + argIdx = Integer.parseInt(argTerm.substring(0, argTerm.length() - 1)) - 1; + break; + } + + Object argItem = args[argIdx]; + + if (typeTerm.equals("s") && argItem instanceof Spanned) { + cookedArg = (Spanned) argItem; + } else { + cookedArg = String.format(locale, "%" + modTerm + typeTerm, argItem); + } + } + + out.replace(i, exprEnd, cookedArg); + i += cookedArg.length(); + } + + return new SpannedString(out); + } +} diff --git a/app/src/main/res/layout/widget_chatmessage_action.xml b/app/src/main/res/layout/widget_chatmessage_action.xml new file mode 100644 index 000000000..b4271dd33 --- /dev/null +++ b/app/src/main/res/layout/widget_chatmessage_action.xml @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ QuasselDroid - Quassel client for Android + ~ Copyright (C) 2016 Janne Koschinski + ~ Copyright (C) 2016 Ken Børge Viktil + ~ Copyright (C) 2016 Magnus Fjell + ~ Copyright (C) 2016 Martin Sandsmark <martin.sandsmark@kde.org> + ~ + ~ This program is free software: you can redistribute it and/or modify it + ~ under the terms of the GNU General Public License as published by the Free + ~ Software Foundation, either version 3 of the License, or (at your option) + ~ any later version. + ~ + ~ This program is distributed in the hope that it will be useful, + ~ but WITHOUT ANY WARRANTY; without even the implied warranty of + ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ~ GNU General Public License for more details. + ~ + ~ You should have received a copy of the GNU General Public License along + ~ with this program. If not, see <http://www.gnu.org/licenses/>. + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clickable="true" + android:focusable="true" + android:gravity="top" + android:orientation="horizontal" + android:paddingBottom="@dimen/message_vertical" + android:paddingEnd="@dimen/message_horizontal" + android:paddingLeft="@dimen/message_horizontal" + android:paddingRight="@dimen/message_horizontal" + android:paddingStart="@dimen/message_horizontal" + android:paddingTop="@dimen/message_vertical" + android:textAppearance="?android:attr/textAppearanceListItemSmall"> + + <TextView + android:id="@+id/time" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/message_horizontal" + android:layout_marginRight="@dimen/message_horizontal" + android:textColor="?attr/colorForegroundSecondary" + android:typeface="monospace" /> + + <TextView + android:id="@+id/content" + android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_weight="1" + android:textColor="?attr/colorForegroundAction" + android:textIsSelectable="true" + android:textStyle="italic" /> +</LinearLayout> diff --git a/app/src/main/res/layout/widget_chatmessage_error.xml b/app/src/main/res/layout/widget_chatmessage_error.xml new file mode 100644 index 000000000..44e42f1cd --- /dev/null +++ b/app/src/main/res/layout/widget_chatmessage_error.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?attr/colorBackgroundSecondary" + android:clickable="true" + android:focusable="true" + android:gravity="top" + android:orientation="horizontal" + android:paddingBottom="@dimen/message_vertical" + android:paddingEnd="@dimen/message_horizontal" + android:paddingLeft="@dimen/message_horizontal" + android:paddingRight="@dimen/message_horizontal" + android:paddingStart="@dimen/message_horizontal" + android:paddingTop="@dimen/message_vertical" + android:textAppearance="?android:attr/textAppearanceListItemSmall"> + + <TextView + android:id="@+id/time" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/message_horizontal" + android:layout_marginRight="@dimen/message_horizontal" + android:textColor="?attr/colorForegroundSecondary" + android:typeface="monospace" /> + + <TextView + android:id="@+id/content" + android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_weight="1" + android:textColor="?attr/colorForegroundError" + android:textIsSelectable="true" + android:textStyle="italic" /> +</LinearLayout> diff --git a/app/src/main/res/layout/widget_chatmessage_plain.xml b/app/src/main/res/layout/widget_chatmessage_plain.xml new file mode 100644 index 000000000..0e16b66a1 --- /dev/null +++ b/app/src/main/res/layout/widget_chatmessage_plain.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clickable="true" + android:focusable="true" + android:gravity="top" + android:orientation="horizontal" + android:paddingBottom="@dimen/message_vertical" + android:paddingEnd="@dimen/message_horizontal" + android:paddingLeft="@dimen/message_horizontal" + android:paddingRight="@dimen/message_horizontal" + android:paddingStart="@dimen/message_horizontal" + android:paddingTop="@dimen/message_vertical" + android:textAppearance="?android:attr/textAppearanceListItemSmall"> + + <TextView + android:id="@+id/time" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/message_horizontal" + android:layout_marginRight="@dimen/message_horizontal" + android:textColor="?attr/colorForegroundSecondary" + android:typeface="monospace" /> + + <TextView + android:id="@+id/content" + android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_weight="1" + android:textColor="?attr/colorForeground" + android:textIsSelectable="true" /> +</LinearLayout> diff --git a/app/src/main/res/layout/widget_chatmessage_server.xml b/app/src/main/res/layout/widget_chatmessage_server.xml new file mode 100644 index 000000000..40613e00c --- /dev/null +++ b/app/src/main/res/layout/widget_chatmessage_server.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?attr/colorBackgroundSecondary" + android:clickable="true" + android:focusable="true" + android:gravity="top" + android:orientation="horizontal" + android:paddingBottom="@dimen/message_vertical" + android:paddingEnd="@dimen/message_horizontal" + android:paddingLeft="@dimen/message_horizontal" + android:paddingRight="@dimen/message_horizontal" + android:paddingStart="@dimen/message_horizontal" + android:paddingTop="@dimen/message_vertical" + android:textAppearance="?android:attr/textAppearanceListItemSmall"> + + <TextView + android:id="@+id/time" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/message_horizontal" + android:layout_marginRight="@dimen/message_horizontal" + android:textColor="?attr/colorForegroundSecondary" + android:typeface="monospace" /> + + <TextView + android:id="@+id/content" + android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_weight="1" + android:textColor="?attr/colorForegroundSecondary" + android:textIsSelectable="true" + android:textStyle="italic" /> +</LinearLayout> diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 4b0853092..dd9f8a6b1 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -1,3 +1,6 @@ <resources> - <dimen name="navigation_drawer_max_width">320dp</dimen> + <dimen name="navigation_drawer_max_width">320dp</dimen> + + <dimen name="message_horizontal">8dp</dimen> + <dimen name="message_vertical">2dp</dimen> </resources> diff --git a/build.gradle.kts b/build.gradle.kts index 98bb71717..b5ff130ac 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,8 +5,8 @@ buildscript { } dependencies { classpath("com.android.tools.build:gradle:3.0.1") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.61") - classpath("org.jetbrains.kotlin:kotlin-android-extensions:1.1.61") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.0") + classpath("org.jetbrains.kotlin:kotlin-android-extensions:1.2.0") } } diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index c39ec3973..0161971e9 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -11,10 +11,10 @@ plugins { val appCompatVersion = "26.1.0" dependencies { - implementation(kotlin("stdlib", "1.1.61")) + implementation(kotlin("stdlib", "1.2.0")) implementation(appCompat("support-annotations")) - implementation("org.threeten:threetenbp:1.3.6") + implementation("org.threeten", "threetenbp", "1.3.6") implementation("io.reactivex.rxjava2:rxjava:2.1.3") implementation(project(":invokerannotations")) diff --git a/malheur/build.gradle.kts b/malheur/build.gradle.kts index d1bdc4450..38e8818bd 100644 --- a/malheur/build.gradle.kts +++ b/malheur/build.gradle.kts @@ -1,7 +1,3 @@ -import org.gradle.kotlin.dsl.apply -import org.gradle.kotlin.dsl.dependencies -import org.gradle.kotlin.dsl.kotlin - plugins { id("com.android.library") kotlin("android") @@ -10,7 +6,7 @@ plugins { android { compileSdkVersion(26) - buildToolsVersion("26.0.2") + buildToolsVersion("27.0.2") defaultConfig { minSdkVersion(9) @@ -21,7 +17,7 @@ android { } dependencies { - implementation(kotlin("stdlib", "1.1.61")) + implementation(kotlin("stdlib", "1.2.0")) - implementation("com.google.code.gson:gson:2.2.4") + implementation("com.google.code.gson", "gson", "2.2.4") } -- GitLab