From 6e18225957b5389676236c168c95d4b153911180 Mon Sep 17 00:00:00 2001 From: Aayush Gupta Date: Sat, 26 Aug 2023 12:40:01 +0530 Subject: [PATCH] ThreadScreen: Implement posts Signed-off-by: Aayush Gupta --- .../aayush/relabs/network/XenforoInterface.kt | 12 ++ .../relabs/network/XenforoRepository.kt | 19 +++ .../relabs/network/data/post/Attachment.kt | 17 +++ .../aayush/relabs/network/data/post/Post.kt | 31 ++++ .../relabs/network/data/thread/ThreadInfo.kt | 10 ++ .../aayush/relabs/ui/components/PostItem.kt | 143 ++++++++++++++++++ .../relabs/ui/components/ThreadPreviewItem.kt | 6 +- .../aayush/relabs/ui/navigation/NavGraph.kt | 2 +- .../relabs/ui/screens/home/HomeScreen.kt | 6 +- .../relabs/ui/screens/news/NewsScreen.kt | 4 +- .../relabs/ui/screens/news/NewsViewModel.kt | 4 +- .../relabs/ui/screens/thread/ThreadScreen.kt | 46 +++++- .../ui/screens/thread/ThreadViewModel.kt | 50 ++++++ .../aayush/relabs/utils/CommonExtensions.kt | 21 +++ .../io/aayush/relabs/utils/CommonModule.kt | 13 ++ .../io/aayush/relabs/utils/DesignQuoteSpan.kt | 59 ++++++++ .../relabs/utils/LinkTransformationMethod.kt | 67 ++++++++ 17 files changed, 502 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/io/aayush/relabs/network/data/post/Attachment.kt create mode 100644 app/src/main/java/io/aayush/relabs/network/data/post/Post.kt create mode 100644 app/src/main/java/io/aayush/relabs/network/data/thread/ThreadInfo.kt create mode 100644 app/src/main/java/io/aayush/relabs/ui/components/PostItem.kt create mode 100644 app/src/main/java/io/aayush/relabs/ui/screens/thread/ThreadViewModel.kt create mode 100644 app/src/main/java/io/aayush/relabs/utils/CommonExtensions.kt create mode 100644 app/src/main/java/io/aayush/relabs/utils/DesignQuoteSpan.kt create mode 100644 app/src/main/java/io/aayush/relabs/utils/LinkTransformationMethod.kt diff --git a/app/src/main/java/io/aayush/relabs/network/XenforoInterface.kt b/app/src/main/java/io/aayush/relabs/network/XenforoInterface.kt index a9d6b17..3b8df6a 100644 --- a/app/src/main/java/io/aayush/relabs/network/XenforoInterface.kt +++ b/app/src/main/java/io/aayush/relabs/network/XenforoInterface.kt @@ -3,9 +3,11 @@ package io.aayush.relabs.network import io.aayush.relabs.network.data.alert.Alerts import io.aayush.relabs.network.data.conversation.Conversations import io.aayush.relabs.network.data.node.Nodes +import io.aayush.relabs.network.data.thread.ThreadInfo import io.aayush.relabs.network.data.thread.Threads import io.aayush.relabs.network.data.user.Me import retrofit2.http.GET +import retrofit2.http.Path import retrofit2.http.Query interface XenforoInterface { @@ -51,4 +53,14 @@ interface XenforoInterface { @GET("threads/audapp-watched/") suspend fun getWatchedThreads(): Threads + + @GET("threads/{id}") + suspend fun getThreadInfo( + @Path("id") id: Int, + @Query("with_posts") with_posts: Boolean? = null, + @Query("page") page: Int? = null, + @Query("with_first_post") with_first_post: Boolean? = null, + @Query("with_last_post") with_last_post: Boolean? = null, + @Query("order") order: String? = null + ): ThreadInfo } diff --git a/app/src/main/java/io/aayush/relabs/network/XenforoRepository.kt b/app/src/main/java/io/aayush/relabs/network/XenforoRepository.kt index 39937cd..2d81cf1 100644 --- a/app/src/main/java/io/aayush/relabs/network/XenforoRepository.kt +++ b/app/src/main/java/io/aayush/relabs/network/XenforoRepository.kt @@ -3,6 +3,7 @@ package io.aayush.relabs.network import io.aayush.relabs.network.data.alert.Alerts import io.aayush.relabs.network.data.conversation.Conversations import io.aayush.relabs.network.data.node.Nodes +import io.aayush.relabs.network.data.thread.ThreadInfo import io.aayush.relabs.network.data.thread.Threads import io.aayush.relabs.network.data.user.Me import javax.inject.Inject @@ -71,4 +72,22 @@ class XenforoRepository @Inject constructor( suspend fun getWatchedThreads(): Threads { return xenforoInterface.getWatchedThreads() } + + suspend fun getThreadInfo( + id: Int, + with_posts: Boolean? = null, + page: Int? = null, + with_first_post: Boolean? = null, + with_last_post: Boolean? = null, + order: String? = null + ): ThreadInfo { + return xenforoInterface.getThreadInfo( + id, + with_posts, + page, + with_first_post, + with_last_post, + order + ) + } } diff --git a/app/src/main/java/io/aayush/relabs/network/data/post/Attachment.kt b/app/src/main/java/io/aayush/relabs/network/data/post/Attachment.kt new file mode 100644 index 0000000..c8f1b6e --- /dev/null +++ b/app/src/main/java/io/aayush/relabs/network/data/post/Attachment.kt @@ -0,0 +1,17 @@ +package io.aayush.relabs.network.data.post + +data class Attachment( + val attach_date: Int = 0, + val attachment_id: Int = 0, + val content_id: Int = 0, + val content_type: String = String(), + val direct_url: String = String(), + val file_size: Int = 0, + val filename: String = String(), + val height: Int = 0, + val is_audio: Boolean = false, + val is_video: Boolean = false, + val thumbnail_url: String = String(), + val view_count: Int = 0, + val width: Int = 0 +) diff --git a/app/src/main/java/io/aayush/relabs/network/data/post/Post.kt b/app/src/main/java/io/aayush/relabs/network/data/post/Post.kt new file mode 100644 index 0000000..84bd412 --- /dev/null +++ b/app/src/main/java/io/aayush/relabs/network/data/post/Post.kt @@ -0,0 +1,31 @@ +package io.aayush.relabs.network.data.post + +import io.aayush.relabs.network.data.user.User + +data class Post( + val Attachments: List = emptyList(), + val User: User = User(), + val attach_count: Int = 0, + val can_edit: Boolean = false, + val can_hard_delete: Boolean = false, + val can_react: Boolean = false, + val can_soft_delete: Boolean = false, + val can_view_attachments: Boolean = false, + val is_first_post: Boolean = false, + val is_last_post: Boolean = false, + val is_reacted_to: Boolean = false, + val is_unread: Boolean = false, + val last_edit_date: Int = 0, + val message: String = String(), + val message_parsed: String = String(), + val message_state: String = String(), + val position: Int = 0, + val post_date: Int = 0, + val post_id: Int = 0, + val reaction_score: Int = 0, + val thread_id: Int = 0, + val user_id: Int = 0, + val username: String = String(), + val view_url: String = String(), + val warning_message: String = String() +) diff --git a/app/src/main/java/io/aayush/relabs/network/data/thread/ThreadInfo.kt b/app/src/main/java/io/aayush/relabs/network/data/thread/ThreadInfo.kt new file mode 100644 index 0000000..5dcef3d --- /dev/null +++ b/app/src/main/java/io/aayush/relabs/network/data/thread/ThreadInfo.kt @@ -0,0 +1,10 @@ +package io.aayush.relabs.network.data.thread + +import io.aayush.relabs.network.data.common.Pagination +import io.aayush.relabs.network.data.post.Post + +data class ThreadInfo( + val pagination: Pagination = Pagination(), + val posts: List = emptyList(), + val thread: Thread = Thread() +) diff --git a/app/src/main/java/io/aayush/relabs/ui/components/PostItem.kt b/app/src/main/java/io/aayush/relabs/ui/components/PostItem.kt new file mode 100644 index 0000000..8c7278e --- /dev/null +++ b/app/src/main/java/io/aayush/relabs/ui/components/PostItem.kt @@ -0,0 +1,143 @@ +package io.aayush.relabs.ui.components + +import android.os.Build +import android.text.format.DateUtils +import android.text.util.Linkify +import androidx.compose.foundation.background +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredSize +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.text.HtmlCompat +import coil.ImageLoader +import coil.compose.SubcomposeAsyncImage +import coil.decode.GifDecoder +import coil.decode.ImageDecoderDecoder +import coil.request.ImageRequest +import com.google.android.material.textview.MaterialTextView +import io.aayush.relabs.R +import io.aayush.relabs.network.data.post.Post +import io.aayush.relabs.utils.DesignQuoteSpan +import io.aayush.relabs.utils.LinkTransformationMethod +import io.aayush.relabs.utils.formatBlockQuotes +import java.util.Date + +@Composable +fun PostItem( + modifier: Modifier, + post: Post, + linkTransformationMethod: LinkTransformationMethod, + designQuoteSpan: DesignQuoteSpan, + onClicked: () -> Unit = {} +) { + Box( + modifier = modifier + .fillMaxWidth() + .clip(RoundedCornerShape(10.dp)) + .background( + color = if (isSystemInDarkTheme()) { + MaterialTheme.colorScheme.onSecondary + } else { + MaterialTheme.colorScheme.secondary + } + ) + ) { + Column( + modifier = Modifier.padding(10.dp), + verticalArrangement = Arrangement.spacedBy(10.dp, Alignment.Top), + horizontalAlignment = Alignment.Start + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Start + ) { + SubcomposeAsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(post.User.avatar_urls.values.first() ?: R.drawable.ic_account) + .placeholder(R.drawable.ic_account) + .crossfade(true) + .build(), + imageLoader = ImageLoader.Builder(LocalContext.current) + .components { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + add(ImageDecoderDecoder.Factory()) + } else { + add(GifDecoder.Factory()) + } + }.build(), + contentDescription = "", + contentScale = ContentScale.Crop, + modifier = Modifier + .requiredSize(64.dp) + .clip(CircleShape) + ) + Spacer(modifier = Modifier.width(10.dp)) + Column( + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.Start + ) { + Text( + text = post.User.username, + fontSize = 15.sp, + fontWeight = FontWeight.Bold, + color = Color.White + ) + Text(text = post.User.user_title, fontSize = 13.sp, color = Color.White) + Text( + text = DateUtils.getRelativeTimeSpanString( + post.post_date.toLong() * 1000L, + Date().time, + DateUtils.MINUTE_IN_MILLIS + ).toString(), + fontSize = 12.sp, + fontWeight = FontWeight.Light, + color = Color.White + ) + } + } + AndroidView( + modifier = modifier, + factory = { + MaterialTextView(it).apply { + autoLinkMask = Linkify.WEB_URLS + linksClickable = true + transformationMethod = linkTransformationMethod + setLinkTextColor(Color.White.toArgb()) + } + }, + update = { + it.setTextColor(Color.White.toArgb()) + it.text = HtmlCompat.fromHtml( + post.message_parsed + // XenForo doesn't applies line break after quote while vbulletin did + .replace("", "
") + .replace("

", "
"), + HtmlCompat.FROM_HTML_MODE_COMPACT + ).formatBlockQuotes(designQuoteSpan) + } + ) + } + } +} diff --git a/app/src/main/java/io/aayush/relabs/ui/components/ThreadPreviewItem.kt b/app/src/main/java/io/aayush/relabs/ui/components/ThreadPreviewItem.kt index 63cc2b0..1991f6d 100644 --- a/app/src/main/java/io/aayush/relabs/ui/components/ThreadPreviewItem.kt +++ b/app/src/main/java/io/aayush/relabs/ui/components/ThreadPreviewItem.kt @@ -1,10 +1,12 @@ package io.aayush.relabs.ui.components import android.os.Build +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.requiredSize import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape @@ -41,7 +43,9 @@ fun ThreadPreviewItem( onClicked: () -> Unit = {} ) { Row( - modifier = modifier, + modifier = modifier + .fillMaxWidth() + .clickable { onClicked() }, verticalAlignment = Alignment.Top, horizontalArrangement = Arrangement.Start ) { diff --git a/app/src/main/java/io/aayush/relabs/ui/navigation/NavGraph.kt b/app/src/main/java/io/aayush/relabs/ui/navigation/NavGraph.kt index 6f2b5e8..f800736 100644 --- a/app/src/main/java/io/aayush/relabs/ui/navigation/NavGraph.kt +++ b/app/src/main/java/io/aayush/relabs/ui/navigation/NavGraph.kt @@ -45,7 +45,7 @@ fun SetupNavGraph( } ) ) { - ThreadScreen(navHostController, it.arguments!!.getInt(NavArg.THREAD_ID.name).toString()) + ThreadScreen(navHostController, it.arguments!!.getInt(NavArg.THREAD_ID.name)) } } } diff --git a/app/src/main/java/io/aayush/relabs/ui/screens/home/HomeScreen.kt b/app/src/main/java/io/aayush/relabs/ui/screens/home/HomeScreen.kt index 0e5d699..5b383e0 100644 --- a/app/src/main/java/io/aayush/relabs/ui/screens/home/HomeScreen.kt +++ b/app/src/main/java/io/aayush/relabs/ui/screens/home/HomeScreen.kt @@ -34,6 +34,7 @@ import androidx.navigation.NavHostController import io.aayush.relabs.R import io.aayush.relabs.network.data.thread.Thread import io.aayush.relabs.ui.components.ThreadPreviewItem +import io.aayush.relabs.ui.navigation.Screen import kotlinx.coroutines.launch @Composable @@ -98,7 +99,10 @@ fun HomeScreen(navHostController: NavHostController, viewModel: HomeViewModel = totalReplies = thread.reply_count, views = thread.view_count, lastReplyDate = thread.last_post_date, - forum = thread.Forum.title + forum = thread.Forum.title, + onClicked = { + navHostController.navigate(Screen.Thread.withID(thread.thread_id)) + } ) } } diff --git a/app/src/main/java/io/aayush/relabs/ui/screens/news/NewsScreen.kt b/app/src/main/java/io/aayush/relabs/ui/screens/news/NewsScreen.kt index 31a694c..033f211 100644 --- a/app/src/main/java/io/aayush/relabs/ui/screens/news/NewsScreen.kt +++ b/app/src/main/java/io/aayush/relabs/ui/screens/news/NewsScreen.kt @@ -2,7 +2,6 @@ package io.aayush.relabs.ui.screens.news import android.net.Uri import android.util.Log -import androidx.browser.customtabs.CustomTabsIntent import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Column @@ -124,8 +123,7 @@ fun NewsScreen(navHostController: NavHostController, viewModel: NewsViewModel = date = article.pubDate ?: "", onClicked = { try { - CustomTabsIntent.Builder() - .build() + viewModel.customTabsIntent .launchUrl(context, Uri.parse(article.link)) } catch (exception: Exception) { Log.e(TAG, "Failed to open profile", exception) diff --git a/app/src/main/java/io/aayush/relabs/ui/screens/news/NewsViewModel.kt b/app/src/main/java/io/aayush/relabs/ui/screens/news/NewsViewModel.kt index ff84885..3bdb92b 100644 --- a/app/src/main/java/io/aayush/relabs/ui/screens/news/NewsViewModel.kt +++ b/app/src/main/java/io/aayush/relabs/ui/screens/news/NewsViewModel.kt @@ -1,5 +1,6 @@ package io.aayush.relabs.ui.screens.news +import androidx.browser.customtabs.CustomTabsIntent import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.prof.rssparser.Article @@ -12,7 +13,8 @@ import javax.inject.Inject @HiltViewModel class NewsViewModel @Inject constructor( - private val rssNewsRepository: RSSNewsRepository + private val rssNewsRepository: RSSNewsRepository, + val customTabsIntent: CustomTabsIntent ) : ViewModel() { private val _mobileFeed = MutableStateFlow>(emptyList()) diff --git a/app/src/main/java/io/aayush/relabs/ui/screens/thread/ThreadScreen.kt b/app/src/main/java/io/aayush/relabs/ui/screens/thread/ThreadScreen.kt index 5ab65fa..5d186b3 100644 --- a/app/src/main/java/io/aayush/relabs/ui/screens/thread/ThreadScreen.kt +++ b/app/src/main/java/io/aayush/relabs/ui/screens/thread/ThreadScreen.kt @@ -1,8 +1,52 @@ package io.aayush.relabs.ui.screens.thread +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavHostController +import io.aayush.relabs.R +import io.aayush.relabs.network.data.post.Post +import io.aayush.relabs.ui.components.PostItem @Composable -fun ThreadScreen(navHostController: NavHostController, threadID: String) { +@OptIn(ExperimentalMaterial3Api::class) +fun ThreadScreen( + navHostController: NavHostController, + threadID: Int, + viewModel: ThreadViewModel = hiltViewModel() +) { + LaunchedEffect(key1 = Unit) { + viewModel.getThreadInfo(threadID) + } + + Scaffold( + modifier = Modifier.fillMaxSize(), + topBar = { TopAppBar(title = { Text(text = stringResource(id = R.string.thread)) }) } + ) { + val postList: List by viewModel.posts.collectAsStateWithLifecycle() + + LazyColumn(modifier = Modifier.padding(it)) { + items(postList) { post -> + PostItem( + modifier = Modifier.padding(10.dp), + post = post, + linkTransformationMethod = viewModel.linkTransformationMethod, + designQuoteSpan = viewModel.designQuoteSpan + ) + } + } + } } diff --git a/app/src/main/java/io/aayush/relabs/ui/screens/thread/ThreadViewModel.kt b/app/src/main/java/io/aayush/relabs/ui/screens/thread/ThreadViewModel.kt new file mode 100644 index 0000000..ae890e9 --- /dev/null +++ b/app/src/main/java/io/aayush/relabs/ui/screens/thread/ThreadViewModel.kt @@ -0,0 +1,50 @@ +package io.aayush.relabs.ui.screens.thread + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import io.aayush.relabs.network.XenforoRepository +import io.aayush.relabs.network.data.post.Post +import io.aayush.relabs.network.data.thread.ThreadInfo +import io.aayush.relabs.utils.DesignQuoteSpan +import io.aayush.relabs.utils.LinkTransformationMethod +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class ThreadViewModel @Inject constructor( + private val xenforoRepository: XenforoRepository, + val linkTransformationMethod: LinkTransformationMethod, + val designQuoteSpan: DesignQuoteSpan +) : ViewModel() { + + private var currentPage = 0 + + private val _threadInfo = MutableStateFlow(ThreadInfo()) + val threadInfo = _threadInfo.asStateFlow() + + private val _posts = MutableStateFlow>(mutableListOf()) + val posts = _posts.asStateFlow() + + fun getThreadInfo(threadID: Int) { + viewModelScope.launch(Dispatchers.IO) { + _threadInfo.value = xenforoRepository.getThreadInfo(threadID, with_posts = true) + _posts.value = _threadInfo.value.posts + } + } + + fun getPosts() { + if (_threadInfo.value.thread.thread_id != 0) { + viewModelScope.launch(Dispatchers.IO) { + val response = xenforoRepository.getThreadInfo( + _threadInfo.value.thread.thread_id, + with_posts = true, + page = ++currentPage + ) + } + } + } +} diff --git a/app/src/main/java/io/aayush/relabs/utils/CommonExtensions.kt b/app/src/main/java/io/aayush/relabs/utils/CommonExtensions.kt new file mode 100644 index 0000000..ed77ddb --- /dev/null +++ b/app/src/main/java/io/aayush/relabs/utils/CommonExtensions.kt @@ -0,0 +1,21 @@ +package io.aayush.relabs.utils + +import android.text.SpannableString +import android.text.Spanned +import android.text.style.QuoteSpan +import androidx.core.text.toSpanned + +fun Spanned.formatBlockQuotes(designQuoteSpan: DesignQuoteSpan): Spanned { + val spannableString = SpannableString(this) + spannableString.apply { + val quoteSpans = this.getSpans(0, this.length, QuoteSpan::class.java) + quoteSpans.forEach { + val start = this.getSpanStart(it) + val end = this.getSpanEnd(it) + val flags = this.getSpanFlags(it) + this.removeSpan(it) + this.setSpan(designQuoteSpan, start, end, flags) + } + } + return spannableString.toSpanned() +} diff --git a/app/src/main/java/io/aayush/relabs/utils/CommonModule.kt b/app/src/main/java/io/aayush/relabs/utils/CommonModule.kt index fee74d0..ecac472 100644 --- a/app/src/main/java/io/aayush/relabs/utils/CommonModule.kt +++ b/app/src/main/java/io/aayush/relabs/utils/CommonModule.kt @@ -2,6 +2,7 @@ package io.aayush.relabs.utils import android.content.Context import android.content.SharedPreferences +import androidx.browser.customtabs.CustomTabsIntent import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -23,4 +24,16 @@ object CommonModule { fun provideSharedPrefInstance(@ApplicationContext context: Context): SharedPreferences { return context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE) } + + @Singleton + @Provides + fun providesCustomTabIntent(): CustomTabsIntent { + return CustomTabsIntent.Builder().build() + } + + @Singleton + @Provides + fun provideDesignQuoteSpan(): DesignQuoteSpan { + return DesignQuoteSpan() + } } diff --git a/app/src/main/java/io/aayush/relabs/utils/DesignQuoteSpan.kt b/app/src/main/java/io/aayush/relabs/utils/DesignQuoteSpan.kt new file mode 100644 index 0000000..2096a8c --- /dev/null +++ b/app/src/main/java/io/aayush/relabs/utils/DesignQuoteSpan.kt @@ -0,0 +1,59 @@ +package io.aayush.relabs.utils + +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.text.Layout +import android.text.style.LeadingMarginSpan +import android.text.style.LineBackgroundSpan + +class DesignQuoteSpan : LeadingMarginSpan, LineBackgroundSpan { + + private val gap = 20F + + override fun getLeadingMargin(first: Boolean): Int { + return (0F + gap).toInt() + } + + 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 + ) { + val style = p.style + val paintColor = p.color + p.style = Paint.Style.FILL + p.color = Color.TRANSPARENT + c.drawRect(x.toFloat(), top.toFloat(), x + dir * 1F, bottom.toFloat(), p) + p.style = style + p.color = paintColor + } + + override fun drawBackground( + canvas: Canvas, + paint: Paint, + left: Int, + right: Int, + top: Int, + baseline: Int, + bottom: Int, + text: CharSequence, + start: Int, + end: Int, + lineNumber: Int + ) { + val paintColor = paint.color + paint.color = Color.GRAY + canvas.drawRect(left.toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat(), paint) + paint.color = paintColor + } +} diff --git a/app/src/main/java/io/aayush/relabs/utils/LinkTransformationMethod.kt b/app/src/main/java/io/aayush/relabs/utils/LinkTransformationMethod.kt new file mode 100644 index 0000000..c60098c --- /dev/null +++ b/app/src/main/java/io/aayush/relabs/utils/LinkTransformationMethod.kt @@ -0,0 +1,67 @@ +package io.aayush.relabs.utils + +import android.graphics.Rect +import android.net.Uri +import android.os.Parcel +import android.text.Spannable +import android.text.Spanned +import android.text.method.TransformationMethod +import android.text.style.URLSpan +import android.text.util.Linkify +import android.util.Log +import android.view.View +import android.widget.TextView +import androidx.browser.customtabs.CustomTabsIntent +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class LinkTransformationMethod @Inject constructor( + private val customTabsIntent: CustomTabsIntent +) : TransformationMethod { + + private val TAG = LinkTransformationMethod::class.java.simpleName + + inner class CustomTabsURLSpan : URLSpan { + constructor(url: String?) : super(url) + constructor(src: Parcel?) : super(src!!) + + override fun onClick(widget: View) { + try { + customTabsIntent.launchUrl(widget.context, Uri.parse(url)) + } catch (exception: Exception) { + Log.e(TAG, "Failed to open link in custom tab!", exception) + super.onClick(widget) + } + } + } + + override fun getTransformation(source: CharSequence?, view: View?): CharSequence? { + if (view is TextView) { + Linkify.addLinks(view, Linkify.WEB_URLS) + if (view.text == null || view.text !is Spannable) { + return source + } + val text = view.text as Spannable + val spans = text.getSpans(0, view.length(), URLSpan::class.java) + spans.indices.reversed().forEach { + val oldSpan = spans[it] + val start = text.getSpanStart(oldSpan) + val end = text.getSpanEnd(oldSpan) + val url = oldSpan.url + text.removeSpan(oldSpan) + text.setSpan(CustomTabsURLSpan(url), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + } + return text + } + return source + } + + override fun onFocusChanged( + view: View?, + sourceText: CharSequence?, + focused: Boolean, + direction: Int, + previouslyFocusedRect: Rect? + ) {} +}