Switch to Rome (#50)

* Switch to Rome

* Fix some styles
This commit is contained in:
Ashinch 2022-05-07 00:28:17 +08:00 committed by GitHub
parent 5e569d1303
commit 4c7ff2a84e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 59 additions and 96 deletions

View File

@ -85,9 +85,8 @@ dependencies {
// https://github.com/dankito/Readability4J // https://github.com/dankito/Readability4J
implementation "net.dankito.readability4j:readability4j:$readability4j" implementation "net.dankito.readability4j:readability4j:$readability4j"
// https://github.com/muhrifqii/ParseRSS/releases // https://mvnrepository.com/artifact/com.rometools/rome
implementation "com.github.muhrifqii.ParseRSS:parserss:$parseRSS" implementation "com.rometools:rome:$rome"
implementation "com.github.muhrifqii.ParseRSS:retrofit:$parseRSS"
// https://coil-kt.github.io/coil/changelog/ // https://coil-kt.github.io/coil/changelog/
implementation("io.coil-kt:coil-compose:$coil") implementation("io.coil-kt:coil-compose:$coil")

View File

@ -30,3 +30,6 @@
-dontwarn org.xmlpull.v1.XmlPullParser -dontwarn org.xmlpull.v1.XmlPullParser
-dontwarn org.xmlpull.v1.XmlSerializer -dontwarn org.xmlpull.v1.XmlSerializer
-keep class org.xmlpull.v1.* {*;} -keep class org.xmlpull.v1.* {*;}
# Rome
-keep class com.rometools.** { *; }

View File

@ -14,7 +14,6 @@ import me.ash.reader.data.repository.*
import me.ash.reader.data.source.AppNetworkDataSource import me.ash.reader.data.source.AppNetworkDataSource
import me.ash.reader.data.source.OpmlLocalDataSource import me.ash.reader.data.source.OpmlLocalDataSource
import me.ash.reader.data.source.ReaderDatabase import me.ash.reader.data.source.ReaderDatabase
import me.ash.reader.data.source.RssNetworkDataSource
import me.ash.reader.ui.ext.* import me.ash.reader.ui.ext.*
import javax.inject.Inject import javax.inject.Inject
@ -35,9 +34,6 @@ class App : Application(), Configuration.Provider {
@Inject @Inject
lateinit var opmlLocalDataSource: OpmlLocalDataSource lateinit var opmlLocalDataSource: OpmlLocalDataSource
@Inject
lateinit var rssNetworkDataSource: RssNetworkDataSource
@Inject @Inject
lateinit var rssHelper: RssHelper lateinit var rssHelper: RssHelper

View File

@ -7,7 +7,6 @@ import dagger.hilt.components.SingletonComponent
import me.ash.reader.data.source.AppNetworkDataSource import me.ash.reader.data.source.AppNetworkDataSource
import me.ash.reader.data.source.FeverApiDataSource import me.ash.reader.data.source.FeverApiDataSource
import me.ash.reader.data.source.GoogleReaderApiDataSource import me.ash.reader.data.source.GoogleReaderApiDataSource
import me.ash.reader.data.source.RssNetworkDataSource
import javax.inject.Singleton import javax.inject.Singleton
@Module @Module
@ -19,11 +18,6 @@ object RetrofitModule {
fun provideAppNetworkDataSource(): AppNetworkDataSource = fun provideAppNetworkDataSource(): AppNetworkDataSource =
AppNetworkDataSource.getInstance() AppNetworkDataSource.getInstance()
@Provides
@Singleton
fun provideRssNetworkDataSource(): RssNetworkDataSource =
RssNetworkDataSource.getInstance()
@Provides @Provides
@Singleton @Singleton
fun provideFeverApiDataSource(): FeverApiDataSource = fun provideFeverApiDataSource(): FeverApiDataSource =

View File

@ -15,7 +15,6 @@ import me.ash.reader.data.dao.ArticleDao
import me.ash.reader.data.dao.FeedDao import me.ash.reader.data.dao.FeedDao
import me.ash.reader.data.dao.GroupDao import me.ash.reader.data.dao.GroupDao
import me.ash.reader.data.entity.* import me.ash.reader.data.entity.*
import me.ash.reader.data.source.RssNetworkDataSource
import me.ash.reader.ui.ext.currentAccountId import me.ash.reader.ui.ext.currentAccountId
import java.util.* import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -26,7 +25,6 @@ abstract class AbstractRssRepository constructor(
private val articleDao: ArticleDao, private val articleDao: ArticleDao,
private val groupDao: GroupDao, private val groupDao: GroupDao,
private val feedDao: FeedDao, private val feedDao: FeedDao,
private val rssNetworkDataSource: RssNetworkDataSource,
private val workManager: WorkManager, private val workManager: WorkManager,
private val dispatcherIO: CoroutineDispatcher, private val dispatcherIO: CoroutineDispatcher,
) { ) {

View File

@ -28,7 +28,6 @@ import me.ash.reader.data.entity.Group
import me.ash.reader.data.module.DispatcherDefault import me.ash.reader.data.module.DispatcherDefault
import me.ash.reader.data.module.DispatcherIO import me.ash.reader.data.module.DispatcherIO
import me.ash.reader.data.repository.SyncWorker.Companion.setIsSyncing import me.ash.reader.data.repository.SyncWorker.Companion.setIsSyncing
import me.ash.reader.data.source.RssNetworkDataSource
import me.ash.reader.ui.ext.currentAccountId import me.ash.reader.ui.ext.currentAccountId
import me.ash.reader.ui.ext.spacerDollar import me.ash.reader.ui.ext.spacerDollar
import me.ash.reader.ui.page.common.ExtraName import me.ash.reader.ui.page.common.ExtraName
@ -42,7 +41,6 @@ class LocalRssRepository @Inject constructor(
private val articleDao: ArticleDao, private val articleDao: ArticleDao,
private val feedDao: FeedDao, private val feedDao: FeedDao,
private val rssHelper: RssHelper, private val rssHelper: RssHelper,
private val rssNetworkDataSource: RssNetworkDataSource,
private val accountDao: AccountDao, private val accountDao: AccountDao,
private val groupDao: GroupDao, private val groupDao: GroupDao,
@DispatcherDefault @DispatcherDefault
@ -52,8 +50,7 @@ class LocalRssRepository @Inject constructor(
workManager: WorkManager, workManager: WorkManager,
) : AbstractRssRepository( ) : AbstractRssRepository(
context, accountDao, articleDao, groupDao, context, accountDao, articleDao, groupDao,
feedDao, rssNetworkDataSource, workManager, feedDao, workManager, dispatcherIO
dispatcherIO
) { ) {
private val notificationManager: NotificationManagerCompat = private val notificationManager: NotificationManagerCompat =
NotificationManagerCompat.from(context).apply { NotificationManagerCompat.from(context).apply {

View File

@ -3,6 +3,9 @@ package me.ash.reader.data.repository
import android.content.Context import android.content.Context
import android.text.Html import android.text.Html
import android.util.Log import android.util.Log
import com.rometools.rome.feed.synd.SyndFeed
import com.rometools.rome.io.SyndFeedInput
import com.rometools.rome.io.XmlReader
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -11,13 +14,13 @@ import me.ash.reader.data.entity.Article
import me.ash.reader.data.entity.Feed import me.ash.reader.data.entity.Feed
import me.ash.reader.data.entity.FeedWithArticle import me.ash.reader.data.entity.FeedWithArticle
import me.ash.reader.data.module.DispatcherIO import me.ash.reader.data.module.DispatcherIO
import me.ash.reader.data.source.RssNetworkDataSource
import me.ash.reader.ui.ext.currentAccountId import me.ash.reader.ui.ext.currentAccountId
import me.ash.reader.ui.ext.spacerDollar import me.ash.reader.ui.ext.spacerDollar
import net.dankito.readability4j.Readability4J import net.dankito.readability4j.Readability4J
import net.dankito.readability4j.extended.Readability4JExtended import net.dankito.readability4j.extended.Readability4JExtended
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import java.net.URL
import java.text.ParsePosition import java.text.ParsePosition
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -26,7 +29,6 @@ import javax.inject.Inject
class RssHelper @Inject constructor( class RssHelper @Inject constructor(
@ApplicationContext @ApplicationContext
private val context: Context, private val context: Context,
private val rssNetworkDataSource: RssNetworkDataSource,
@DispatcherIO @DispatcherIO
private val dispatcherIO: CoroutineDispatcher, private val dispatcherIO: CoroutineDispatcher,
) { ) {
@ -34,7 +36,7 @@ class RssHelper @Inject constructor(
suspend fun searchFeed(feedLink: String): FeedWithArticle { suspend fun searchFeed(feedLink: String): FeedWithArticle {
return withContext(dispatcherIO) { return withContext(dispatcherIO) {
val accountId = context.currentAccountId val accountId = context.currentAccountId
val parseRss = rssNetworkDataSource.parseRss(feedLink) val parseRss: SyndFeed = SyndFeedInput().build(XmlReader(URL(feedLink)))
val feed = Feed( val feed = Feed(
id = accountId.spacerDollar(UUID.randomUUID().toString()), id = accountId.spacerDollar(UUID.randomUUID().toString()),
name = parseRss.title!!, name = parseRss.title!!,
@ -83,28 +85,36 @@ class RssHelper @Inject constructor(
return withContext(dispatcherIO) { return withContext(dispatcherIO) {
val a = mutableListOf<Article>() val a = mutableListOf<Article>()
val accountId = context.currentAccountId val accountId = context.currentAccountId
val parseRss = rssNetworkDataSource.parseRss(feed.url) val parseRss: SyndFeed = SyndFeedInput().build(XmlReader(URL(feed.url)))
parseRss.items.forEach { parseRss.entries.forEach {
if (latestLink != null && latestLink == it.link) return@withContext a if (latestLink != null && latestLink == it.link) return@withContext a
Log.i("RLog", "request rss:\n${feed.name},${feed.url}\n${it.title}\n${it.link}\n") val desc = it.description?.value
val content = it.contents
.takeIf { it.isNotEmpty() }
?.let { it.joinToString("\n") { it.value } }
Log.i(
"RLog",
"request rss:\n" +
"name: ${feed.name}\n" +
"url: ${feed.url}\n" +
"title: ${it.title}\n" +
"desc: ${desc}\n" +
"content: ${content}\n"
)
a.add( a.add(
Article( Article(
id = accountId.spacerDollar(UUID.randomUUID().toString()), id = accountId.spacerDollar(UUID.randomUUID().toString()),
accountId = accountId, accountId = accountId,
feedId = feed.id, feedId = feed.id,
date = (it.publishDate ?: it.lastUpdated).toString().let { date = it.publishedDate ?: it.updatedDate ?: Date(),
try {
Date(it)
} catch (e: IllegalArgumentException) {
parseDate(it) ?: Date()
}
},
title = Html.fromHtml(it.title.toString()).toString(), title = Html.fromHtml(it.title.toString()).toString(),
author = it.author?.name, author = it.author,
rawDescription = it.description ?: it.summary ?: "", rawDescription = (desc ?: content) ?: "",
shortDescription = shortDescription = (Readability4JExtended("", desc ?: content ?: "")
(Readability4JExtended("", it.description ?: it.summary ?: "") .parse().textContent ?: "")
.parse().textContent ?: "").take(100).trim(), .take(100)
.trim(),
fullContent = content,
link = it.link ?: "", link = it.link ?: "",
) )
) )

View File

@ -1,9 +1,7 @@
package me.ash.reader.data.source package me.ash.reader.data.source
import com.github.muhrifqii.parserss.ParseRSS
import okhttp3.RequestBody import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import org.xmlpull.v1.XmlPullParserFactory
import retrofit2.Call import retrofit2.Call
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
@ -48,7 +46,6 @@ interface FeverApiDataSource {
fun getInstance(): FeverApiDataSource { fun getInstance(): FeverApiDataSource {
return instance ?: synchronized(this) { return instance ?: synchronized(this) {
ParseRSS.init(XmlPullParserFactory.newInstance())
instance ?: Retrofit.Builder() instance ?: Retrofit.Builder()
.baseUrl("http://10.0.2.2/api/") .baseUrl("http://10.0.2.2/api/")
.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create())

View File

@ -1,7 +1,5 @@
package me.ash.reader.data.source package me.ash.reader.data.source
import com.github.muhrifqii.parserss.ParseRSS
import org.xmlpull.v1.XmlPullParserFactory
import retrofit2.Call import retrofit2.Call
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
@ -33,7 +31,6 @@ interface GoogleReaderApiDataSource {
fun getInstance(): GoogleReaderApiDataSource { fun getInstance(): GoogleReaderApiDataSource {
return instance ?: synchronized(this) { return instance ?: synchronized(this) {
ParseRSS.init(XmlPullParserFactory.newInstance())
instance ?: Retrofit.Builder() instance ?: Retrofit.Builder()
.baseUrl("http://10.0.2.2/api/greader.php/") .baseUrl("http://10.0.2.2/api/greader.php/")
.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create())

View File

@ -1,30 +0,0 @@
package me.ash.reader.data.source
import com.github.muhrifqii.parserss.ParseRSS
import com.github.muhrifqii.parserss.RSSFeedObject
import com.github.muhrifqii.parserss.retrofit.ParseRSSConverterFactory
import org.xmlpull.v1.XmlPullParserFactory
import retrofit2.Retrofit
import retrofit2.http.GET
import retrofit2.http.Url
interface RssNetworkDataSource {
@GET
suspend fun parseRss(@Url url: String): RSSFeedObject
companion object {
private var instance: RssNetworkDataSource? = null
fun getInstance(): RssNetworkDataSource {
return instance ?: synchronized(this) {
ParseRSS.init(XmlPullParserFactory.newInstance())
instance ?: Retrofit.Builder()
.baseUrl("https://api.feeddd.org/feeds/")
.addConverterFactory(ParseRSSConverterFactory.create<RSSFeedObject>())
.build().create(RssNetworkDataSource::class.java).also {
instance = it
}
}
}
}
}

View File

@ -6,10 +6,7 @@ import android.net.http.SslError
import android.util.Log import android.util.Log
import android.webkit.* import android.webkit.*
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.*
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
@ -122,11 +119,12 @@ fun WebView(
// } // }
AndroidView( AndroidView(
modifier = modifier, modifier = modifier,//.padding(horizontal = if (content.contains("class=\"page\"")) 0.dp else 24.dp),
factory = { webView }, factory = { webView },
update = { update = {
it.apply { it.apply {
Log.i("RLog", "CustomWebView: ${content}") Log.i("RLog", "CustomWebView: ${content}")
settings.javaScriptEnabled = true
loadDataWithBaseURL( loadDataWithBaseURL(
null, null,
getStyle(color) + content, getStyle(color) + content,
@ -138,10 +136,12 @@ fun WebView(
) )
} }
@Stable
fun argbToCssColor(argb: Int): String = String.format("#%06X", 0xFFFFFF and argb) fun argbToCssColor(argb: Int): String = String.format("#%06X", 0xFFFFFF and argb)
@Stable
fun getStyle(argb: Int): String = """ fun getStyle(argb: Int): String = """
<head><style> <html><head><style>
*{ *{
padding: 0; padding: 0;
margin: 0; margin: 0;
@ -154,14 +154,13 @@ fun getStyle(argb: Int): String = """
url('/android_asset_font/font/google_sans_text_bold.TTF'); url('/android_asset_font/font/google_sans_text_bold.TTF');
} }
.page { html {
padding: 0 24px; padding: 0 24px;
} }
img, video { img, video, iframe {
margin: 0 -24px 20px; margin: 0 -24px 20px;
width: calc(100% + 48px); width: calc(100% + 48px);
height: auto;
border-top: 1px solid ${argbToCssColor(argb)}08; border-top: 1px solid ${argbToCssColor(argb)}08;
border-bottom: 1px solid ${argbToCssColor(argb)}08; border-bottom: 1px solid ${argbToCssColor(argb)}08;
} }
@ -213,5 +212,5 @@ h1,h2,h3,h4,h5,h6,figure,br {
} }
.element::-webkit-scrollbar { width: 0 !important } .element::-webkit-scrollbar { width: 0 !important }
</style></head> </style></head></html>
""" """

View File

@ -190,7 +190,7 @@ fun FeedsPage(
item { item {
Banner( Banner(
title = filterState.filter.getName(), title = filterState.filter.getName(),
desc = feedsViewState.importantCount, desc = feedsViewState.importantCount.ifEmpty { stringResource(R.string.loading) },
icon = filterState.filter.iconOutline, icon = filterState.filter.iconOutline,
action = { action = {
Icon( Icon(

View File

@ -121,7 +121,7 @@ fun ArticleItem(
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
) )
// Description // Description
if (articleListDesc.value) { if (articleListDesc.value && articleWithFeed.article.shortDescription.isNotBlank()) {
Text( Text(
text = articleWithFeed.article.shortDescription, text = articleWithFeed.article.shortDescription,
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f), color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),

View File

@ -273,6 +273,7 @@ fun FlowPage(
if (pagingItems.loadState.source.refresh is LoadState.NotLoading && pagingItems.itemCount != 0) { if (pagingItems.loadState.source.refresh is LoadState.NotLoading && pagingItems.itemCount != 0) {
Spacer(modifier = Modifier.height(64.dp)) Spacer(modifier = Modifier.height(64.dp))
} }
Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars))
} }
} }
} }

View File

@ -46,11 +46,13 @@ fun Header(
) )
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))
articleWithFeed.article.author?.let { articleWithFeed.article.author?.let {
Text( if (it.isNotEmpty()) {
text = articleWithFeed.article.author, Text(
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f), text = it,
style = MaterialTheme.typography.labelMedium, color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f),
) style = MaterialTheme.typography.labelMedium,
)
}
} }
Text( Text(
text = articleWithFeed.feed.name, text = articleWithFeed.feed.name,

View File

@ -54,10 +54,8 @@ class ReadViewModel @Inject constructor(
private fun renderDescriptionContent() { private fun renderDescriptionContent() {
_viewState.update { _viewState.update {
it.copy( it.copy(
content = rssHelper.parseDescriptionContent( content = it.articleWithFeed?.article?.fullContent
link = it.articleWithFeed?.article?.link ?: "", ?: it.articleWithFeed?.article?.rawDescription ?: "",
content = it.articleWithFeed?.article?.rawDescription ?: "",
)
) )
} }
} }

View File

@ -8,6 +8,7 @@
<string name="starred_desc">%1$d 项已加星标</string> <string name="starred_desc">%1$d 项已加星标</string>
<string name="feeds">分组</string> <string name="feeds">分组</string>
<string name="syncing">正在同步…</string> <string name="syncing">正在同步…</string>
<string name="loading">加载中…</string>
<string name="expand_less">收缩</string> <string name="expand_less">收缩</string>
<string name="expand_more">展开</string> <string name="expand_more">展开</string>
<string name="confirm">确认</string> <string name="confirm">确认</string>

View File

@ -8,6 +8,7 @@
<string name="starred_desc">%1$d Starred Items</string> <string name="starred_desc">%1$d Starred Items</string>
<string name="feeds">Feeds</string> <string name="feeds">Feeds</string>
<string name="syncing">Syncing…</string> <string name="syncing">Syncing…</string>
<string name="loading">Loading…</string>
<string name="expand_less">Expand Less</string> <string name="expand_less">Expand Less</string>
<string name="expand_more">Expand More</string> <string name="expand_more">Expand More</string>
<string name="confirm">Confirm</string> <string name="confirm">Confirm</string>

View File

@ -12,7 +12,7 @@ buildscript {
profileinstaller = '1.2.0-alpha02' profileinstaller = '1.2.0-alpha02'
retrofit2 = '2.9.0' retrofit2 = '2.9.0'
coil = '2.0.0-rc03' coil = '2.0.0-rc03'
parseRSS = '0.6.0' rome = '1.18.0'
readability4j = '1.0.8' readability4j = '1.0.8'
opmlParser = '2.2.0' opmlParser = '2.2.0'
androidSVG = '1.4' androidSVG = '1.4'