diff --git a/app/build.gradle b/app/build.gradle index c7f1c027f464f23eb15efaddcb59ce3ad940cf29..85584f433807491ad59d2568f3cd5310824892a3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -22,7 +22,7 @@ android { viewBinding true } - signingConfigs{ + signingConfigs { release { Properties properties = new Properties() if (rootProject.file("signing.properties").exists()) { @@ -77,7 +77,7 @@ dependencies { implementation project(path: ':gallery') //Firebase - gplayImplementation platform('com.google.firebase:firebase-bom:32.5.0') + gplayImplementation platform('com.google.firebase:firebase-bom:32.6.0') gplayImplementation 'com.google.firebase:firebase-crashlytics-ktx' gplayImplementation 'com.google.firebase:firebase-analytics-ktx' gplayImplementation 'com.google.firebase:firebase-messaging-ktx' @@ -88,13 +88,6 @@ dependencies { //Emoji implementation 'com.vanniktech:emoji-google:0.17.0' - //Markdown - def markwon_version = "4.6.2" - implementation "io.noties.markwon:core:$markwon_version" - implementation "io.noties.markwon:linkify:$markwon_version" - implementation "io.noties.markwon:ext-strikethrough:$markwon_version" - implementation "io.noties.markwon:ext-tasklist:$markwon_version" - //Log implementation 'com.jakewharton.timber:timber:5.0.1' diff --git a/app/src/main/java/org/futo/circles/extensions/EditableExtensions.kt b/app/src/main/java/org/futo/circles/extensions/EditableExtensions.kt deleted file mode 100644 index bc1f20a2fca6913b1052fd12106c7fec80100460..0000000000000000000000000000000000000000 --- a/app/src/main/java/org/futo/circles/extensions/EditableExtensions.kt +++ /dev/null @@ -1,17 +0,0 @@ -package org.futo.circles.extensions - -import android.text.Editable -import org.futo.circles.feature.timeline.post.markdown.span.TextStyle -import org.futo.circles.feature.timeline.post.markdown.span.toSpanClass - -fun Editable.getGivenSpansAt( - vararg span: TextStyle, - start: Int = 0, - end: Int = length -): MutableList<Any> { - val spanList = mutableListOf<Any>() - for (selectedSpan in span) { - getSpans(start, end, selectedSpan.toSpanClass()).forEach { spanList.add(it) } - } - return spanList -} diff --git a/app/src/main/java/org/futo/circles/feature/notifications/DisplayableEventFormatter.kt b/app/src/main/java/org/futo/circles/feature/notifications/DisplayableEventFormatter.kt index ce28f3d31155eb089cf4ed361794f2f5de48716e..dca27d5ce8f8ac055a2fda41b53e29a03c4eb902 100644 --- a/app/src/main/java/org/futo/circles/feature/notifications/DisplayableEventFormatter.kt +++ b/app/src/main/java/org/futo/circles/feature/notifications/DisplayableEventFormatter.kt @@ -4,7 +4,7 @@ import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import org.futo.circles.R import org.futo.circles.core.provider.MatrixSessionProvider -import org.futo.circles.feature.timeline.post.markdown.MarkdownParser +import org.futo.circles.core.feature.markdown.MarkdownParser import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel diff --git a/app/src/main/java/org/futo/circles/feature/timeline/TimelineDialogFragment.kt b/app/src/main/java/org/futo/circles/feature/timeline/TimelineDialogFragment.kt index f55058b5cbc73ec13d399ce95d92e0558dda4a64..6da9154bacb1eddcf6c1f89ff3944adc8ac56dc6 100644 --- a/app/src/main/java/org/futo/circles/feature/timeline/TimelineDialogFragment.kt +++ b/app/src/main/java/org/futo/circles/feature/timeline/TimelineDialogFragment.kt @@ -84,6 +84,7 @@ class TimelineDialogFragment : BaseFullscreenDialogFragment(DialogFragmentTimeli private val listAdapter by lazy { TimelineAdapter( + requireContext(), getCurrentUserPowerLevel(args.roomId), this, isThread diff --git a/app/src/main/java/org/futo/circles/feature/timeline/list/TimelineAdapter.kt b/app/src/main/java/org/futo/circles/feature/timeline/list/TimelineAdapter.kt index 8f932c8a6541afd06420a627d5721a75447c2731..2a789691a2e5cdc96197e4def61e21fb0de2f336 100644 --- a/app/src/main/java/org/futo/circles/feature/timeline/list/TimelineAdapter.kt +++ b/app/src/main/java/org/futo/circles/feature/timeline/list/TimelineAdapter.kt @@ -1,14 +1,17 @@ package org.futo.circles.feature.timeline.list import android.annotation.SuppressLint +import android.content.Context import android.view.ViewGroup import org.futo.circles.core.base.list.BaseRvAdapter +import org.futo.circles.core.feature.markdown.MarkdownParser import org.futo.circles.core.model.Post import org.futo.circles.core.model.PostContentType import org.futo.circles.model.PostItemPayload import org.futo.circles.view.PostOptionsListener class TimelineAdapter( + context: Context, private var userPowerLevel: Int, private val postOptionsListener: PostOptionsListener, private val isThread: Boolean, @@ -22,6 +25,9 @@ class TimelineAdapter( needToUpdateFullItem = new.content != old.content || new.postInfo != old.postInfo ) }) { + + private val markwon = MarkdownParser.markwonBuilder(context) + @SuppressLint("NotifyDataSetChanged") fun updateUserPowerLevel(level: Int) { userPowerLevel = level @@ -36,7 +42,7 @@ class TimelineAdapter( parent, postOptionsListener, isThread ) - else -> TextMediaPostViewHolder(parent, postOptionsListener, isThread) + else -> TextMediaPostViewHolder(parent, markwon, postOptionsListener, isThread) } } diff --git a/app/src/main/java/org/futo/circles/feature/timeline/list/TimelineViewHolder.kt b/app/src/main/java/org/futo/circles/feature/timeline/list/TimelineViewHolder.kt index d43530f631e7deb32e190ea15968cdd7299ef3c0..dc2e3b4cb2a9c9f78d6c5680af01e9abb9000cd3 100644 --- a/app/src/main/java/org/futo/circles/feature/timeline/list/TimelineViewHolder.kt +++ b/app/src/main/java/org/futo/circles/feature/timeline/list/TimelineViewHolder.kt @@ -8,12 +8,12 @@ import android.view.ViewGroup import android.widget.TextView import androidx.core.view.updateLayoutParams import androidx.recyclerview.widget.RecyclerView +import io.noties.markwon.Markwon +import org.futo.circles.core.base.list.ViewBindingHolder import org.futo.circles.core.extensions.gone import org.futo.circles.core.extensions.loadEncryptedThumbOrFullIntoWithAspect import org.futo.circles.core.extensions.setIsVisible import org.futo.circles.core.extensions.visible -import org.futo.circles.core.base.list.ViewBindingHolder -import org.futo.circles.core.base.list.context import org.futo.circles.core.model.MediaContent import org.futo.circles.core.model.MediaType import org.futo.circles.core.model.PollContent @@ -21,7 +21,6 @@ import org.futo.circles.core.model.Post import org.futo.circles.core.model.TextContent import org.futo.circles.databinding.ViewPollPostBinding import org.futo.circles.databinding.ViewTextMediaPostBinding -import org.futo.circles.feature.timeline.post.markdown.MarkdownParser import org.futo.circles.model.* import org.futo.circles.view.PostLayout import org.futo.circles.view.PostOptionsListener @@ -31,7 +30,6 @@ sealed class PostViewHolder(view: View, private val isThread: Boolean) : RecyclerView.ViewHolder(view) { abstract val postLayout: PostLayout - protected val markwon = MarkdownParser.markwonBuilder(context) open fun bind(post: Post, userPowerLevel: Int) { postLayout.setData(post, userPowerLevel, isThread) @@ -44,6 +42,7 @@ sealed class PostViewHolder(view: View, private val isThread: Boolean) : class TextMediaPostViewHolder( parent: ViewGroup, + private val markwon: Markwon, postOptionsListener: PostOptionsListener, isThread: Boolean ) : PostViewHolder(inflate(parent, ViewTextMediaPostBinding::inflate), isThread), diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/create/CreatePostDialogFragment.kt b/app/src/main/java/org/futo/circles/feature/timeline/post/create/CreatePostDialogFragment.kt index 2f02efc3bf74fa5ccf1c45e46e4dcf4a2205c950..37b629d3aaeda0588c4623ce8598d9d707f9e43d 100644 --- a/app/src/main/java/org/futo/circles/feature/timeline/post/create/CreatePostDialogFragment.kt +++ b/app/src/main/java/org/futo/circles/feature/timeline/post/create/CreatePostDialogFragment.kt @@ -11,26 +11,24 @@ import androidx.navigation.fragment.navArgs import dagger.hilt.android.AndroidEntryPoint import org.futo.circles.R import org.futo.circles.core.base.NetworkObserver +import org.futo.circles.core.base.fragment.BaseFullscreenDialogFragment import org.futo.circles.core.extensions.navigateSafe import org.futo.circles.core.extensions.observeData -import org.futo.circles.core.extensions.onBackPressed import org.futo.circles.core.extensions.showError -import org.futo.circles.core.base.fragment.BaseFullscreenDialogFragment +import org.futo.circles.core.feature.picker.helper.MediaPickerHelper import org.futo.circles.core.model.MediaContent import org.futo.circles.core.model.MediaType import org.futo.circles.core.model.PostContentType import org.futo.circles.core.model.TextContent -import org.futo.circles.core.feature.picker.helper.MediaPickerHelper import org.futo.circles.databinding.DialogFragmentCreatePostBinding import org.futo.circles.feature.timeline.post.emoji.EmojiPickerListener -import org.futo.circles.feature.timeline.post.markdown.span.TextStyle -import org.futo.circles.view.PreviewPostListener +import org.futo.circles.model.CreatePostContent import java.util.* @AndroidEntryPoint class CreatePostDialogFragment : BaseFullscreenDialogFragment(DialogFragmentCreatePostBinding::inflate), - PostConfigurationOptionListener, EmojiPickerListener { + PreviewPostListener, EmojiPickerListener { private val args: CreatePostDialogFragmentArgs by navArgs() private val binding by lazy { @@ -58,29 +56,7 @@ class CreatePostDialogFragment : private fun setupViews() { setToolbarTitle() with(binding) { - btnSend.apply { - setOnClickListener { - if (!NetworkObserver.isConnected()) { - showError(getString(org.futo.circles.core.R.string.no_internet_connection)) - return@setOnClickListener - } - sendPost() - onBackPressed() - } - } - binding.vPostOptions.apply { - setOptionsListener(this@CreatePostDialogFragment) - showMainOptionsList(!args.isEdit) - } - vPostPreview.setup( - object : PreviewPostListener { - override fun onPostContentAvailable(isAvailable: Boolean) { - binding.btnSend.isEnabled = isAvailable - } - }, - onHighlightTextStyle = { textStyle -> binding.vPostOptions.highlightStyle(textStyle) }, - roomId = args.roomId - ) + vPostPreview.setup(this@CreatePostDialogFragment, args.roomId, args.isEdit) } } @@ -106,16 +82,16 @@ class CreatePostDialogFragment : binding.vPostPreview.setMedia(uri, type) } - private fun sendPost() { - if (args.isEdit) onEditPost() + private fun sendPost(content: CreatePostContent) { + if (args.isEdit) onEditPost(content) else createPostListener?.onSendPost( - args.roomId, binding.vPostPreview.getPostContent(), args.eventId + args.roomId, content, args.eventId ) } - private fun onEditPost() { + private fun onEditPost(content: CreatePostContent) { val eventId = args.eventId ?: return - createPostListener?.onEditPost(args.roomId, binding.vPostPreview.getPostContent(), eventId) + createPostListener?.onEditPost(args.roomId, content, eventId) } override fun onUploadMediaClicked() { @@ -131,20 +107,21 @@ class CreatePostDialogFragment : ) } - override fun onMentionClicked() { - binding.vPostPreview.insertMention() - } - - override fun onTextStyleSelected(textStyle: TextStyle, isSelected: Boolean) { - binding.vPostPreview.setTextStyle(textStyle, isSelected) - } - override fun onAddLinkClicked() { AddLinkDialog(requireContext()) { title, link -> binding.vPostPreview.insertLink(title, link) }.show() } + override fun onSendClicked(content: CreatePostContent) { + if (!NetworkObserver.isConnected()) { + showError(getString(org.futo.circles.core.R.string.no_internet_connection)) + return + } + sendPost(content) + dismiss() + } + override fun onEmojiSelected(roomId: String?, eventId: String?, emoji: String) { binding.vPostPreview.insertEmoji(emoji) } diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/create/PostConfigurationOptionListener.kt b/app/src/main/java/org/futo/circles/feature/timeline/post/create/PostConfigurationOptionListener.kt deleted file mode 100644 index e07ad2f2fe140ff09ba723d1111adde3e115cadd..0000000000000000000000000000000000000000 --- a/app/src/main/java/org/futo/circles/feature/timeline/post/create/PostConfigurationOptionListener.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.futo.circles.feature.timeline.post.create - -import org.futo.circles.feature.timeline.post.markdown.span.TextStyle - -interface PostConfigurationOptionListener { - fun onUploadMediaClicked() - fun onEmojiClicked() - fun onMentionClicked() - fun onTextStyleSelected(textStyle: TextStyle, isSelected: Boolean) - fun onAddLinkClicked() -} \ No newline at end of file diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/create/PreviewPostListener.kt b/app/src/main/java/org/futo/circles/feature/timeline/post/create/PreviewPostListener.kt new file mode 100644 index 0000000000000000000000000000000000000000..feb0d838003572dcf822999fa3049b51abc497a4 --- /dev/null +++ b/app/src/main/java/org/futo/circles/feature/timeline/post/create/PreviewPostListener.kt @@ -0,0 +1,10 @@ +package org.futo.circles.feature.timeline.post.create + +import org.futo.circles.model.CreatePostContent + +interface PreviewPostListener { + fun onUploadMediaClicked() + fun onEmojiClicked() + fun onAddLinkClicked() + fun onSendClicked(content: CreatePostContent) +} \ No newline at end of file diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/EnhancedMovementMethod.kt b/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/EnhancedMovementMethod.kt deleted file mode 100644 index c330c316775d5e0b90337c303dab173168606429..0000000000000000000000000000000000000000 --- a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/EnhancedMovementMethod.kt +++ /dev/null @@ -1,58 +0,0 @@ -package org.futo.circles.feature.timeline.post.markdown - - -import android.text.Selection -import android.text.Spannable -import android.text.method.ArrowKeyMovementMethod -import android.text.method.MovementMethod -import android.text.style.ClickableSpan -import android.view.MotionEvent -import android.widget.TextView - - -class EnhancedMovementMethod : ArrowKeyMovementMethod() { - private var sInstance: EnhancedMovementMethod? = null - - fun getsInstance(): MovementMethod? { - if (sInstance == null) { - sInstance = EnhancedMovementMethod() - } - return sInstance - } - - override fun onTouchEvent(widget: TextView, buffer: Spannable, event: MotionEvent): Boolean { - val action = event.action - if (action == MotionEvent.ACTION_UP || - action == MotionEvent.ACTION_DOWN - ) { - var x = event.x.toInt() - var y = event.y.toInt() - x -= widget.totalPaddingLeft - y -= widget.totalPaddingTop - x += widget.scrollX - y += widget.scrollY - val layout = widget.layout - val line = layout.getLineForVertical(y) - val off = layout.getOffsetForHorizontal(line, x.toFloat()) - val link = buffer.getSpans( - off, off, - ClickableSpan::class.java - ) - if (link.isNotEmpty()) { - if (action == MotionEvent.ACTION_UP) { - if (x < layout.getLineMax(0)) { - link[0].onClick(widget) - } - } else { - Selection.setSelection( - buffer, - buffer.getSpanStart(link[0]), - buffer.getSpanEnd(link[0]) - ) - } - return true - } - } - return super.onTouchEvent(widget, buffer, event) - } -} \ No newline at end of file diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/MarkdownParser.kt b/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/MarkdownParser.kt deleted file mode 100644 index 3928f432776aafef114b3e725c8a8037665c560d..0000000000000000000000000000000000000000 --- a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/MarkdownParser.kt +++ /dev/null @@ -1,140 +0,0 @@ -package org.futo.circles.feature.timeline.post.markdown - -import android.content.Context -import android.graphics.Color -import android.graphics.Typeface -import android.text.Editable -import android.text.style.StrikethroughSpan -import android.text.style.StyleSpan -import androidx.core.content.ContextCompat -import androidx.core.text.getSpans -import io.noties.markwon.AbstractMarkwonPlugin -import io.noties.markwon.Markwon -import io.noties.markwon.MarkwonSpansFactory -import io.noties.markwon.core.spans.BulletListItemSpan -import io.noties.markwon.core.spans.EmphasisSpan -import io.noties.markwon.core.spans.LinkSpan -import io.noties.markwon.core.spans.StrongEmphasisSpan -import io.noties.markwon.ext.strikethrough.StrikethroughPlugin -import io.noties.markwon.ext.tasklist.TaskListPlugin -import io.noties.markwon.ext.tasklist.TaskListSpan -import io.noties.markwon.linkify.LinkifyPlugin -import org.commonmark.node.Emphasis -import org.commonmark.node.StrongEmphasis -import org.futo.circles.R -import org.futo.circles.core.extensions.notEmptyDisplayName -import org.futo.circles.core.provider.MatrixSessionProvider -import org.futo.circles.extensions.getGivenSpansAt -import org.futo.circles.feature.timeline.post.markdown.mentions.plugin.MentionPlugin -import org.futo.circles.feature.timeline.post.markdown.span.MentionSpan -import org.futo.circles.feature.timeline.post.markdown.span.OrderedListItemSpan -import org.futo.circles.feature.timeline.post.markdown.span.TextStyle -import org.matrix.android.sdk.api.session.getUserOrDefault - - -object MarkdownParser { - - const val mentionMark = "@" - private const val boldMark = "**" - private const val italicMark = "_" - private const val strikeMark = "~~" - private const val notDoneMark = "* [ ]" - private const val doneMark = "* [x]" - - fun editableToMarkdown(text: Editable): String { - val textCopy = Editable.Factory.getInstance().newEditable(text) - text.getGivenSpansAt(span = TextStyle.values()).forEach { - val start = textCopy.getSpanStart(it) - val end = textCopy.getSpanEnd(it) - when (it) { - is StrongEmphasisSpan -> { - val endIndex = calculateLastIndexToInsert(textCopy, end, boldMark) - textCopy.insert(start, boldMark) - textCopy.insert(endIndex, boldMark) - } - - is EmphasisSpan -> { - val endIndex = calculateLastIndexToInsert(textCopy, end, italicMark) - textCopy.insert(start, italicMark) - textCopy.insert(endIndex, italicMark) - } - - is StrikethroughSpan -> { - val endIndex = calculateLastIndexToInsert(textCopy, end, strikeMark) - textCopy.insert(start, strikeMark) - textCopy.insert(endIndex, strikeMark) - } - - is LinkSpan -> { - val linkStartMark = "[" - textCopy.insert(start, linkStartMark) - textCopy.insert(end + linkStartMark.length, "](${it.link})") - } - - is BulletListItemSpan -> textCopy.insert(start, "*") - is OrderedListItemSpan -> textCopy.insert(start, it.number) - is TaskListSpan -> { - val taskSpanMark = if (it.isDone) doneMark else notDoneMark - textCopy.insert(start, taskSpanMark) - } - } - } - text.getSpans<MentionSpan>().forEach { - val end = textCopy.getSpanEnd(it) - val textToInsert = it.name + mentionMark - textCopy.insert(end, textToInsert) - } - return textCopy.toString() - } - - fun markwonBuilder(context: Context): Markwon = Markwon.builder(context) - .usePlugin(StrikethroughPlugin.create()) - .usePlugin(LinkifyPlugin.create()) - .usePlugin(MentionPlugin(context)) - .usePlugin( - TaskListPlugin.create( - ContextCompat.getColor(context, R.color.blue), - ContextCompat.getColor(context, R.color.blue), - Color.WHITE - ) - ).build() - - fun markwonNotificationBuilder(context: Context): Markwon = Markwon.builder(context) - .usePlugin(object : AbstractMarkwonPlugin() { - override fun configureSpansFactory(builder: MarkwonSpansFactory.Builder) { - builder.setFactory( - Emphasis::class.java - ) { _, _ -> StyleSpan(Typeface.ITALIC) } - } - }) - .usePlugin(object : AbstractMarkwonPlugin() { - override fun configureSpansFactory(builder: MarkwonSpansFactory.Builder) { - builder.setFactory( - StrongEmphasis::class.java - ) { _, _ -> StyleSpan(Typeface.BOLD) } - } - }) - .usePlugin(StrikethroughPlugin.create()) - .usePlugin(LinkifyPlugin.create()) - .usePlugin( - TaskListPlugin.create( - ContextCompat.getColor(context, R.color.blue), - ContextCompat.getColor(context, R.color.blue), - Color.WHITE - ) - ).build() - - fun hasCurrentUserMention(text: String): Boolean { - val session = MatrixSessionProvider.currentSession ?: return false - val userName = session.getUserOrDefault(session.myUserId).notEmptyDisplayName() - val mentionString = mentionMark + userName + mentionMark - return text.contains(mentionString) - } - - private fun calculateLastIndexToInsert(textCopy: Editable, spanEnd: Int, mark: String): Int { - var endIndex = spanEnd + mark.length - val lastChar = textCopy.getOrNull(spanEnd - 1).toString() - if (lastChar == " " || lastChar == "\n") endIndex -= 1 - return endIndex - } -} \ No newline at end of file diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/span/OrderedListItemSpan.kt b/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/span/OrderedListItemSpan.kt deleted file mode 100644 index fc008d79ec29caea53eb2107e55ba84a5095cee4..0000000000000000000000000000000000000000 --- a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/span/OrderedListItemSpan.kt +++ /dev/null @@ -1,56 +0,0 @@ -package org.futo.circles.feature.timeline.post.markdown.span - -import android.graphics.Canvas -import android.graphics.Paint -import android.text.Layout -import android.text.style.LeadingMarginSpan -import io.noties.markwon.core.MarkwonTheme -import io.noties.markwon.utils.LeadingMarginUtils - - -class OrderedListItemSpan( - private val theme: MarkwonTheme, - val number: String -) : LeadingMarginSpan { - private val paint = Paint(Paint.ANTI_ALIAS_FLAG) - - private var margin = 0 - override fun getLeadingMargin(first: Boolean): Int = margin.coerceAtLeast(theme.blockMargin) - - override fun drawLeadingMargin( - c: Canvas, - p: Paint, - x: Int, - dir: Int, - top: Int, - baseline: Int, - bottom: Int, - text: CharSequence, - start: Int, - end: Int, - first: Boolean, - layout: Layout - ) { - if (!first || !LeadingMarginUtils.selfStart(start, text, this)) return - - paint.set(p) - theme.applyListItemStyle(paint) - - val numberWidth = (paint.measureText(number) + .5f).toInt() - - var width = theme.blockMargin - if (numberWidth > width) { - width = numberWidth - margin = numberWidth - } else { - margin = 0 - } - val left: Int = if (dir > 0) { - x + width * dir - numberWidth - } else { - x + width * dir + (width - numberWidth) - } - - c.drawText(number, left.toFloat(), baseline.toFloat(), paint) - } -} \ No newline at end of file diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/span/TextStyle.kt b/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/span/TextStyle.kt deleted file mode 100644 index 1e06a1b9fc7773e3cdb3ef45f11ed1afd2d9bce4..0000000000000000000000000000000000000000 --- a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/span/TextStyle.kt +++ /dev/null @@ -1,28 +0,0 @@ -package org.futo.circles.feature.timeline.post.markdown.span - -import android.text.style.StrikethroughSpan -import io.noties.markwon.core.spans.BulletListItemSpan -import io.noties.markwon.core.spans.EmphasisSpan -import io.noties.markwon.core.spans.LinkSpan -import io.noties.markwon.core.spans.StrongEmphasisSpan -import io.noties.markwon.ext.tasklist.TaskListSpan - -enum class TextStyle { - BOLD, - ITALIC, - STRIKE, - LINK, - UNORDERED_LIST, - ORDERED_LIST, - TASKS_LIST -} - -fun TextStyle.toSpanClass() = when (this) { - TextStyle.BOLD -> StrongEmphasisSpan::class.java - TextStyle.ITALIC -> EmphasisSpan::class.java - TextStyle.STRIKE -> StrikethroughSpan::class.java - TextStyle.LINK -> LinkSpan::class.java - TextStyle.UNORDERED_LIST -> BulletListItemSpan::class.java - TextStyle.ORDERED_LIST -> OrderedListItemSpan::class.java - TextStyle.TASKS_LIST -> TaskListSpan::class.java -} \ No newline at end of file diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/style_bar/OptionsStyleBarAdapter.kt b/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/style_bar/OptionsStyleBarAdapter.kt deleted file mode 100644 index a45c7b440cf110f5347c9c94ca3b7d616a4413a8..0000000000000000000000000000000000000000 --- a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/style_bar/OptionsStyleBarAdapter.kt +++ /dev/null @@ -1,20 +0,0 @@ -package org.futo.circles.feature.timeline.post.markdown.style_bar - -import android.view.ViewGroup -import org.futo.circles.core.base.list.BaseRvAdapter -import org.futo.circles.model.StyleBarListItem - -class OptionsStyleBarAdapter( - private val onOptionSelected: (Int) -> Unit -) : BaseRvAdapter<StyleBarListItem, StyleBarOptionViewHolder>( - DefaultIdEntityCallback() -) { - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StyleBarOptionViewHolder { - return StyleBarOptionViewHolder(parent = parent) { onOptionSelected(getItem(it).id) } - } - - override fun onBindViewHolder(holder: StyleBarOptionViewHolder, position: Int) { - holder.bind(getItem(position)) - } -} \ No newline at end of file diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/style_bar/StyleBarOptionViewHolder.kt b/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/style_bar/StyleBarOptionViewHolder.kt deleted file mode 100644 index 0ab9ac3284b48a4298337ef8761fa6e8ee9d6e4d..0000000000000000000000000000000000000000 --- a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/style_bar/StyleBarOptionViewHolder.kt +++ /dev/null @@ -1,36 +0,0 @@ -package org.futo.circles.feature.timeline.post.markdown.style_bar - -import android.view.ViewGroup -import androidx.core.content.ContextCompat -import androidx.recyclerview.widget.RecyclerView -import org.futo.circles.R -import org.futo.circles.core.extensions.onClick -import org.futo.circles.core.base.list.ViewBindingHolder -import org.futo.circles.core.base.list.context -import org.futo.circles.databinding.ListItemStyleBarBinding -import org.futo.circles.model.StyleBarListItem - -class StyleBarOptionViewHolder( - parent: ViewGroup, onItemClicked: (Int) -> Unit -) : RecyclerView.ViewHolder( - inflate(parent, ListItemStyleBarBinding::inflate) -) { - private companion object : ViewBindingHolder - - private val binding = baseBinding as ListItemStyleBarBinding - - init { - onClick(binding.cvOption) { onItemClicked(it) } - } - - fun bind(data: StyleBarListItem) { - binding.ivIcon.setImageResource(data.iconResId) - binding.cvOption.setCardBackgroundColor( - ContextCompat.getColor( - context, if (data.isSelected) R.color.blue else - org.futo.circles.core.R.color.post_card_background_color - ) - ) - } -} - diff --git a/app/src/main/java/org/futo/circles/view/DimensionConverter.kt b/app/src/main/java/org/futo/circles/view/DimensionConverter.kt new file mode 100644 index 0000000000000000000000000000000000000000..2225c471de1b53bae1b9daee055daa446d91d6bc --- /dev/null +++ b/app/src/main/java/org/futo/circles/view/DimensionConverter.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 org.futo.circles.view + +import android.content.res.Resources +import android.util.TypedValue +import androidx.annotation.Px +import javax.inject.Inject + +class DimensionConverter @Inject constructor(val resources: Resources) { + + @Px + fun dpToPx(dp: Int): Int { + return TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + dp.toFloat(), + resources.displayMetrics + ).toInt() + } + + @Px + fun spToPx(sp: Int): Int { + return TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_SP, + sp.toFloat(), + resources.displayMetrics + ).toInt() + } + + fun pxToDp(@Px px: Int): Int { + return (px.toFloat() / resources.displayMetrics.density).toInt() + } +} diff --git a/app/src/main/java/org/futo/circles/view/MarkdownEditText.kt b/app/src/main/java/org/futo/circles/view/MarkdownEditText.kt deleted file mode 100644 index 04efcc95af6916f148330cf1d739c8bc0df3a913..0000000000000000000000000000000000000000 --- a/app/src/main/java/org/futo/circles/view/MarkdownEditText.kt +++ /dev/null @@ -1,304 +0,0 @@ -package org.futo.circles.view - - -import android.content.Context -import android.graphics.Color -import android.graphics.drawable.ColorDrawable -import android.text.Editable -import android.text.Spannable -import android.text.Spanned -import android.text.style.ClickableSpan -import android.text.style.StrikethroughSpan -import android.util.AttributeSet -import android.view.View -import androidx.appcompat.widget.AppCompatEditText -import androidx.core.content.ContextCompat -import androidx.core.widget.doOnTextChanged -import io.noties.markwon.LinkResolverDef -import io.noties.markwon.Markwon -import io.noties.markwon.core.spans.BulletListItemSpan -import io.noties.markwon.core.spans.EmphasisSpan -import io.noties.markwon.core.spans.LinkSpan -import io.noties.markwon.core.spans.StrongEmphasisSpan -import io.noties.markwon.ext.tasklist.TaskListDrawable -import io.noties.markwon.ext.tasklist.TaskListSpan -import org.futo.circles.R -import org.futo.circles.core.feature.autocomplete.Autocomplete -import org.futo.circles.core.feature.autocomplete.AutocompleteCallback -import org.futo.circles.core.feature.autocomplete.CharPolicy -import org.futo.circles.core.model.UserListItem -import org.futo.circles.extensions.getGivenSpansAt -import org.futo.circles.feature.timeline.post.markdown.EnhancedMovementMethod -import org.futo.circles.feature.timeline.post.markdown.MarkdownParser -import org.futo.circles.feature.timeline.post.markdown.mentions.MentionsPresenter -import org.futo.circles.feature.timeline.post.markdown.span.MentionSpan -import org.futo.circles.feature.timeline.post.markdown.span.OrderedListItemSpan -import org.futo.circles.feature.timeline.post.markdown.span.TextStyle - - -class MarkdownEditText( - context: Context, - attrs: AttributeSet? = null -) : AppCompatEditText(context, attrs) { - - private val markwon: Markwon - private var isSelectionStyling = false - private var listSpanStart = 0 - private var currentListSpanNumber = 0 - private var currentListSpanLine = 0 - private val taskBoxColor by lazy { ContextCompat.getColor(context, R.color.blue) } - private val taskBoxMarkColor = Color.WHITE - private val textStyles = - arrayOf(TextStyle.BOLD, TextStyle.ITALIC, TextStyle.STRIKE) - private val listStyles = - arrayOf(TextStyle.UNORDERED_LIST, TextStyle.ORDERED_LIST, TextStyle.TASKS_LIST) - private var onHighlightSpanListener: ((List<TextStyle>) -> Unit)? = null - private val selectedStyles = mutableSetOf<TextStyle>() - - init { - movementMethod = EnhancedMovementMethod().getsInstance() - markwon = MarkdownParser.markwonBuilder(context) - doOnTextChanged { _, start, before, count -> - styliseText(start, start + count) - handleListSpanTextChange(before, count) - } - } - - override fun getText(): Editable { - return super.getText() ?: Editable.Factory.getInstance().newEditable("") - } - - fun getTextWithMarkdown() = MarkdownParser.editableToMarkdown(text) - - fun setHighlightSelectedSpanListener(onHighlight: (List<TextStyle>) -> Unit) { - onHighlightSpanListener = onHighlight - } - - fun insertMentionMark() { - insertText(MarkdownParser.mentionMark) - } - - fun insertText(message: String) { - text.insert(selectionStart, message) - } - - fun triggerStyle(textStyle: TextStyle, isSelected: Boolean) { - if (isSelected) { - selectOnlyOneListStyleIfNeed(textStyle) - selectedStyles.add(textStyle) - } else selectedStyles.remove(textStyle) - handleSelectionStylingIfNeed() - onHighlightSpanListener?.invoke(selectedStyles.toList()) - } - - fun initMentionsAutocomplete(roomId: String) { - Autocomplete.on<UserListItem>(this) - .with(CharPolicy('@', true)) - .with(MentionsPresenter(context, roomId)) - .with( - ColorDrawable( - ContextCompat.getColor( - context, - org.futo.circles.core.R.color.post_card_background_color - ) - ) - ) - .with(object : AutocompleteCallback<UserListItem> { - override fun onPopupItemClicked(editable: Editable, item: UserListItem): Boolean { - val range = CharPolicy.getQueryRange(editable) ?: return false - insertMentionSpan(editable, item.user.name, range[0]) - return true - } - - override fun onPopupVisibilityChanged(shown: Boolean) { - } - }) - .with(6f) - .build() - } - - private fun handleSelectionStylingIfNeed() { - if (!isSelectionStyling) return - text.getGivenSpansAt(span = textStyles, selectionStart, selectionEnd).forEach { - text.removeSpan(it) - } - styliseText(selectionStart, selectionEnd) - } - - private fun selectOnlyOneListStyleIfNeed(textStyle: TextStyle) { - if (textStyle !in listStyles) return - selectedStyles.removeAll { it in listStyles } - triggerListStyle(textStyle) - } - - private fun triggerListStyle(listSpanStyle: TextStyle) { - currentListSpanNumber = 1 - val currentLineStart = layout.getLineStart(getCurrentCursorLine()) - if (selectionStart == currentLineStart) - text.insert(selectionStart, " ") - else text.insert(selectionStart, "\n ") - - listSpanStart = selectionStart - 1 - text.setSpan( - getListSpan(listSpanStyle, "${currentListSpanNumber}.", false), - listSpanStart, - selectionStart, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE - ) - currentListSpanNumber++ - currentListSpanLine = lineCount - } - - private fun handleListSpanTextChange(before: Int, count: Int) { - val listSpanStyle = selectedStyles.firstOrNull { it in listStyles } ?: return - if (before > count) return - if (selectionStart == selectionEnd && currentListSpanLine < lineCount) { - currentListSpanLine = lineCount - val string = text.toString() - // If user hit enter - if (string[selectionStart - 1] == '\n') { - listSpanStart = selectionStart - text.insert(selectionStart, " ") - text.setSpan( - getListSpan(listSpanStyle, "${currentListSpanNumber}.", false), - listSpanStart, - listSpanStart + 1, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE - ) - currentListSpanNumber++ - } else { - for (listSpan in text.getGivenSpansAt( - span = arrayOf(listSpanStyle), - listSpanStart, - listSpanStart + 1 - )) { - val number = (listSpan as? OrderedListItemSpan)?.number ?: "" - val isDone = (listSpan as? TaskListSpan)?.isDone ?: false - text.removeSpan(listSpan) - text.setSpan( - getListSpan(listSpanStyle, number, isDone), - listSpanStart, - selectionStart, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE - ) - } - } - } - } - - private fun getListSpan(listSpanStyle: TextStyle, currentNum: String, isDone: Boolean): Any = - when (listSpanStyle) { - TextStyle.ORDERED_LIST -> OrderedListItemSpan( - markwon.configuration().theme(), - currentNum - ) - - TextStyle.TASKS_LIST -> setTaskSpan(listSpanStart, selectionStart, isDone) - else -> BulletListItemSpan(markwon.configuration().theme(), 0) - } - - fun addLinkSpan(title: String?, link: String) { - val newTitle = if (title.isNullOrEmpty()) link else title - val cursorStart = selectionStart - text.insert(cursorStart, newTitle) - text.setSpan( - LinkSpan(markwon.configuration().theme(), link, LinkResolverDef()), - cursorStart, - cursorStart + newTitle.length, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE - ) - } - - private fun insertMentionSpan(editable: Editable, name: String, start: Int) { - editable.setSpan( - MentionSpan(context, name), start - 1, start, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE - ) - } - - private fun setTaskSpan(start: Int, end: Int, isDone: Boolean) { - val taskSpan = TaskListSpan( - markwon.configuration().theme(), - TaskListDrawable(taskBoxColor, taskBoxColor, taskBoxMarkColor), - isDone - ) - text.setSpan(taskSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) - text.setSpan(getTaskClickableSpan(taskSpan), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) - } - - private fun styliseText(start: Int, end: Int) { - if (start >= end) return - if (text.substring(start, end).isBlank()) return - selectedStyles.forEach { textStyle -> - val span = when (textStyle) { - TextStyle.BOLD -> StrongEmphasisSpan() - TextStyle.ITALIC -> EmphasisSpan() - TextStyle.STRIKE -> StrikethroughSpan() - else -> null - } - span?.let { - if (text.getGivenSpansAt(span = arrayOf(textStyle), start, end).isEmpty()) - text.setSpan(it, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) - } - } - } - - override fun onSelectionChanged(selStart: Int, selEnd: Int) { - super.onSelectionChanged(selStart, selEnd) - isSelectionStyling = selStart != selEnd - if (selStart <= 0) return - if (isDividerSymbol(selStart - 1) && !isSelectionStyling) return - - val spans = mutableSetOf<TextStyle>() - val currentLineStart = layout.getLineStart(getCurrentCursorLine()) - val listsSpans = text.getGivenSpansAt( - span = listStyles, - start = currentLineStart, end = currentLineStart + 1 - ) - listsSpans.forEach { - when (it) { - is BulletListItemSpan -> spans.add(TextStyle.UNORDERED_LIST) - is OrderedListItemSpan -> spans.add(TextStyle.ORDERED_LIST) - is TaskListSpan -> spans.add(TextStyle.TASKS_LIST) - } - } - val textStart = if (isSelectionStyling) selStart else selStart - 1 - val textEnd = if (isSelectionStyling) selEnd else selStart - val textSpans = text.getGivenSpansAt( - span = textStyles, - start = textStart, end = textEnd - ) - textSpans.forEach { - when (it) { - is StrongEmphasisSpan -> spans.add(TextStyle.BOLD) - is EmphasisSpan -> spans.add(TextStyle.ITALIC) - is StrikethroughSpan -> spans.add(TextStyle.STRIKE) - } - } - if (spans != selectedStyles) { - selectedStyles.clear() - selectedStyles.addAll(spans) - } - onHighlightSpanListener?.invoke(spans.toList()) - } - - private fun isDividerSymbol(index: Int): Boolean { - val char = text.getOrNull(index).toString() - return char == " " || char == "\n" - } - - private fun getTaskClickableSpan(taskSpan: TaskListSpan) = object : ClickableSpan() { - override fun onClick(widget: View) { - val spanStart = text.getSpanStart(taskSpan) - val spanEnd = text.getSpanEnd(taskSpan) - taskSpan.isDone = !taskSpan.isDone - if (spanStart >= 0) { - text.setSpan(taskSpan, spanStart, spanEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) - } - } - } - - private fun getCurrentCursorLine(): Int { - return if (selectionStart != -1) layout.getLineForOffset(selectionStart) else -1 - } -} \ No newline at end of file diff --git a/app/src/main/java/org/futo/circles/view/MarkdownStyleBar.kt b/app/src/main/java/org/futo/circles/view/MarkdownStyleBar.kt deleted file mode 100644 index 3af36c28d269f94108e4c1e0c5fe8fe493412e10..0000000000000000000000000000000000000000 --- a/app/src/main/java/org/futo/circles/view/MarkdownStyleBar.kt +++ /dev/null @@ -1,98 +0,0 @@ -package org.futo.circles.view - -import android.content.Context -import android.util.AttributeSet -import android.view.LayoutInflater -import androidx.constraintlayout.widget.ConstraintLayout -import org.futo.circles.R -import org.futo.circles.core.extensions.setIsVisible -import org.futo.circles.databinding.ViewMarkdownStylebarBinding -import org.futo.circles.feature.timeline.post.create.PostConfigurationOptionListener -import org.futo.circles.model.MainStyleBarOption -import org.futo.circles.model.StyleBarListItem -import org.futo.circles.feature.timeline.post.markdown.span.TextStyle -import org.futo.circles.feature.timeline.post.markdown.style_bar.OptionsStyleBarAdapter - -class MarkdownStyleBar( - context: Context, - attrs: AttributeSet? = null -) : ConstraintLayout(context, attrs) { - - private val binding = - ViewMarkdownStylebarBinding.inflate(LayoutInflater.from(context), this) - - private val mainOptions = listOf( - StyleBarListItem(MainStyleBarOption.Media.ordinal, R.drawable.ic_image), - StyleBarListItem(MainStyleBarOption.Emoji.ordinal, R.drawable.ic_emoji), - StyleBarListItem(MainStyleBarOption.Mention.ordinal, R.drawable.ic_mention), - StyleBarListItem(MainStyleBarOption.Link.ordinal, R.drawable.ic_link), - StyleBarListItem(MainStyleBarOption.TextStyle.ordinal, R.drawable.ic_text) - ) - - private var textStyleOptions = listOf( - StyleBarListItem(TextStyle.BOLD.ordinal, R.drawable.ic_bold), - StyleBarListItem(TextStyle.ITALIC.ordinal, R.drawable.ic_italic), - StyleBarListItem(TextStyle.STRIKE.ordinal, R.drawable.ic_strikethrough), - StyleBarListItem(TextStyle.UNORDERED_LIST.ordinal, R.drawable.ic_bullet_list), - StyleBarListItem(TextStyle.ORDERED_LIST.ordinal, R.drawable.ic_number_list), - StyleBarListItem(TextStyle.TASKS_LIST.ordinal, R.drawable.ic_check_box) - ) - - private val mainOptionsAdapter = OptionsStyleBarAdapter(::onMainOptionSelected) - private val textStyleOptionsAdapter = - OptionsStyleBarAdapter(::onTextStyleSelected).apply { submitList(textStyleOptions) } - - private var postConfigurationListener: PostConfigurationOptionListener? = null - - private var isTextOptionsOpened = false - - init { - setupViews() - } - - fun setOptionsListener(listener: PostConfigurationOptionListener) { - postConfigurationListener = listener - } - - fun showMainOptionsList(isMediaAvailable: Boolean) { - mainOptionsAdapter.submitList( - if (isMediaAvailable) mainOptions - else mainOptions.filter { it.id != MainStyleBarOption.Media.ordinal } - ) - } - - fun highlightStyle(textStyles: List<TextStyle>) { - textStyleOptions = textStyleOptions.map { - it.copy(isSelected = it.id in textStyles.map { it.ordinal }) - } - textStyleOptionsAdapter.submitList(textStyleOptions) - } - - private fun setupViews() { - setTextOptionsOpened(false) - binding.ivCancel.setOnClickListener { setTextOptionsOpened(false) } - binding.rvMainOptions.adapter = mainOptionsAdapter - binding.rvTextOptions.adapter = textStyleOptionsAdapter - } - - private fun setTextOptionsOpened(isOpened: Boolean) { - isTextOptionsOpened = isOpened - binding.rvMainOptions.setIsVisible(!isTextOptionsOpened) - binding.textOptionsGroup.setIsVisible(isTextOptionsOpened) - } - - private fun onMainOptionSelected(id: Int) { - when (MainStyleBarOption.values()[id]) { - MainStyleBarOption.Media -> postConfigurationListener?.onUploadMediaClicked() - MainStyleBarOption.Emoji -> postConfigurationListener?.onEmojiClicked() - MainStyleBarOption.Mention -> postConfigurationListener?.onMentionClicked() - MainStyleBarOption.Link -> postConfigurationListener?.onAddLinkClicked() - MainStyleBarOption.TextStyle -> setTextOptionsOpened(true) - } - } - - private fun onTextStyleSelected(id: Int) { - val item = textStyleOptions.firstOrNull { it.id == id } ?: return - postConfigurationListener?.onTextStyleSelected(TextStyle.values()[id], !item.isSelected) - } -} \ No newline at end of file diff --git a/app/src/main/java/org/futo/circles/view/PostLayout.kt b/app/src/main/java/org/futo/circles/view/PostLayout.kt index 40f74e3a9b9fef7ad19afddb08b2ab951f4df7de..808ffc9dd615465989ee663fb6a9b71ca99621a2 100644 --- a/app/src/main/java/org/futo/circles/view/PostLayout.kt +++ b/app/src/main/java/org/futo/circles/view/PostLayout.kt @@ -17,7 +17,7 @@ import org.futo.circles.core.model.Post import org.futo.circles.core.model.PostContent import org.futo.circles.core.model.TextContent import org.futo.circles.databinding.LayoutPostBinding -import org.futo.circles.feature.timeline.post.markdown.MarkdownParser +import org.futo.circles.core.feature.markdown.MarkdownParser import org.futo.circles.model.PostItemPayload import org.matrix.android.sdk.api.session.room.send.SendState @@ -114,10 +114,10 @@ class PostLayout( private fun setMentionBorder(content: PostContent) { val hasMention = when (content) { is MediaContent -> content.caption?.let { - MarkdownParser.hasCurrentUserMention(it) + MarkdownParser.hasCurrentUserMention(it.toString()) } ?: false - is TextContent -> MarkdownParser.hasCurrentUserMention(content.message) + is TextContent -> MarkdownParser.hasCurrentUserMention(content.message.toString()) is PollContent -> false } if (hasMention) binding.lCard.setBackgroundResource(R.drawable.bg_mention_highlight) diff --git a/app/src/main/java/org/futo/circles/view/PreviewPostView.kt b/app/src/main/java/org/futo/circles/view/PreviewPostView.kt index 06264d5c6ee84a872fb651b9d734fc366dbdce93..f18fd33c2bf0edd88bd4c59695fe847706db7ccb 100644 --- a/app/src/main/java/org/futo/circles/view/PreviewPostView.kt +++ b/app/src/main/java/org/futo/circles/view/PreviewPostView.kt @@ -2,39 +2,56 @@ package org.futo.circles.view import android.content.Context +import android.graphics.drawable.ColorDrawable import android.net.Uri +import android.text.Editable import android.util.AttributeSet import android.view.LayoutInflater import android.view.inputmethod.InputMethodManager -import android.widget.TextView +import android.widget.LinearLayout +import androidx.annotation.DrawableRes +import androidx.cardview.widget.CardView import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.ContextCompat +import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.core.widget.doAfterTextChanged -import org.futo.circles.core.extensions.loadEncryptedIntoWithAspect +import io.element.android.wysiwyg.EditorEditText +import io.element.android.wysiwyg.display.LinkDisplayHandler +import io.element.android.wysiwyg.display.TextDisplay +import io.element.android.wysiwyg.view.models.InlineFormat +import org.futo.circles.R import org.futo.circles.core.extensions.loadEncryptedThumbOrFullIntoWithAspect import org.futo.circles.core.extensions.loadImage import org.futo.circles.core.extensions.notEmptyDisplayName import org.futo.circles.core.extensions.setIsVisible +import org.futo.circles.core.feature.autocomplete.Autocomplete +import org.futo.circles.core.feature.autocomplete.AutocompleteCallback +import org.futo.circles.core.feature.autocomplete.CharPolicy +import org.futo.circles.core.feature.markdown.mentions.MentionsLinkDisplayHandler +import org.futo.circles.core.feature.markdown.mentions.MentionsPresenter +import org.futo.circles.core.feature.markdown.span.MentionSpan import org.futo.circles.core.model.MediaContent import org.futo.circles.core.model.MediaType +import org.futo.circles.core.model.UserListItem import org.futo.circles.core.provider.MatrixSessionProvider import org.futo.circles.core.utils.ImageUtils import org.futo.circles.core.utils.VideoUtils import org.futo.circles.core.utils.VideoUtils.getVideoDuration import org.futo.circles.core.utils.VideoUtils.getVideoDurationString import org.futo.circles.databinding.ViewPreviewPostBinding +import org.futo.circles.databinding.ViewRichTextMenuButtonBinding import org.futo.circles.extensions.convertDpToPixel -import org.futo.circles.feature.timeline.post.markdown.MarkdownParser -import org.futo.circles.feature.timeline.post.markdown.span.TextStyle +import org.futo.circles.feature.timeline.post.create.PreviewPostListener import org.futo.circles.model.CreatePostContent import org.futo.circles.model.MediaPostContent import org.futo.circles.model.TextPostContent +import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.session.getUser +import org.matrix.android.sdk.api.session.permalinks.PermalinkService.Companion.MATRIX_TO_URL_BASE import org.matrix.android.sdk.api.session.user.model.User - -interface PreviewPostListener { - fun onPostContentAvailable(isAvailable: Boolean) -} +import uniffi.wysiwyg_composer.ActionState +import uniffi.wysiwyg_composer.ComposerAction class PreviewPostView( context: Context, @@ -59,47 +76,62 @@ class PreviewPostView( ) } setOnClickListener { requestFocusOnText() } - binding.etTextPost.doAfterTextChanged { - listener?.onPostContentAvailable(it?.toString()?.isNotBlank() == true) - } + binding.ivRemoveImage.setOnClickListener { setTextContent() } + updateContentView() + + binding.btnSend.setOnClickListener { listener?.onSendClicked(getPostContent()) } + binding.ivCancel.setOnClickListener { setTextEditorMode(false) } + + with(binding.etTextPost) { + doAfterTextChanged { + binding.btnSend.isEnabled = it?.toString()?.isNotBlank() == true + } + linkDisplayHandler = MentionsLinkDisplayHandler(context) + } + setupRichTextMenu() + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + binding.etTextPost.actionStatesChangedListener = + EditorEditText.OnActionStatesChangedListener { state -> + for (action in state.keys) { + updateMenuStateFor(action, state) + } + } + } + + override fun onDetachedFromWindow() { + binding.etTextPost.setMarkdown("") + super.onDetachedFromWindow() } fun setup( previewPostListener: PreviewPostListener, - onHighlightTextStyle: (List<TextStyle>) -> Unit, - roomId: String + roomId: String, + isEdit: Boolean ) { + setTextEditorMode(false) listener = previewPostListener - binding.etTextPost.setHighlightSelectedSpanListener(onHighlightTextStyle) - binding.etTextPost.initMentionsAutocomplete(roomId) + setupMainMenu(!isEdit) + initMentionsAutocomplete(roomId) } fun setText(message: String) { - binding.etTextPost.setText( - MarkdownParser.markwonBuilder(context).toMarkdown(message), - TextView.BufferType.SPANNABLE - ) + binding.etTextPost.setFormattedMarkdown(message) setTextContent() } - fun setTextStyle(style: TextStyle, isSelected: Boolean) { - binding.etTextPost.triggerStyle(style, isSelected) - } - fun insertEmoji(unicode: String) { - binding.etTextPost.insertText(unicode) - } - - fun insertMention() { - binding.etTextPost.insertMentionMark() + binding.etTextPost.append(unicode) } fun insertLink(title: String?, link: String) { - binding.etTextPost.addLinkSpan(title, link) + binding.etTextPost.insertLink(link, title ?: link) } fun setMediaFromExistingPost(mediaContent: MediaContent) { @@ -116,7 +148,7 @@ class PreviewPostView( if (isVideo) binding.lMediaContent.tvDuration.text = mediaContent.mediaFileData.duration - listener?.onPostContentAvailable(true) + binding.btnSend.isEnabled = true } @@ -131,12 +163,12 @@ class PreviewPostView( binding.lMediaContent.tvDuration.text = getVideoDurationString(getVideoDuration(context, contentUri)) - listener?.onPostContentAvailable(true) + binding.btnSend.isEnabled = true } - fun getPostContent() = (postContent as? MediaPostContent)?.copy( - caption = binding.etTextPost.getTextWithMarkdown().trim().takeIf { it.isNotEmpty() } - ) ?: TextPostContent(binding.etTextPost.getTextWithMarkdown().trim()) + private fun getPostContent() = (postContent as? MediaPostContent)?.copy( + caption = binding.etTextPost.getFormattedMarkdown().takeIf { it.isNotEmpty() } + ) ?: TextPostContent(binding.etTextPost.getFormattedMarkdown()) private fun updateContentView() { val isTextContent = postContent is TextPostContent || postContent == null @@ -153,7 +185,7 @@ class PreviewPostView( private fun setTextContent() { postContent = null updateContentView() - listener?.onPostContentAvailable(binding.etTextPost.text.toString().isNotBlank()) + binding.btnSend.isEnabled = false } private fun loadMediaCover(uri: Uri, mediaType: MediaType) { @@ -183,10 +215,38 @@ class PreviewPostView( mediaContent.loadEncryptedThumbOrFullIntoWithAspect(image) } + private fun initMentionsAutocomplete(roomId: String) { + Autocomplete.on<UserListItem>(binding.etTextPost) + .with(CharPolicy('@', true)) + .with(MentionsPresenter(context, roomId)) + .with( + ColorDrawable( + ContextCompat.getColor( + context, + org.futo.circles.core.R.color.post_card_background_color + ) + ) + ) + .with(object : AutocompleteCallback<UserListItem> { + override fun onPopupItemClicked(editable: Editable, item: UserListItem): Boolean { + binding.etTextPost.setLinkSuggestion( + MATRIX_TO_URL_BASE + item.id, + "@${item.user.name}@" + ) + return true + } + + override fun onPopupVisibilityChanged(shown: Boolean) { + } + }) + .with(6f) + .build() + } + private fun requestFocusOnText() { binding.etTextPost.post { requestFocus() - binding.etTextPost.setSelection(binding.etTextPost.text.length) + binding.etTextPost.text?.let { binding.etTextPost.setSelection(it.length) } showKeyboard() } } @@ -201,4 +261,119 @@ class PreviewPostView( val session = MatrixSessionProvider.currentSession return session?.myUserId?.let { session.getUser(it) } } + + private fun setupMainMenu(isMediaAvailable: Boolean) { + if (isMediaAvailable) { + addMenuItem(binding.lMainMenu, R.drawable.ic_image) { + listener?.onUploadMediaClicked() + } + } + addMenuItem(binding.lMainMenu, R.drawable.ic_emoji) { + listener?.onEmojiClicked() + } + addMenuItem(binding.lMainMenu, org.futo.circles.core.R.drawable.ic_mention) { + binding.etTextPost.append("@") + } + addMenuItem(binding.lMainMenu, R.drawable.ic_link) { + listener?.onAddLinkClicked() + } + addMenuItem(binding.lMainMenu, R.drawable.ic_text) { + setTextEditorMode(true) + } + } + + private fun setTextEditorMode(isEnabled: Boolean) { + binding.ivCancel.setIsVisible(isEnabled) + binding.lTextMenu.setIsVisible(isEnabled) + binding.lMainMenu.setIsVisible(!isEnabled) + } + + private fun setupRichTextMenu() { + addMenuItem( + binding.lTextMenu, + R.drawable.ic_bold, + ComposerAction.BOLD + ) { + binding.etTextPost.toggleInlineFormat(InlineFormat.Bold) + } + addMenuItem( + binding.lTextMenu, + R.drawable.ic_italic, + ComposerAction.ITALIC + ) { + binding.etTextPost.toggleInlineFormat(InlineFormat.Italic) + } + addMenuItem( + binding.lTextMenu, + R.drawable.ic_strikethrough, + ComposerAction.STRIKE_THROUGH + ) { + binding.etTextPost.toggleInlineFormat(InlineFormat.StrikeThrough) + } + addMenuItem( + binding.lTextMenu, + R.drawable.ic_bullet_list, + ComposerAction.UNORDERED_LIST + ) { + binding.etTextPost.toggleList(ordered = false) + } + addMenuItem( + binding.lTextMenu, + R.drawable.ic_number_list, + ComposerAction.ORDERED_LIST + ) { + binding.etTextPost.toggleList(ordered = true) + } + addMenuItem( + binding.lTextMenu, + R.drawable.ic_composer_quote, + ComposerAction.QUOTE + ) { + binding.etTextPost.toggleQuote() + } + } + + private fun addMenuItem( + container: LinearLayout, + @DrawableRes iconId: Int, + action: ComposerAction? = null, + onClick: () -> Unit + ) { + val inflater = LayoutInflater.from(context) + val item = ViewRichTextMenuButtonBinding.inflate(inflater, container, true) + action?.let { + item.root.tag = it + } + with(item.root) { + item.ivIcon.setImageResource(iconId) + setOnClickListener { onClick() } + } + } + + private fun updateMenuStateFor( + action: ComposerAction, + menuState: Map<ComposerAction, ActionState> + ) { + val button = findViewWithTag<CardView>(action) ?: return + val stateForAction = menuState[action] + button.isEnabled = stateForAction != ActionState.DISABLED + button.isSelected = stateForAction == ActionState.REVERSED + + if (action == ComposerAction.INDENT || action == ComposerAction.UNINDENT) { + val indentationButtonIsVisible = + menuState[ComposerAction.ORDERED_LIST] == ActionState.REVERSED || + menuState[ComposerAction.UNORDERED_LIST] == ActionState.REVERSED + button.isVisible = indentationButtonIsVisible + } + } + + private fun EditorEditText.getFormattedMarkdown(): String = getMarkdown() + .replace("<br><br>", "") + .replace("\\", "") + + private fun EditorEditText.setFormattedMarkdown(message: String) { + val formattedMessage = message.replace("\\r\\r|\\n\\n".toRegex(), "<br><br>") + setMarkdown(formattedMessage) + } + } \ No newline at end of file diff --git a/app/src/main/java/org/futo/circles/view/SimpleTextWatcher.kt b/app/src/main/java/org/futo/circles/view/SimpleTextWatcher.kt new file mode 100644 index 0000000000000000000000000000000000000000..d471738bd4a6fecc6bf8f737e20858a25a5a0e78 --- /dev/null +++ b/app/src/main/java/org/futo/circles/view/SimpleTextWatcher.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 org.futo.circles.view + +import android.text.Editable +import android.text.TextWatcher + +/** + * TextWatcher with default no op implementation. + */ +open class SimpleTextWatcher : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { + // No op + } + + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + // No op + } + + override fun afterTextChanged(s: Editable) { + // No op + } +} diff --git a/app/src/main/java/org/futo/circles/view/UriContentListener.kt b/app/src/main/java/org/futo/circles/view/UriContentListener.kt new file mode 100644 index 0000000000000000000000000000000000000000..3cc04857fddff947b9ee079e09e7455eb073e60d --- /dev/null +++ b/app/src/main/java/org/futo/circles/view/UriContentListener.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * 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 org.futo.circles.view + +import android.content.ClipData +import android.net.Uri +import android.view.View +import androidx.core.view.ContentInfoCompat +import androidx.core.view.OnReceiveContentListener + +class UriContentListener( + private val onContent: (uri: Uri) -> Unit +) : OnReceiveContentListener { + override fun onReceiveContent(view: View, payload: ContentInfoCompat): ContentInfoCompat? { + val split = payload.partition { item -> item.uri != null } + val uriContent = split.first + val remaining = split.second + + if (uriContent != null) { + val clip: ClipData = uriContent.clip + for (i in 0 until clip.itemCount) { + val uri = clip.getItemAt(i).uri + // ... app-specific logic to handle the URI ... + onContent(uri) + } + } + // Return anything that we didn't handle ourselves. This preserves the default platform + // behavior for text and anything else for which we are not implementing custom handling. + return remaining + } +} diff --git a/app/src/main/res/drawable/bg_code_block.xml b/app/src/main/res/drawable/bg_code_block.xml new file mode 100644 index 0000000000000000000000000000000000000000..ef7227f6842b28bc38823dbcb26415fed7bde875 --- /dev/null +++ b/app/src/main/res/drawable/bg_code_block.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="#F4F6FA" /> + <stroke + android:width="@dimen/code_block_border_width" + android:color="#E3E8F0" /> + <corners android:radius="@dimen/code_block_border_radius" /> +</shape> diff --git a/app/src/main/res/drawable/bg_rich_text_menu_button.xml b/app/src/main/res/drawable/bg_rich_text_menu_button.xml new file mode 100644 index 0000000000000000000000000000000000000000..03af9b25b9240b1888d8403bb7917adcfcf4efe9 --- /dev/null +++ b/app/src/main/res/drawable/bg_rich_text_menu_button.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@color/blue" android:state_selected="true" /> + <item android:color="@color/red" android:state_enabled="false" /> + <item android:color="@color/post_card_background_color" /> +</selector> diff --git a/app/src/main/res/drawable/ic_composer_bold.xml b/app/src/main/res/drawable/ic_composer_bold.xml new file mode 100644 index 0000000000000000000000000000000000000000..d2e26cf1e59a3105483837beaadd6acbfeadf1cb --- /dev/null +++ b/app/src/main/res/drawable/ic_composer_bold.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="44dp" + android:height="44dp" + android:viewportWidth="44" + android:viewportHeight="44"> + <path + android:fillColor="@color/blue" + android:fillType="evenOdd" + android:pathData="M16,14.5C16,13.672 16.672,13 17.5,13H22.288C25.139,13 27.25,15.466 27.25,18.25C27.25,19.38 26.902,20.458 26.298,21.34C27.765,22.268 28.75,23.882 28.75,25.75C28.75,28.689 26.311,31 23.393,31H17.5C16.672,31 16,30.328 16,29.5V14.5ZM19,16V20.5H22.288C23.261,20.5 24.25,19.608 24.25,18.25C24.25,16.892 23.261,16 22.288,16H19ZM19,23.5V28H23.393C24.735,28 25.75,26.953 25.75,25.75C25.75,24.547 24.735,23.5 23.393,23.5H19Z" /> +</vector> diff --git a/app/src/main/res/drawable/ic_composer_bullet_list.xml b/app/src/main/res/drawable/ic_composer_bullet_list.xml new file mode 100644 index 0000000000000000000000000000000000000000..d372209ced344182d558b43d73f89df20eaa9307 --- /dev/null +++ b/app/src/main/res/drawable/ic_composer_bullet_list.xml @@ -0,0 +1,12 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="44dp" + android:height="44dp" + android:viewportWidth="44" + android:viewportHeight="44"> + <group> + <clip-path android:pathData="M10,10h24v24h-24z" /> + <path + android:fillColor="@color/blue" + android:pathData="M14,20.5C13.17,20.5 12.5,21.17 12.5,22C12.5,22.83 13.17,23.5 14,23.5C14.83,23.5 15.5,22.83 15.5,22C15.5,21.17 14.83,20.5 14,20.5ZM14,14.5C13.17,14.5 12.5,15.17 12.5,16C12.5,16.83 13.17,17.5 14,17.5C14.83,17.5 15.5,16.83 15.5,16C15.5,15.17 14.83,14.5 14,14.5ZM14,26.5C13.17,26.5 12.5,27.18 12.5,28C12.5,28.82 13.18,29.5 14,29.5C14.82,29.5 15.5,28.82 15.5,28C15.5,27.18 14.83,26.5 14,26.5ZM18,29H30C30.55,29 31,28.55 31,28C31,27.45 30.55,27 30,27H18C17.45,27 17,27.45 17,28C17,28.55 17.45,29 18,29ZM18,23H30C30.55,23 31,22.55 31,22C31,21.45 30.55,21 30,21H18C17.45,21 17,21.45 17,22C17,22.55 17.45,23 18,23ZM17,16C17,16.55 17.45,17 18,17H30C30.55,17 31,16.55 31,16C31,15.45 30.55,15 30,15H18C17.45,15 17,15.45 17,16Z" /> + </group> +</vector> diff --git a/app/src/main/res/drawable/ic_composer_code_block.xml b/app/src/main/res/drawable/ic_composer_code_block.xml new file mode 100644 index 0000000000000000000000000000000000000000..aaa3dbe78da6559eef7d6eb4c6c35e1dd7c9b32b --- /dev/null +++ b/app/src/main/res/drawable/ic_composer_code_block.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="44dp" + android:height="44dp" + android:tint="@color/menu_icon_color" + android:viewportWidth="44" + android:viewportHeight="44"> + <path + android:fillColor="@color/white" + android:pathData="m18,22c0.7,-0.72 1.4,-1.4 2.1,-2.1 0.68,-0.98 -0.93,-1.9 -1.5,-0.96 -0.87,0.88 -1.8,1.7 -2.6,2.6 -0.45,0.67 0.27,1.2 0.7,1.6 0.75,0.74 1.5,1.5 2.2,2.2 0.98,0.68 1.9,-0.93 0.96,-1.5l-1.9,-1.9zM26.6,22c-0.71,0.72 -1.5,1.4 -2.1,2.2 -0.68,0.98 0.93,1.9 1.5,0.96 0.88,-0.89 1.8,-1.8 2.6,-2.7 0.45,-0.67 -0.27,-1.2 -0.7,-1.6 -0.75,-0.74 -1.5,-1.5 -2.2,-2.2 -0.99,-0.66 -2,0.94 -0.96,1.5l1.9,1.9zM13.6,32c-1.1,0.021 -1.9,-1 -1.7,-2.1 0.005,-5.7 -0.011,-11 0.008,-17 0.088,-1 1.1,-1.7 2.1,-1.5 5.6,0.005 11,-0.011 17,0.008 1,0.088 1.7,1.1 1.5,2.1 -0.005,5.6 0.011,11 -0.008,17 -0.088,1 -1.1,1.7 -2.1,1.5h-17zM13.6,30.3h17v-17h-17v17z" /> +</vector> diff --git a/app/src/main/res/drawable/ic_composer_indent.xml b/app/src/main/res/drawable/ic_composer_indent.xml new file mode 100644 index 0000000000000000000000000000000000000000..cbef736659cf55c7868cf0ddad15fd7a0acf24f8 --- /dev/null +++ b/app/src/main/res/drawable/ic_composer_indent.xml @@ -0,0 +1,13 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="44dp" + android:height="44dp" + android:tint="@color/menu_icon_color" + android:viewportWidth="44" + android:viewportHeight="44"> + <group> + <clip-path android:pathData="M10,10h24v24h-24z" /> + <path + android:fillColor="@color/blue" + android:pathData="M14,31H30C30.55,31 31,30.55 31,30C31,29.45 30.55,29 30,29H14C13.45,29 13,29.45 13,30C13,30.55 13.45,31 14,31ZM13,19.21V24.8C13,25.25 13.54,25.47 13.85,25.15L16.64,22.36C16.84,22.16 16.84,21.85 16.64,21.65L13.85,18.85C13.54,18.54 13,18.76 13,19.21ZM22,27H30C30.55,27 31,26.55 31,26C31,25.45 30.55,25 30,25H22C21.45,25 21,25.45 21,26C21,26.55 21.45,27 22,27ZM13,14C13,14.55 13.45,15 14,15H30C30.55,15 31,14.55 31,14C31,13.45 30.55,13 30,13H14C13.45,13 13,13.45 13,14ZM22,19H30C30.55,19 31,18.55 31,18C31,17.45 30.55,17 30,17H22C21.45,17 21,17.45 21,18C21,18.55 21.45,19 22,19ZM22,23H30C30.55,23 31,22.55 31,22C31,21.45 30.55,21 30,21H22C21.45,21 21,21.45 21,22C21,22.55 21.45,23 22,23Z" /> + </group> +</vector> diff --git a/app/src/main/res/drawable/ic_composer_inline_code.xml b/app/src/main/res/drawable/ic_composer_inline_code.xml new file mode 100644 index 0000000000000000000000000000000000000000..bc054e639a79590165c84994b991961df2579493 --- /dev/null +++ b/app/src/main/res/drawable/ic_composer_inline_code.xml @@ -0,0 +1,15 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="44dp" + android:height="44dp" + android:viewportWidth="44" + android:viewportHeight="44"> + <path + android:fillColor="@color/blue" + android:pathData="M24.958,15.621C25.117,15.092 24.816,14.534 24.287,14.375C23.758,14.217 23.201,14.517 23.042,15.046L19.042,28.379C18.883,28.908 19.184,29.466 19.713,29.624C20.242,29.783 20.799,29.483 20.958,28.954L24.958,15.621Z" /> + <path + android:fillColor="@color/blue" + android:pathData="M15.974,17.232C15.549,16.878 14.919,16.936 14.565,17.36L11.232,21.36C10.923,21.731 10.923,22.269 11.232,22.64L14.565,26.64C14.919,27.065 15.549,27.122 15.974,26.768C16.398,26.415 16.455,25.784 16.102,25.36L13.302,22L16.102,18.64C16.455,18.216 16.398,17.585 15.974,17.232Z" /> + <path + android:fillColor="@color/blue" + android:pathData="M28.027,17.232C28.451,16.878 29.081,16.936 29.435,17.36L32.768,21.36C33.077,21.731 33.077,22.269 32.768,22.64L29.435,26.64C29.081,27.065 28.451,27.122 28.027,26.768C27.602,26.415 27.545,25.784 27.898,25.36L30.698,22L27.898,18.64C27.545,18.216 27.602,17.585 28.027,17.232Z" /> +</vector> diff --git a/app/src/main/res/drawable/ic_composer_italic.xml b/app/src/main/res/drawable/ic_composer_italic.xml new file mode 100644 index 0000000000000000000000000000000000000000..15c40a206504405f3a9bb5fbb2056e3da01512e2 --- /dev/null +++ b/app/src/main/res/drawable/ic_composer_italic.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="44dp" + android:height="44dp" + android:viewportWidth="44" + android:viewportHeight="44"> + <path + android:pathData="M22.619,14.999L19.747,29.005H17.2C16.758,29.005 16.4,29.363 16.4,29.805C16.4,30.247 16.758,30.605 17.2,30.605H20.389C20.397,30.605 20.405,30.605 20.412,30.605H23.6C24.042,30.605 24.4,30.247 24.4,29.805C24.4,29.363 24.042,29.005 23.6,29.005H21.381L24.253,14.999H26.8C27.242,14.999 27.6,14.64 27.6,14.199C27.6,13.757 27.242,13.399 26.8,13.399H23.615C23.604,13.398 23.594,13.398 23.583,13.399H20.4C19.958,13.399 19.6,13.757 19.6,14.199C19.6,14.64 19.958,14.999 20.4,14.999H22.619Z" + android:fillColor="@color/blue" + android:fillType="evenOdd" /> +</vector> diff --git a/app/src/main/res/drawable/ic_composer_link.xml b/app/src/main/res/drawable/ic_composer_link.xml new file mode 100644 index 0000000000000000000000000000000000000000..be76286af10f63eb0c7dac33ec22a30781103e52 --- /dev/null +++ b/app/src/main/res/drawable/ic_composer_link.xml @@ -0,0 +1,12 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="44dp" + android:height="44dp" + android:viewportWidth="44" + android:viewportHeight="44"> + <path + android:fillColor="#00000000" + android:pathData="M22.566,16.151L23.101,15.616C24.577,14.14 26.956,14.126 28.415,15.585C29.874,17.044 29.86,19.423 28.383,20.899L25.844,23.438C24.368,24.915 21.989,24.929 20.53,23.47M21.434,27.849L20.899,28.383C19.423,29.86 17.044,29.874 15.585,28.415C14.126,26.956 14.14,24.577 15.616,23.101L18.156,20.562C19.632,19.086 22.011,19.071 23.47,20.53" + android:strokeWidth="1.5" + android:strokeColor="@color/blue" + android:strokeLineCap="round" /> +</vector> diff --git a/app/src/main/res/drawable/ic_composer_numbered_list.xml b/app/src/main/res/drawable/ic_composer_numbered_list.xml new file mode 100644 index 0000000000000000000000000000000000000000..5690e2de91efcec7eb129bfcf564f3139882153d --- /dev/null +++ b/app/src/main/res/drawable/ic_composer_numbered_list.xml @@ -0,0 +1,24 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="44dp" + android:height="44dp" + android:viewportWidth="44" + android:viewportHeight="44"> + <path + android:pathData="m14.5,20h-2c-0.28,0 -0.5,0.22 -0.5,0.5 0,0.28 0.22,0.5 0.5,0.5h1.3l-1.68,1.96C12.04,23.05 12,23.17 12,23.28v0.22c0,0.28 0.22,0.5 0.5,0.5h2C14.78,24 15,23.78 15,23.5 15,23.22 14.78,23 14.5,23h-1.3l1.68,-1.96C14.96,20.95 15,20.83 15,20.72V20.5C15,20.22 14.78,20 14.5,20Z" + android:fillColor="@color/blue" /> + <path + android:pathData="M12.5,15H13v2.5c0,0.28 0.22,0.5 0.5,0.5 0.28,0 0.5,-0.22 0.5,-0.5v-3C14,14.22 13.78,14 13.5,14h-1c-0.28,0 -0.5,0.22 -0.5,0.5 0,0.28 0.22,0.5 0.5,0.5z" + android:fillColor="@color/blue" /> + <path + android:pathData="m14.5,26h-2c-0.28,0 -0.5,0.22 -0.5,0.5 0,0.28 0.22,0.5 0.5,0.5H14v0.5h-0.5c-0.28,0 -0.5,0.22 -0.5,0.5 0,0.28 0.22,0.5 0.5,0.5H14V29h-1.5c-0.28,0 -0.5,0.22 -0.5,0.5 0,0.28 0.22,0.5 0.5,0.5h2c0.28,0 0.5,-0.22 0.5,-0.5v-3C15,26.22 14.78,26 14.5,26Z" + android:fillColor="@color/blue" /> + <path + android:pathData="M30,21H18c-0.55,0 -1,0.45 -1,1 0,0.55 0.45,1 1,1h12c0.55,0 1,-0.45 1,-1 0,-0.55 -0.45,-1 -1,-1z" + android:fillColor="@color/blue" /> + <path + android:pathData="M30,27H18c-0.55,0 -1,0.45 -1,1 0,0.55 0.45,1 1,1h12c0.55,0 1,-0.45 1,-1 0,-0.55 -0.45,-1 -1,-1z" + android:fillColor="@color/blue" /> + <path + android:pathData="m18,17h12c0.55,0 1,-0.45 1,-1 0,-0.55 -0.45,-1 -1,-1H18c-0.55,0 -1,0.45 -1,1 0,0.55 0.45,1 1,1z" + android:fillColor="@color/blue" /> +</vector> diff --git a/app/src/main/res/drawable/ic_composer_quote.xml b/app/src/main/res/drawable/ic_composer_quote.xml new file mode 100644 index 0000000000000000000000000000000000000000..f0b222fac6acbd29b20ddadf4acc4c439dc05a51 --- /dev/null +++ b/app/src/main/res/drawable/ic_composer_quote.xml @@ -0,0 +1,19 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="36dp" + android:height="44dp" + android:tint="@color/menu_icon_color" + android:viewportWidth="36" + android:viewportHeight="44"> + <path + android:fillColor="@color/blue" + android:pathData="M14.719,14.34C14.813,13.698 14.353,13.104 13.691,13.012C13.028,12.92 12.415,13.366 12.32,14.008L11.512,19.486C11.418,20.128 11.878,20.723 12.54,20.814C13.203,20.906 13.816,20.46 13.911,19.818L14.719,14.34Z" /> + <path + android:fillColor="@color/blue" + android:pathData="M26.834,24.514C26.928,23.872 26.468,23.277 25.806,23.186C25.143,23.094 24.53,23.54 24.435,24.182L23.628,29.66C23.533,30.302 23.993,30.896 24.656,30.988C25.318,31.08 25.932,30.634 26.026,29.992L26.834,24.514Z" /> + <path + android:fillColor="@color/blue" + android:pathData="M19.318,13.009C19.983,13.086 20.456,13.671 20.376,14.315L20.354,14.49C20.34,14.602 20.319,14.763 20.293,14.961C20.242,15.358 20.17,15.902 20.088,16.496C19.927,17.667 19.72,19.075 19.553,19.882C19.422,20.518 18.784,20.931 18.128,20.803C17.472,20.676 17.046,20.058 17.177,19.422C17.326,18.701 17.523,17.37 17.687,16.185C17.767,15.599 17.838,15.061 17.889,14.669C17.915,14.473 17.935,14.314 17.949,14.204L17.97,14.034C18.051,13.39 18.654,12.931 19.318,13.009Z" /> + <path + android:fillColor="@color/blue" + android:pathData="M32.488,24.514C32.582,23.872 32.122,23.277 31.46,23.186C30.797,23.094 30.184,23.54 30.089,24.182L29.281,29.66C29.187,30.302 29.647,30.896 30.309,30.988C30.972,31.08 31.585,30.634 31.68,29.992L32.488,24.514Z" /> +</vector> diff --git a/app/src/main/res/drawable/ic_composer_strikethrough.xml b/app/src/main/res/drawable/ic_composer_strikethrough.xml new file mode 100644 index 0000000000000000000000000000000000000000..593cd3aca18017d5ffca71a20b2905dc443def31 --- /dev/null +++ b/app/src/main/res/drawable/ic_composer_strikethrough.xml @@ -0,0 +1,12 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="44dp" + android:height="44dp" + android:viewportWidth="44" + android:viewportHeight="44"> + <path + android:pathData="M24.897,17.154C24.235,15.821 22.876,15.21 21.374,15.372C19.05,15.622 18.44,17.423 18.722,18.592C19.032,19.872 20.046,20.37 21.839,20.826H29.92C30.517,20.826 31,21.351 31,22C31,22.648 30.517,23.174 29.92,23.174H14.08C13.483,23.174 13,22.648 13,22C13,21.351 13.483,20.826 14.08,20.826H17.355C17.041,20.377 16.791,19.839 16.633,19.189C16.003,16.581 17.554,13.424 21.16,13.036C23.285,12.807 25.615,13.661 26.798,16.038C27.081,16.608 26.886,17.32 26.361,17.629C25.836,17.937 25.181,17.725 24.897,17.154Z" + android:fillColor="@color/blue" /> + <path + android:pathData="M25.427,25.13H27.67C27.888,26.306 27.721,27.56 27.05,28.632C26.114,30.125 24.37,31 21.985,31C18.076,31 16.279,28.584 15.912,26.986C15.768,26.357 16.12,25.72 16.698,25.563C17.277,25.406 17.863,25.788 18.008,26.417C18.119,26.902 19.002,28.652 21.985,28.652C23.907,28.652 24.854,27.965 25.264,27.31C25.642,26.707 25.708,25.909 25.427,25.13Z" + android:fillColor="@color/blue" /> +</vector> diff --git a/app/src/main/res/drawable/ic_composer_underlined.xml b/app/src/main/res/drawable/ic_composer_underlined.xml new file mode 100644 index 0000000000000000000000000000000000000000..c0b515adff45070dd208c65ef8ee2ebda6bfe451 --- /dev/null +++ b/app/src/main/res/drawable/ic_composer_underlined.xml @@ -0,0 +1,13 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="44dp" + android:height="44dp" + android:tint="@color/menu_icon_color" + android:viewportWidth="44" + android:viewportHeight="44"> + <group> + <clip-path android:pathData="M10,10h24v24h-24z" /> + <path + android:fillColor="@color/blue" + android:pathData="M22.79,26.95C25.82,26.56 28,23.84 28,20.79V14.25C28,13.56 27.44,13 26.75,13C26.06,13 25.5,13.56 25.5,14.25V20.9C25.5,22.57 24.37,24.09 22.73,24.42C20.48,24.89 18.5,23.17 18.5,21V14.25C18.5,13.56 17.94,13 17.25,13C16.56,13 16,13.56 16,14.25V21C16,24.57 19.13,27.42 22.79,26.95ZM15,30C15,30.55 15.45,31 16,31H28C28.55,31 29,30.55 29,30C29,29.45 28.55,29 28,29H16C15.45,29 15,29.45 15,30Z" /> + </group> +</vector> diff --git a/app/src/main/res/drawable/ic_composer_unindent.xml b/app/src/main/res/drawable/ic_composer_unindent.xml new file mode 100644 index 0000000000000000000000000000000000000000..a4a7fc69a4890a0e380e051ffe4876d5125672b1 --- /dev/null +++ b/app/src/main/res/drawable/ic_composer_unindent.xml @@ -0,0 +1,13 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="44dp" + android:height="44dp" + android:tint="@color/menu_icon_color" + android:viewportWidth="44" + android:viewportHeight="44"> + <group> + <clip-path android:pathData="M10,10h24v24h-24z" /> + <path + android:fillColor="@color/blue" + android:pathData="M22,27H30C30.55,27 31,26.55 31,26C31,25.45 30.55,25 30,25H22C21.45,25 21,25.45 21,26C21,26.55 21.45,27 22,27ZM13.35,22.35L16.14,25.14C16.46,25.46 17,25.24 17,24.79V19.21C17,18.76 16.46,18.54 16.15,18.86L13.36,21.65C13.16,21.84 13.16,22.16 13.35,22.35ZM14,31H30C30.55,31 31,30.55 31,30C31,29.45 30.55,29 30,29H14C13.45,29 13,29.45 13,30C13,30.55 13.45,31 14,31ZM13,14C13,14.55 13.45,15 14,15H30C30.55,15 31,14.55 31,14C31,13.45 30.55,13 30,13H14C13.45,13 13,13.45 13,14ZM22,19H30C30.55,19 31,18.55 31,18C31,17.45 30.55,17 30,17H22C21.45,17 21,17.45 21,18C21,18.55 21.45,19 22,19ZM22,23H30C30.55,23 31,22.55 31,22C31,21.45 30.55,21 30,21H22C21.45,21 21,21.45 21,22C21,22.55 21.45,23 22,23Z" /> + </group> +</vector> diff --git a/app/src/main/res/layout/dialog_fragment_create_post.xml b/app/src/main/res/layout/dialog_fragment_create_post.xml index dd15dfc9bcbb3e671431bab5cd8d656c40a1d6f3..ac9ce83e80201a992489b57dc0f8e32cbc3ba6c5 100644 --- a/app/src/main/res/layout/dialog_fragment_create_post.xml +++ b/app/src/main/res/layout/dialog_fragment_create_post.xml @@ -40,38 +40,11 @@ android:id="@+id/vPostPreview" android:layout_width="match_parent" android:layout_height="0dp" - app:layout_constraintBottom_toTopOf="@id/btnSend" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> - <com.google.android.material.button.MaterialButton - android:id="@+id/btnSend" - android:layout_width="48dp" - android:layout_height="48dp" - android:layout_marginEnd="8dp" - android:layout_marginBottom="8dp" - android:background="?selectableItemBackgroundBorderless" - android:enabled="false" - android:focusable="true" - android:minWidth="0dp" - android:minHeight="0dp" - android:padding="0dp" - app:icon="@drawable/ic_send" - app:iconGravity="end" - app:iconSize="40dp" - app:iconTint="@color/send_ic_state_color" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" /> - - <org.futo.circles.view.MarkdownStyleBar - android:id="@+id/vPostOptions" - android:layout_width="0dp" - android:layout_height="wrap_content" - app:layout_constraintBottom_toBottomOf="@id/btnSend" - app:layout_constraintEnd_toStartOf="@id/btnSend" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="@id/btnSend" /> </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/app/src/main/res/layout/view_markdown_stylebar.xml b/app/src/main/res/layout/view_markdown_stylebar.xml deleted file mode 100644 index f152df049e022e44cd5f7a8f89bc1f52b149551d..0000000000000000000000000000000000000000 --- a/app/src/main/res/layout/view_markdown_stylebar.xml +++ /dev/null @@ -1,57 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<merge xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal" - tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout"> - - <androidx.recyclerview.widget.RecyclerView - android:id="@+id/rvMainOptions" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:orientation="horizontal" - android:paddingStart="8dp" - app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" - tools:ignore="RtlSymmetry" /> - - <androidx.recyclerview.widget.RecyclerView - android:id="@+id/rvTextOptions" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:orientation="horizontal" - app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toEndOf="@id/ivCancel" - app:layout_constraintTop_toTopOf="parent" /> - - - <com.google.android.material.imageview.ShapeableImageView - android:id="@+id/ivCancel" - android:layout_width="0dp" - android:layout_height="0dp" - android:background="@color/close_background" - android:clickable="true" - android:focusable="true" - android:padding="8dp" - android:src="@drawable/ic_close" - android:tint="@color/white" - app:layout_constraintBottom_toBottomOf="@id/rvTextOptions" - app:layout_constraintDimensionRatio="1:1" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="@id/rvTextOptions" - app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.App.CornerSize50Percent" /> - - - <androidx.constraintlayout.widget.Group - android:id="@+id/textOptionsGroup" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:visibility="gone" - app:constraint_referenced_ids="ivCancel, rvTextOptions" /> - -</merge> \ No newline at end of file diff --git a/app/src/main/res/layout/view_preview_post.xml b/app/src/main/res/layout/view_preview_post.xml index 13f7ae6b17bb69fbbd396196a4890d7049390cb1..f7fafaf9e2b557ddee31d13bac1c4140537ccc7c 100644 --- a/app/src/main/res/layout/view_preview_post.xml +++ b/app/src/main/res/layout/view_preview_post.xml @@ -39,9 +39,10 @@ <ScrollView android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="0dp" android:layout_marginTop="8dp" android:orientation="vertical" + app:layout_constraintBottom_toTopOf="@id/lBottomContainer" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/postHeader"> @@ -51,20 +52,20 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - <org.futo.circles.view.MarkdownEditText + <io.element.android.wysiwyg.EditorEditText android:id="@+id/etTextPost" android:layout_width="0dp" android:layout_height="wrap_content" android:background="@null" android:gravity="top" android:hint="@string/enter_your_message_here" - android:inputType="textCapSentences|textMultiLine" - android:paddingStart="12dp" - android:paddingEnd="12dp" - app:layout_constraintBottom_toTopOf="@id/lMediaContent" + app:bulletGap="8sp" + app:bulletRadius="4sp" + app:pillBackgroundColor="@color/white" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toTopOf="parent" + tools:text="some text" /> <include @@ -91,8 +92,87 @@ app:layout_constraintEnd_toEndOf="@id/lMediaContent" app:layout_constraintTop_toTopOf="@id/lMediaContent" app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.App.CornerSize50Percent" /> - + </androidx.constraintlayout.widget.ConstraintLayout> </ScrollView> + + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/lBottomContainer" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent"> + + <com.google.android.material.imageview.ShapeableImageView + android:id="@+id/ivCancel" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:background="@color/close_background" + android:clickable="true" + android:focusable="true" + android:padding="8dp" + android:src="@drawable/ic_close" + android:tint="@color/white" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.App.CornerSize50Percent" /> + + <HorizontalScrollView + android:layout_width="0dp" + android:layout_height="wrap_content" + android:fadingEdgeLength="28dp" + android:fillViewport="true" + android:minHeight="52dp" + android:requiresFadingEdge="horizontal" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@id/btnSend" + app:layout_constraintStart_toEndOf="@id/ivCancel" + app:layout_constraintTop_toTopOf="parent"> + + <LinearLayout + android:id="@+id/lMenu" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:orientation="horizontal"> + + <LinearLayout + android:id="@+id/lMainMenu" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:orientation="horizontal" /> + + <LinearLayout + android:id="@+id/lTextMenu" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:orientation="horizontal" /> + + </LinearLayout> + + </HorizontalScrollView> + + <com.google.android.material.button.MaterialButton + android:id="@+id/btnSend" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_marginEnd="8dp" + android:background="?selectableItemBackgroundBorderless" + android:enabled="false" + android:focusable="true" + android:minWidth="0dp" + android:minHeight="0dp" + android:padding="0dp" + app:icon="@drawable/ic_send" + app:iconGravity="end" + app:iconSize="40dp" + app:iconTint="@color/send_ic_state_color" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + </androidx.constraintlayout.widget.ConstraintLayout> </merge> \ No newline at end of file diff --git a/app/src/main/res/layout/view_rich_text_menu_button.xml b/app/src/main/res/layout/view_rich_text_menu_button.xml new file mode 100644 index 0000000000000000000000000000000000000000..ced20d9a86ae47272bc3715740f99c741ede1538 --- /dev/null +++ b/app/src/main/res/layout/view_rich_text_menu_button.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/cvOption" + android:layout_width="48dp" + android:layout_height="48dp" + android:clickable="true" + android:elevation="4dp" + android:focusable="true" + android:foreground="?selectableItemBackground" + app:cardBackgroundColor="@drawable/bg_rich_text_menu_button" + app:cardCornerRadius="8dp" + app:cardUseCompatPadding="true"> + + <ImageView + android:id="@+id/ivIcon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:src="@drawable/ic_mention" /> + +</androidx.cardview.widget.CardView> + diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index ac9eed3a50e31cf0d63f6b3e43237019b581fe79..be23a2f4fb314a6cb382433b30295ae3cf4a6225 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -1,6 +1,8 @@ <?xml version="1.0" encoding="utf-8"?> <resources> + <attr name="vctr_content_quaternary" format="color" /> + <declare-styleable name="GroupPostHeaderView"> <attr name="optionsVisible" format="boolean" /> </declare-styleable> diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index a56cca454e9ad1497609483baa3841852b78856d..ed95017c76f6572d163340f7c993dc9ad6cc3b11 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -19,6 +19,5 @@ <color name="close_background">#80000000</color> <color name="highlight_color">#4D0E7AFE</color> <color name="launcher_background_color">#00008b</color> - <color name="transparent">#00000000</color> </resources> \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 57b80004bddfd311f2648dba71b56de014ee6fe8..4671e2397061ff82636af83cf1e4aa33cb93816f 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -5,4 +5,7 @@ <dimen name="post_text_side_margin">24dp</dimen> <dimen name="circle_icon_size">100dp</dimen> <dimen name="profile_avatar_size">50dp</dimen> + + <dimen name="rich_text_composer_corner_radius_expanded">14dp</dimen> + <dimen name="rich_text_composer_menu_item_size">44dp</dimen> </resources> \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index eade56ad373fabfec4110c8cf6297c5f078551d0..f748de26c95780acd5a507c8210aba181ac0e295 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -25,7 +25,6 @@ <string name="invites">Invites</string> <string name="create_new_group">Create new group</string> <string name="create_new_circle">Create new circle</string> - <string name="circles_created">Circles created</string> <string name="private_circles">Private Circles</string> <string name="log_out">Log out</string> <string name="others">Others</string> @@ -74,7 +73,6 @@ <string name="severity_formatter">Severity: %d</string> <string name="add_reaction">Add reaction</string> <string name="unfollow">Unfollow</string> - <string name="requested_to_follow_format">%s requested to follow</string> <string name="request_to_join">Request to join</string> <string name="request_to_follow">Request to follow</string> <string name="select_circles_in_which_you_want_to_follow_this_timeline">Select circles in which you want to follow this timeline</string> @@ -184,9 +182,6 @@ <string name="unifiedpush_distributor_background_sync">Background synchronization</string> <string name="no_valid_google_play_services_apk">No valid Google Play Services APK found. Notifications may not work properly.</string> <string name="unable_to_register_receiver">Unable to register the receiver</string> - <string name="scan_profile">Scan profile</string> - <string name="mute_notifications">Mute notifications</string> - <string name="unmute_notifications">Unmute notifications</string> <string name="shortcut_disabled">Shortcut disabled</string> <string name="shared_circles_space_not_found">Shared Circles space not found</string> <string name="notification_method">Notification method</string> @@ -202,7 +197,6 @@ <string name="request_to_become_member_room">Request to join to become a member of this room</string> <string name="you_have_pending_invitation_user">You have a pending invitation to follow this user. Check your invite notifications</string> <string name="you_are_already_following_user">You are already following this user</string> - <string name="you_are_banned_user">You are banned</string> <string name="send_request_to_follow_user">Send request to follow this user</string> <string name="unable_to_parse_url">Unable to parse url</string> <string name="developer_mode_enabled">Developer mode is enabled</string> @@ -216,6 +210,19 @@ <string name="help">Help</string> <string name="optional_request_message">Optional: Request message</string> + <!-- Rich text editor --> + <string name="rich_text_editor_format_bold">Apply bold format</string> + <string name="rich_text_editor_format_italic">Apply italic format</string> + <string name="rich_text_editor_format_strikethrough">Apply strikethrough format</string> + <string name="rich_text_editor_format_underline">Apply underline format</string> + <string name="rich_text_editor_link">Set link</string> + <string name="rich_text_editor_numbered_list">Toggle numbered list</string> + <string name="rich_text_editor_bullet_list">Toggle bullet list</string> + <string name="rich_text_editor_indent">Indent</string> + <string name="rich_text_editor_unindent">Unindent</string> + <string name="rich_text_editor_quote">Toggle quote</string> + <string name="rich_text_editor_code_block">Toggle code block</string> + <string-array name="report_categories"> <item>@string/crude_language</item> <item>@string/copyright_violation</item> diff --git a/build.gradle b/build.gradle index 924f2bdbcbd6dd528a06efabe21af3db2a95853c..5eb8219f82e9b52c881e335243fc333223ce9736 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.1.3' + classpath 'com.android.tools.build:gradle:8.1.4' classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.21' classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$androidx_nav_version" classpath 'com.google.gms:google-services:4.4.0' diff --git a/core/build.gradle b/core/build.gradle index cca12aac79de792467a40ca7e63d38a56742a086..daecc1f1b5e4dd76cababd0d19863d2e8becac37 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -95,6 +95,15 @@ dependencies { //QR api 'com.google.zxing:core:3.5.2' + //Markdown + def markwon_version = "4.6.2" + api "io.noties.markwon:core:$markwon_version" + api "io.noties.markwon:linkify:$markwon_version" + api "io.noties.markwon:ext-strikethrough:$markwon_version" + api "io.noties.markwon:ext-tasklist:$markwon_version" + //noinspection GradleDependency + api "io.element.android:wysiwyg:2.2.2" + //Shake detection implementation 'com.squareup:seismic:1.0.3' diff --git a/core/src/main/java/org/futo/circles/core/base/NetworkObserver.kt b/core/src/main/java/org/futo/circles/core/base/NetworkObserver.kt index ff318d241a79a6d9cc653881e2820c48ab4fd6b7..7c304a8936dfc7e117100c5de547618a758d35da 100644 --- a/core/src/main/java/org/futo/circles/core/base/NetworkObserver.kt +++ b/core/src/main/java/org/futo/circles/core/base/NetworkObserver.kt @@ -17,7 +17,7 @@ import org.matrix.android.sdk.api.extensions.orFalse object NetworkObserver { - private val internetConnectionFlow = MutableStateFlow(false) + private val internetConnectionFlow = MutableStateFlow(true) fun isConnected() = internetConnectionFlow.value @@ -59,7 +59,7 @@ object NetworkObserver { } private fun isConnectedToInternet(context: Context): Boolean { - val connectivityManager = context.getSystemService<ConnectivityManager>() ?: return false + val connectivityManager = context.getSystemService<ConnectivityManager>() ?: return true val capabilities = connectivityManager.activeNetwork?.let { connectivityManager.getNetworkCapabilities(it) } val hasWifi = capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI).orFalse() diff --git a/core/src/main/java/org/futo/circles/core/feature/markdown/MarkdownParser.kt b/core/src/main/java/org/futo/circles/core/feature/markdown/MarkdownParser.kt new file mode 100644 index 0000000000000000000000000000000000000000..ccab220dcf8192a491b20369d203de5a1245b5a6 --- /dev/null +++ b/core/src/main/java/org/futo/circles/core/feature/markdown/MarkdownParser.kt @@ -0,0 +1,57 @@ +package org.futo.circles.core.feature.markdown + +import android.content.Context +import android.graphics.Typeface +import android.text.style.StyleSpan +import io.noties.markwon.AbstractMarkwonPlugin +import io.noties.markwon.Markwon +import io.noties.markwon.MarkwonSpansFactory +import io.noties.markwon.SoftBreakAddsNewLinePlugin +import io.noties.markwon.ext.strikethrough.StrikethroughPlugin +import io.noties.markwon.linkify.LinkifyPlugin +import org.commonmark.node.Emphasis +import org.commonmark.node.StrongEmphasis +import org.futo.circles.core.extensions.notEmptyDisplayName +import org.futo.circles.core.feature.markdown.mentions.plugin.MentionPlugin +import org.futo.circles.core.provider.MatrixSessionProvider +import org.matrix.android.sdk.api.session.getUserOrDefault + + +object MarkdownParser { + + + fun markwonBuilder(context: Context): Markwon = Markwon.builder(context) + .usePlugin(SoftBreakAddsNewLinePlugin.create()) + .usePlugin(StrikethroughPlugin.create()) + .usePlugin(LinkifyPlugin.create()) + .usePlugin(MentionPlugin(context)) + .build() + + fun markwonNotificationBuilder(context: Context): Markwon = Markwon.builder(context) + .usePlugin(SoftBreakAddsNewLinePlugin.create()) + .usePlugin(object : AbstractMarkwonPlugin() { + override fun configureSpansFactory(builder: MarkwonSpansFactory.Builder) { + builder.setFactory( + Emphasis::class.java + ) { _, _ -> StyleSpan(Typeface.ITALIC) } + } + }) + .usePlugin(object : AbstractMarkwonPlugin() { + override fun configureSpansFactory(builder: MarkwonSpansFactory.Builder) { + builder.setFactory( + StrongEmphasis::class.java + ) { _, _ -> StyleSpan(Typeface.BOLD) } + } + }) + .usePlugin(StrikethroughPlugin.create()) + .usePlugin(LinkifyPlugin.create()) + .build() + + fun hasCurrentUserMention(text: String): Boolean { + val session = MatrixSessionProvider.currentSession ?: return false + val userName = session.getUserOrDefault(session.myUserId).notEmptyDisplayName() + val mentionString = "@$userName@" + return text.contains(mentionString) + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/MentionsAdapter.kt b/core/src/main/java/org/futo/circles/core/feature/markdown/mentions/MentionsAdapter.kt similarity index 91% rename from app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/MentionsAdapter.kt rename to core/src/main/java/org/futo/circles/core/feature/markdown/mentions/MentionsAdapter.kt index f2d8de1993e2f6c2be27c607c37ba7acc48668cb..43a4416fc5316648cc57d8a15675832c3c3c279d 100644 --- a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/MentionsAdapter.kt +++ b/core/src/main/java/org/futo/circles/core/feature/markdown/mentions/MentionsAdapter.kt @@ -1,4 +1,4 @@ -package org.futo.circles.feature.timeline.post.markdown.mentions +package org.futo.circles.core.feature.markdown.mentions import android.view.ViewGroup import org.futo.circles.core.base.list.BaseRvAdapter diff --git a/core/src/main/java/org/futo/circles/core/feature/markdown/mentions/MentionsLinkDisplayHandler.kt b/core/src/main/java/org/futo/circles/core/feature/markdown/mentions/MentionsLinkDisplayHandler.kt new file mode 100644 index 0000000000000000000000000000000000000000..3e61d3e8b8d0f6e321ecd3b7f457fca5754ad7b3 --- /dev/null +++ b/core/src/main/java/org/futo/circles/core/feature/markdown/mentions/MentionsLinkDisplayHandler.kt @@ -0,0 +1,22 @@ +package org.futo.circles.core.feature.markdown.mentions + +import android.content.Context +import io.element.android.wysiwyg.display.LinkDisplayHandler +import io.element.android.wysiwyg.display.TextDisplay +import org.futo.circles.core.feature.markdown.span.MentionSpan +import org.matrix.android.sdk.api.MatrixPatterns +import org.matrix.android.sdk.api.session.permalinks.PermalinkService + +class MentionsLinkDisplayHandler(private val context: Context) : LinkDisplayHandler { + + override fun resolveLinkDisplay(text: String, url: String): TextDisplay { + val userId = url.removePrefix(PermalinkService.MATRIX_TO_URL_BASE) + return if (MatrixPatterns.isUserId(userId)) + TextDisplay.Custom( + MentionSpan( + context, + text.replace("@", "") + ) + ) else TextDisplay.Plain + } +} \ No newline at end of file diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/MentionsPresenter.kt b/core/src/main/java/org/futo/circles/core/feature/markdown/mentions/MentionsPresenter.kt similarity index 96% rename from app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/MentionsPresenter.kt rename to core/src/main/java/org/futo/circles/core/feature/markdown/mentions/MentionsPresenter.kt index 1024ed548fd3ea7fbf907c31a5ba17a348474af7..bedc93b4af49cdd16dcc485c64d760323b3b3433 100644 --- a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/MentionsPresenter.kt +++ b/core/src/main/java/org/futo/circles/core/feature/markdown/mentions/MentionsPresenter.kt @@ -1,4 +1,4 @@ -package org.futo.circles.feature.timeline.post.markdown.mentions +package org.futo.circles.core.feature.markdown.mentions import android.content.Context import androidx.recyclerview.widget.RecyclerView diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/plugin/MentionDelimiterProcessor.kt b/core/src/main/java/org/futo/circles/core/feature/markdown/mentions/plugin/MentionDelimiterProcessor.kt similarity index 93% rename from app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/plugin/MentionDelimiterProcessor.kt rename to core/src/main/java/org/futo/circles/core/feature/markdown/mentions/plugin/MentionDelimiterProcessor.kt index 66c2bbf46cc9f3e2425a038bd31b34469acdd118..817dc7d4df479bb3d42f881bce34c624662528bd 100644 --- a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/plugin/MentionDelimiterProcessor.kt +++ b/core/src/main/java/org/futo/circles/core/feature/markdown/mentions/plugin/MentionDelimiterProcessor.kt @@ -1,4 +1,4 @@ -package org.futo.circles.feature.timeline.post.markdown.mentions.plugin +package org.futo.circles.core.feature.markdown.mentions.plugin import org.commonmark.node.Node import org.commonmark.node.Text diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/plugin/MentionNode.kt b/core/src/main/java/org/futo/circles/core/feature/markdown/mentions/plugin/MentionNode.kt similarity index 72% rename from app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/plugin/MentionNode.kt rename to core/src/main/java/org/futo/circles/core/feature/markdown/mentions/plugin/MentionNode.kt index 4e0274255c76c70f3c273d722392ad098565918f..6aca6a1a2d95303145f66e2b351209391e8179b5 100644 --- a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/plugin/MentionNode.kt +++ b/core/src/main/java/org/futo/circles/core/feature/markdown/mentions/plugin/MentionNode.kt @@ -1,4 +1,4 @@ -package org.futo.circles.feature.timeline.post.markdown.mentions.plugin +package org.futo.circles.core.feature.markdown.mentions.plugin import org.commonmark.node.CustomNode import org.commonmark.node.Visitor diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/plugin/MentionPlugin.kt b/core/src/main/java/org/futo/circles/core/feature/markdown/mentions/plugin/MentionPlugin.kt similarity index 88% rename from app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/plugin/MentionPlugin.kt rename to core/src/main/java/org/futo/circles/core/feature/markdown/mentions/plugin/MentionPlugin.kt index 797fc787e8f32866a56257a7e4d9d46bbd4e634f..2a2b1ff6b0f65ef42ef8398dde7c34cd09e06b45 100644 --- a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/mentions/plugin/MentionPlugin.kt +++ b/core/src/main/java/org/futo/circles/core/feature/markdown/mentions/plugin/MentionPlugin.kt @@ -1,11 +1,11 @@ -package org.futo.circles.feature.timeline.post.markdown.mentions.plugin +package org.futo.circles.core.feature.markdown.mentions.plugin import android.content.Context import io.noties.markwon.AbstractMarkwonPlugin import io.noties.markwon.MarkwonVisitor import io.noties.markwon.SpannableBuilder import org.commonmark.parser.Parser -import org.futo.circles.feature.timeline.post.markdown.span.MentionSpan +import org.futo.circles.core.feature.markdown.span.MentionSpan class MentionPlugin(private val context: Context) : AbstractMarkwonPlugin() { diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/span/MentionSpan.kt b/core/src/main/java/org/futo/circles/core/feature/markdown/span/MentionSpan.kt similarity index 87% rename from app/src/main/java/org/futo/circles/feature/timeline/post/markdown/span/MentionSpan.kt rename to core/src/main/java/org/futo/circles/core/feature/markdown/span/MentionSpan.kt index 04f900ea4edacf94494470b5c86e46e86f42b50d..bb331381539d859c66135cb2efa5ebd9ee628b3d 100644 --- a/app/src/main/java/org/futo/circles/feature/timeline/post/markdown/span/MentionSpan.kt +++ b/core/src/main/java/org/futo/circles/core/feature/markdown/span/MentionSpan.kt @@ -1,11 +1,11 @@ -package org.futo.circles.feature.timeline.post.markdown.span +package org.futo.circles.core.feature.markdown.span import android.content.Context import android.graphics.drawable.Drawable import android.text.style.DynamicDrawableSpan import androidx.core.content.ContextCompat import com.google.android.material.chip.ChipDrawable -import org.futo.circles.R +import org.futo.circles.core.R class MentionSpan( private val context: Context, diff --git a/core/src/main/java/org/futo/circles/core/feature/timeline/data_source/BaseTimelineDataSource.kt b/core/src/main/java/org/futo/circles/core/feature/timeline/data_source/BaseTimelineDataSource.kt index 82715bea1084a75cbcac35e2dfbe77aaba447993..c6de328db778fbc371a0bc78bddac362489184da 100644 --- a/core/src/main/java/org/futo/circles/core/feature/timeline/data_source/BaseTimelineDataSource.kt +++ b/core/src/main/java/org/futo/circles/core/feature/timeline/data_source/BaseTimelineDataSource.kt @@ -3,11 +3,11 @@ package org.futo.circles.core.feature.timeline.data_source import androidx.lifecycle.MutableLiveData import androidx.lifecycle.SavedStateHandle import org.futo.circles.core.extensions.getOrThrow -import org.futo.circles.core.model.Post -import org.futo.circles.core.provider.MatrixSessionProvider import org.futo.circles.core.feature.timeline.builder.BaseTimelineBuilder import org.futo.circles.core.feature.timeline.builder.MultiTimelineBuilder import org.futo.circles.core.feature.timeline.builder.SingleTimelineBuilder +import org.futo.circles.core.model.Post +import org.futo.circles.core.provider.MatrixSessionProvider import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.timeline.Timeline diff --git a/core/src/main/java/org/futo/circles/core/feature/timeline/post/PostContentDataSource.kt b/core/src/main/java/org/futo/circles/core/feature/timeline/post/PostContentDataSource.kt index aae616280fc90761c8bef16b3230404bd12f95f4..2ee5c0b96fce1159037a9ddaac13052d3ceae075 100644 --- a/core/src/main/java/org/futo/circles/core/feature/timeline/post/PostContentDataSource.kt +++ b/core/src/main/java/org/futo/circles/core/feature/timeline/post/PostContentDataSource.kt @@ -15,6 +15,7 @@ class PostContentDataSource @Inject constructor() { private val session = MatrixSessionProvider.currentSession + fun getPost(roomId: String, eventId: String): Post? { val roomForMessage = session?.getRoom(roomId) val timelineEvent = roomForMessage?.getTimelineEvent(eventId) ?: return null diff --git a/core/src/main/java/org/futo/circles/core/feature/timeline/post/PostOptionsDataSource.kt b/core/src/main/java/org/futo/circles/core/feature/timeline/post/PostOptionsDataSource.kt index 7db832ffd78486ffe04331d0e8a9ffa2b64e94a9..bae01ac4ff9df0f3008f1c5b4fd69a330a1c4bb0 100644 --- a/core/src/main/java/org/futo/circles/core/feature/timeline/post/PostOptionsDataSource.kt +++ b/core/src/main/java/org/futo/circles/core/feature/timeline/post/PostOptionsDataSource.kt @@ -44,7 +44,7 @@ class PostOptionsDataSource @Inject constructor( suspend fun getShareableContent(content: PostContent): ShareableContent? = onBG { when (content) { is MediaContent -> getShareableMediaContent(content.mediaFileData) - is TextContent -> TextShareable(content.message) + is TextContent -> TextShareable(content.message.toString()) else -> throw IllegalArgumentException("Not shareable post content") } } diff --git a/core/src/main/java/org/futo/circles/core/mapping/TextPostContentMapping.kt b/core/src/main/java/org/futo/circles/core/mapping/TextPostContentMapping.kt index a8104f4eb09f000175154ecd9d1dcac5a160766d..2044b74d571cc969659cb5f38f3d65d3be3f7edd 100644 --- a/core/src/main/java/org/futo/circles/core/mapping/TextPostContentMapping.kt +++ b/core/src/main/java/org/futo/circles/core/mapping/TextPostContentMapping.kt @@ -4,6 +4,8 @@ import org.futo.circles.core.model.TextContent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent -fun TimelineEvent.toTextContent(): TextContent = TextContent( - message = getTextEditableContent(false) -) \ No newline at end of file +fun TimelineEvent.toTextContent(): TextContent = + TextContent(getTextEditableContent(false)) + + + diff --git a/core/src/main/java/org/futo/circles/core/mapping/TimelineEventMapping.kt b/core/src/main/java/org/futo/circles/core/mapping/TimelineEventMapping.kt index c024e6572c2b55778c154fff433fe04508ef6c1b..440831acc7021de534597870b9e4b4fa750186b6 100644 --- a/core/src/main/java/org/futo/circles/core/mapping/TimelineEventMapping.kt +++ b/core/src/main/java/org/futo/circles/core/mapping/TimelineEventMapping.kt @@ -30,13 +30,14 @@ private fun TimelineEvent.toPostInfo(): PostInfo = PostInfo( isEdited = hasBeenEdited() ) -private fun TimelineEvent.toPostContent(): PostContent = when (getPostContentType()) { - PostContentType.TEXT_CONTENT -> toTextContent() - PostContentType.IMAGE_CONTENT -> toMediaContent(MediaType.Image) - PostContentType.VIDEO_CONTENT -> toMediaContent(MediaType.Video) - PostContentType.POLL_CONTENT -> toPollContent() - else -> toTextContent() -} +private fun TimelineEvent.toPostContent(): PostContent = + when (getPostContentType()) { + PostContentType.TEXT_CONTENT -> toTextContent() + PostContentType.IMAGE_CONTENT -> toMediaContent(MediaType.Image) + PostContentType.VIDEO_CONTENT -> toMediaContent(MediaType.Video) + PostContentType.POLL_CONTENT -> toPollContent() + else -> toTextContent() + } private fun TimelineEvent.getReadByCount(receipts: List<Long>): Int { val eventTime = root.originServerTs ?: 0 diff --git a/app/src/main/res/drawable/ic_mention.xml b/core/src/main/res/drawable/ic_mention.xml similarity index 100% rename from app/src/main/res/drawable/ic_mention.xml rename to core/src/main/res/drawable/ic_mention.xml diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index 1fe6f027a502a9afe13d7ea85427a37dbee6eb99..12b0cbde91f4e029ce1ba3b03019a0566ec39f2e 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -124,7 +124,6 @@ <string name="delete_gallery_message">Are you sure you want to remove this gallery?</string> <string name="accept">Accept</string> <string name="decline">Decline</string> - <string name="requested_to_join_format">%s requested to join</string> <string name="invited_by_format">Invited by %s</string> <string name="circle_encryption_warning">NOTE: Circle name and cover image are not encrypted</string> <string name="circle_name">Circle name</string> diff --git a/core/src/main/res/values/styles.xml b/core/src/main/res/values/styles.xml index 398bd338d110a0ccdc9333ab61c1cf3a26307316..aedfaa4c38711a05a5055af8e7cce525d812a477 100644 --- a/core/src/main/res/values/styles.xml +++ b/core/src/main/res/values/styles.xml @@ -71,13 +71,6 @@ <item name="cornerSize">10%</item> </style> - <style name="ShapeAppearanceOverlay.App.Message" parent=""> - <item name="cornerSizeTopRight">10dp</item> - <item name="cornerSizeBottomRight">10dp</item> - <item name="cornerSizeTopLeft">10dp</item> - <item name="cornerFamilyTopRight">rounded</item> - </style> - <style name="settingMenuItem" parent="body"> <item name="android:background">?attr/selectableItemBackground</item> <item name="android:clickable">true</item> diff --git a/app/src/main/res/xml/bg_chip.xml b/core/src/main/res/xml/bg_chip.xml similarity index 100% rename from app/src/main/res/xml/bg_chip.xml rename to core/src/main/res/xml/bg_chip.xml