diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt index 3c288152..d1e891d0 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt @@ -236,6 +236,9 @@ class TimelineFragment : if (loadState.append is LoadState.NotLoading && loadState.source.refresh is LoadState.NotLoading) { binding.statusView.show() binding.statusView.setup(R.drawable.elephant_friend_empty, R.string.message_empty) + if (kind == TimelineViewModel.Kind.HOME) { + binding.statusView.showHelp(R.string.help_empty_home) + } } } is LoadState.Error -> { diff --git a/app/src/main/java/com/keylesspalace/tusky/util/SpanUtils.kt b/app/src/main/java/com/keylesspalace/tusky/util/SpanUtils.kt index cba17613..0da64c1c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/SpanUtils.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/SpanUtils.kt @@ -1,10 +1,19 @@ package com.keylesspalace.tusky.util +import android.content.Context +import android.graphics.drawable.Drawable +import android.os.Build import android.text.Spannable +import android.text.SpannableStringBuilder import android.text.Spanned import android.text.style.CharacterStyle +import android.text.style.DynamicDrawableSpan import android.text.style.ForegroundColorSpan +import android.text.style.ImageSpan import android.text.style.URLSpan +import androidx.appcompat.content.res.AppCompatResources +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial import java.util.regex.Pattern import kotlin.math.max @@ -61,6 +70,66 @@ private class PatternFinder( val pattern: Pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE) } +/** + * Takes text containing mentions and hashtags and urls and makes them the given colour. + */ +fun highlightSpans(text: Spannable, colour: Int) { + // Strip all existing colour spans. + for (spanClass in spanClasses) { + clearSpans(text, spanClass) + } + + // Colour the mentions and hashtags. + val string = text.toString() + val length = text.length + var start = 0 + var end = 0 + while (end in 0 until length && start >= 0) { + // Search for url first because it can contain the other characters + val found = findPattern(string, end) + start = found.start + end = found.end + if (start in 0 until end) { + text.setSpan(getSpan(found.matchType, string, colour, start, end), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE) + start += finders[found.matchType]!!.searchPrefixWidth + } + } +} + +/** + * Replaces text of the form [drawabale name] or [iconics name] with their spanned counterparts (ImageSpan). + */ +fun addDrawables(text: CharSequence, color: Int, size: Int, context: Context): Spannable { + val alignment = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) DynamicDrawableSpan.ALIGN_CENTER else DynamicDrawableSpan.ALIGN_BASELINE + + val builder = SpannableStringBuilder(text) + + val pattern = Pattern.compile("\\[(drawable|iconics) ([0-9a-z_]+)\\]") + val matcher = pattern.matcher(builder) + while (matcher.find()) { + val resourceType = matcher.group(1) + val resourceName = matcher.group(2) + ?: continue + + val drawable: Drawable? = when (resourceType) { + "iconics" -> IconicsDrawable(context, GoogleMaterial.getIcon(resourceName)) + else -> { + val drawableResourceId = context.resources.getIdentifier(resourceName, "drawable", context.packageName) + if (drawableResourceId != 0) AppCompatResources.getDrawable(context, drawableResourceId) else null + } + } + + if (drawable != null) { + drawable.setBounds(0, 0, size, size) + drawable.setTint(color) + + builder.setSpan(ImageSpan(drawable, alignment), matcher.start(), matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + } + + return builder +} + private fun clearSpans(text: Spannable, spanClass: Class) { for (span in text.getSpans(0, text.length, spanClass)) { text.removeSpan(span) @@ -136,30 +205,6 @@ private fun getSpan(matchType: FoundMatchType, string: String, colour: Int, star } } -/** Takes text containing mentions and hashtags and urls and makes them the given colour. */ -fun highlightSpans(text: Spannable, colour: Int) { - // Strip all existing colour spans. - for (spanClass in spanClasses) { - clearSpans(text, spanClass) - } - - // Colour the mentions and hashtags. - val string = text.toString() - val length = text.length - var start = 0 - var end = 0 - while (end in 0 until length && start >= 0) { - // Search for url first because it can contain the other characters - val found = findPattern(string, end) - start = found.start - end = found.end - if (start in 0 until end) { - text.setSpan(getSpan(found.matchType, string, colour, start, end), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE) - start += finders[found.matchType]!!.searchPrefixWidth - } - } -} - private fun isWordCharacters(codePoint: Int): Boolean { return (codePoint in 0x30..0x39) || // [0-9] (codePoint in 0x41..0x5a) || // [A-Z] diff --git a/app/src/main/java/com/keylesspalace/tusky/view/BackgroundMessageView.kt b/app/src/main/java/com/keylesspalace/tusky/view/BackgroundMessageView.kt index 82860f9f..4ccd5627 100644 --- a/app/src/main/java/com/keylesspalace/tusky/view/BackgroundMessageView.kt +++ b/app/src/main/java/com/keylesspalace/tusky/view/BackgroundMessageView.kt @@ -6,15 +6,16 @@ import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.widget.LinearLayout +import android.widget.TextView import androidx.annotation.DrawableRes import androidx.annotation.StringRes import com.keylesspalace.tusky.R import com.keylesspalace.tusky.databinding.ViewBackgroundMessageBinding +import com.keylesspalace.tusky.util.addDrawables import com.keylesspalace.tusky.util.visible /** - * This view is used for screens with downloadable content which may fail. - * Can show an image, text and button below them. + * This view is used for screens with content which may be empty or might have failed to download. */ class BackgroundMessageView @JvmOverloads constructor( context: Context, @@ -47,4 +48,15 @@ class BackgroundMessageView @JvmOverloads constructor( binding.button.setOnClickListener(clickListener) binding.button.visible(clickListener != null) } + + fun showHelp(@StringRes helpRes: Int) { + val size: Int = binding.helpText.textSize.toInt() + 2 + val color = binding.helpText.currentTextColor + val text = context.getText(helpRes) + val textWithDrawables = addDrawables(text, color, size, context) + + binding.helpText.setText(textWithDrawables, TextView.BufferType.SPANNABLE) + + binding.helpText.visible(true) + } } diff --git a/app/src/main/res/drawable/help_message_background.xml b/app/src/main/res/drawable/help_message_background.xml new file mode 100644 index 00000000..cb28b798 --- /dev/null +++ b/app/src/main/res/drawable/help_message_background.xml @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout-sw640dp/fragment_timeline.xml b/app/src/main/res/layout-sw640dp/fragment_timeline.xml index e85798eb..53e13a64 100644 --- a/app/src/main/res/layout-sw640dp/fragment_timeline.xml +++ b/app/src/main/res/layout-sw640dp/fragment_timeline.xml @@ -12,18 +12,6 @@ android:layout_gravity="center_horizontal" android:background="?android:attr/colorBackground"> - - - - - - + + + + + + - \ No newline at end of file + diff --git a/app/src/main/res/layout/fragment_timeline.xml b/app/src/main/res/layout/fragment_timeline.xml index d3e716d6..e24e4cd3 100644 --- a/app/src/main/res/layout/fragment_timeline.xml +++ b/app/src/main/res/layout/fragment_timeline.xml @@ -6,6 +6,28 @@ android:layout_height="match_parent" android:background="?android:attr/colorBackground"> + + + + - - - - - \ No newline at end of file + diff --git a/app/src/main/res/layout/view_background_message.xml b/app/src/main/res/layout/view_background_message.xml index 2d07ff8d..65e9cc5a 100644 --- a/app/src/main/res/layout/view_background_message.xml +++ b/app/src/main/res/layout/view_background_message.xml @@ -1,39 +1,60 @@ - + + + android:gravity="center" + android:orientation="vertical"> - + -