parent
5e569d1303
commit
4c7ff2a84e
|
@ -85,9 +85,8 @@ dependencies {
|
|||
// https://github.com/dankito/Readability4J
|
||||
implementation "net.dankito.readability4j:readability4j:$readability4j"
|
||||
|
||||
// https://github.com/muhrifqii/ParseRSS/releases
|
||||
implementation "com.github.muhrifqii.ParseRSS:parserss:$parseRSS"
|
||||
implementation "com.github.muhrifqii.ParseRSS:retrofit:$parseRSS"
|
||||
// https://mvnrepository.com/artifact/com.rometools/rome
|
||||
implementation "com.rometools:rome:$rome"
|
||||
|
||||
// https://coil-kt.github.io/coil/changelog/
|
||||
implementation("io.coil-kt:coil-compose:$coil")
|
||||
|
|
5
app/proguard-rules.pro
vendored
5
app/proguard-rules.pro
vendored
|
@ -29,4 +29,7 @@
|
|||
# ksoap2 XmlPullParser confusion
|
||||
-dontwarn org.xmlpull.v1.XmlPullParser
|
||||
-dontwarn org.xmlpull.v1.XmlSerializer
|
||||
-keep class org.xmlpull.v1.* {*;}
|
||||
-keep class org.xmlpull.v1.* {*;}
|
||||
|
||||
# Rome
|
||||
-keep class com.rometools.** { *; }
|
|
@ -14,7 +14,6 @@ import me.ash.reader.data.repository.*
|
|||
import me.ash.reader.data.source.AppNetworkDataSource
|
||||
import me.ash.reader.data.source.OpmlLocalDataSource
|
||||
import me.ash.reader.data.source.ReaderDatabase
|
||||
import me.ash.reader.data.source.RssNetworkDataSource
|
||||
import me.ash.reader.ui.ext.*
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -35,9 +34,6 @@ class App : Application(), Configuration.Provider {
|
|||
@Inject
|
||||
lateinit var opmlLocalDataSource: OpmlLocalDataSource
|
||||
|
||||
@Inject
|
||||
lateinit var rssNetworkDataSource: RssNetworkDataSource
|
||||
|
||||
@Inject
|
||||
lateinit var rssHelper: RssHelper
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ import dagger.hilt.components.SingletonComponent
|
|||
import me.ash.reader.data.source.AppNetworkDataSource
|
||||
import me.ash.reader.data.source.FeverApiDataSource
|
||||
import me.ash.reader.data.source.GoogleReaderApiDataSource
|
||||
import me.ash.reader.data.source.RssNetworkDataSource
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
|
@ -19,11 +18,6 @@ object RetrofitModule {
|
|||
fun provideAppNetworkDataSource(): AppNetworkDataSource =
|
||||
AppNetworkDataSource.getInstance()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideRssNetworkDataSource(): RssNetworkDataSource =
|
||||
RssNetworkDataSource.getInstance()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFeverApiDataSource(): FeverApiDataSource =
|
||||
|
|
|
@ -15,7 +15,6 @@ import me.ash.reader.data.dao.ArticleDao
|
|||
import me.ash.reader.data.dao.FeedDao
|
||||
import me.ash.reader.data.dao.GroupDao
|
||||
import me.ash.reader.data.entity.*
|
||||
import me.ash.reader.data.source.RssNetworkDataSource
|
||||
import me.ash.reader.ui.ext.currentAccountId
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
@ -26,7 +25,6 @@ abstract class AbstractRssRepository constructor(
|
|||
private val articleDao: ArticleDao,
|
||||
private val groupDao: GroupDao,
|
||||
private val feedDao: FeedDao,
|
||||
private val rssNetworkDataSource: RssNetworkDataSource,
|
||||
private val workManager: WorkManager,
|
||||
private val dispatcherIO: CoroutineDispatcher,
|
||||
) {
|
||||
|
|
|
@ -28,7 +28,6 @@ import me.ash.reader.data.entity.Group
|
|||
import me.ash.reader.data.module.DispatcherDefault
|
||||
import me.ash.reader.data.module.DispatcherIO
|
||||
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.spacerDollar
|
||||
import me.ash.reader.ui.page.common.ExtraName
|
||||
|
@ -42,7 +41,6 @@ class LocalRssRepository @Inject constructor(
|
|||
private val articleDao: ArticleDao,
|
||||
private val feedDao: FeedDao,
|
||||
private val rssHelper: RssHelper,
|
||||
private val rssNetworkDataSource: RssNetworkDataSource,
|
||||
private val accountDao: AccountDao,
|
||||
private val groupDao: GroupDao,
|
||||
@DispatcherDefault
|
||||
|
@ -52,8 +50,7 @@ class LocalRssRepository @Inject constructor(
|
|||
workManager: WorkManager,
|
||||
) : AbstractRssRepository(
|
||||
context, accountDao, articleDao, groupDao,
|
||||
feedDao, rssNetworkDataSource, workManager,
|
||||
dispatcherIO
|
||||
feedDao, workManager, dispatcherIO
|
||||
) {
|
||||
private val notificationManager: NotificationManagerCompat =
|
||||
NotificationManagerCompat.from(context).apply {
|
||||
|
|
|
@ -3,6 +3,9 @@ package me.ash.reader.data.repository
|
|||
import android.content.Context
|
||||
import android.text.Html
|
||||
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 kotlinx.coroutines.CoroutineDispatcher
|
||||
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.FeedWithArticle
|
||||
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.spacerDollar
|
||||
import net.dankito.readability4j.Readability4J
|
||||
import net.dankito.readability4j.extended.Readability4JExtended
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import java.net.URL
|
||||
import java.text.ParsePosition
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
@ -26,7 +29,6 @@ import javax.inject.Inject
|
|||
class RssHelper @Inject constructor(
|
||||
@ApplicationContext
|
||||
private val context: Context,
|
||||
private val rssNetworkDataSource: RssNetworkDataSource,
|
||||
@DispatcherIO
|
||||
private val dispatcherIO: CoroutineDispatcher,
|
||||
) {
|
||||
|
@ -34,7 +36,7 @@ class RssHelper @Inject constructor(
|
|||
suspend fun searchFeed(feedLink: String): FeedWithArticle {
|
||||
return withContext(dispatcherIO) {
|
||||
val accountId = context.currentAccountId
|
||||
val parseRss = rssNetworkDataSource.parseRss(feedLink)
|
||||
val parseRss: SyndFeed = SyndFeedInput().build(XmlReader(URL(feedLink)))
|
||||
val feed = Feed(
|
||||
id = accountId.spacerDollar(UUID.randomUUID().toString()),
|
||||
name = parseRss.title!!,
|
||||
|
@ -83,28 +85,36 @@ class RssHelper @Inject constructor(
|
|||
return withContext(dispatcherIO) {
|
||||
val a = mutableListOf<Article>()
|
||||
val accountId = context.currentAccountId
|
||||
val parseRss = rssNetworkDataSource.parseRss(feed.url)
|
||||
parseRss.items.forEach {
|
||||
val parseRss: SyndFeed = SyndFeedInput().build(XmlReader(URL(feed.url)))
|
||||
parseRss.entries.forEach {
|
||||
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(
|
||||
Article(
|
||||
id = accountId.spacerDollar(UUID.randomUUID().toString()),
|
||||
accountId = accountId,
|
||||
feedId = feed.id,
|
||||
date = (it.publishDate ?: it.lastUpdated).toString().let {
|
||||
try {
|
||||
Date(it)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
parseDate(it) ?: Date()
|
||||
}
|
||||
},
|
||||
date = it.publishedDate ?: it.updatedDate ?: Date(),
|
||||
title = Html.fromHtml(it.title.toString()).toString(),
|
||||
author = it.author?.name,
|
||||
rawDescription = it.description ?: it.summary ?: "",
|
||||
shortDescription =
|
||||
(Readability4JExtended("", it.description ?: it.summary ?: "")
|
||||
.parse().textContent ?: "").take(100).trim(),
|
||||
author = it.author,
|
||||
rawDescription = (desc ?: content) ?: "",
|
||||
shortDescription = (Readability4JExtended("", desc ?: content ?: "")
|
||||
.parse().textContent ?: "")
|
||||
.take(100)
|
||||
.trim(),
|
||||
fullContent = content,
|
||||
link = it.link ?: "",
|
||||
)
|
||||
)
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
package me.ash.reader.data.source
|
||||
|
||||
import com.github.muhrifqii.parserss.ParseRSS
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import org.xmlpull.v1.XmlPullParserFactory
|
||||
import retrofit2.Call
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
|
@ -48,7 +46,6 @@ interface FeverApiDataSource {
|
|||
|
||||
fun getInstance(): FeverApiDataSource {
|
||||
return instance ?: synchronized(this) {
|
||||
ParseRSS.init(XmlPullParserFactory.newInstance())
|
||||
instance ?: Retrofit.Builder()
|
||||
.baseUrl("http://10.0.2.2/api/")
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package me.ash.reader.data.source
|
||||
|
||||
import com.github.muhrifqii.parserss.ParseRSS
|
||||
import org.xmlpull.v1.XmlPullParserFactory
|
||||
import retrofit2.Call
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
|
@ -33,7 +31,6 @@ interface GoogleReaderApiDataSource {
|
|||
|
||||
fun getInstance(): GoogleReaderApiDataSource {
|
||||
return instance ?: synchronized(this) {
|
||||
ParseRSS.init(XmlPullParserFactory.newInstance())
|
||||
instance ?: Retrofit.Builder()
|
||||
.baseUrl("http://10.0.2.2/api/greader.php/")
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,10 +6,7 @@ import android.net.http.SslError
|
|||
import android.util.Log
|
||||
import android.webkit.*
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
@ -122,11 +119,12 @@ fun WebView(
|
|||
// }
|
||||
|
||||
AndroidView(
|
||||
modifier = modifier,
|
||||
modifier = modifier,//.padding(horizontal = if (content.contains("class=\"page\"")) 0.dp else 24.dp),
|
||||
factory = { webView },
|
||||
update = {
|
||||
it.apply {
|
||||
Log.i("RLog", "CustomWebView: ${content}")
|
||||
settings.javaScriptEnabled = true
|
||||
loadDataWithBaseURL(
|
||||
null,
|
||||
getStyle(color) + content,
|
||||
|
@ -138,10 +136,12 @@ fun WebView(
|
|||
)
|
||||
}
|
||||
|
||||
@Stable
|
||||
fun argbToCssColor(argb: Int): String = String.format("#%06X", 0xFFFFFF and argb)
|
||||
|
||||
@Stable
|
||||
fun getStyle(argb: Int): String = """
|
||||
<head><style>
|
||||
<html><head><style>
|
||||
*{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
@ -154,14 +154,13 @@ fun getStyle(argb: Int): String = """
|
|||
url('/android_asset_font/font/google_sans_text_bold.TTF');
|
||||
}
|
||||
|
||||
.page {
|
||||
html {
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
img, video {
|
||||
img, video, iframe {
|
||||
margin: 0 -24px 20px;
|
||||
width: calc(100% + 48px);
|
||||
height: auto;
|
||||
border-top: 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 }
|
||||
</style></head>
|
||||
</style></head></html>
|
||||
"""
|
|
@ -190,7 +190,7 @@ fun FeedsPage(
|
|||
item {
|
||||
Banner(
|
||||
title = filterState.filter.getName(),
|
||||
desc = feedsViewState.importantCount,
|
||||
desc = feedsViewState.importantCount.ifEmpty { stringResource(R.string.loading) },
|
||||
icon = filterState.filter.iconOutline,
|
||||
action = {
|
||||
Icon(
|
||||
|
|
|
@ -121,7 +121,7 @@ fun ArticleItem(
|
|||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
// Description
|
||||
if (articleListDesc.value) {
|
||||
if (articleListDesc.value && articleWithFeed.article.shortDescription.isNotBlank()) {
|
||||
Text(
|
||||
text = articleWithFeed.article.shortDescription,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
|
||||
|
|
|
@ -273,6 +273,7 @@ fun FlowPage(
|
|||
if (pagingItems.loadState.source.refresh is LoadState.NotLoading && pagingItems.itemCount != 0) {
|
||||
Spacer(modifier = Modifier.height(64.dp))
|
||||
}
|
||||
Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,11 +46,13 @@ fun Header(
|
|||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
articleWithFeed.article.author?.let {
|
||||
Text(
|
||||
text = articleWithFeed.article.author,
|
||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
if (it.isNotEmpty()) {
|
||||
Text(
|
||||
text = it,
|
||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.7f),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = articleWithFeed.feed.name,
|
||||
|
|
|
@ -54,10 +54,8 @@ class ReadViewModel @Inject constructor(
|
|||
private fun renderDescriptionContent() {
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
content = rssHelper.parseDescriptionContent(
|
||||
link = it.articleWithFeed?.article?.link ?: "",
|
||||
content = it.articleWithFeed?.article?.rawDescription ?: "",
|
||||
)
|
||||
content = it.articleWithFeed?.article?.fullContent
|
||||
?: it.articleWithFeed?.article?.rawDescription ?: "",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
<string name="starred_desc">%1$d 项已加星标</string>
|
||||
<string name="feeds">分组</string>
|
||||
<string name="syncing">正在同步…</string>
|
||||
<string name="loading">加载中…</string>
|
||||
<string name="expand_less">收缩</string>
|
||||
<string name="expand_more">展开</string>
|
||||
<string name="confirm">确认</string>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
<string name="starred_desc">%1$d Starred Items</string>
|
||||
<string name="feeds">Feeds</string>
|
||||
<string name="syncing">Syncing…</string>
|
||||
<string name="loading">Loading…</string>
|
||||
<string name="expand_less">Expand Less</string>
|
||||
<string name="expand_more">Expand More</string>
|
||||
<string name="confirm">Confirm</string>
|
||||
|
|
|
@ -12,7 +12,7 @@ buildscript {
|
|||
profileinstaller = '1.2.0-alpha02'
|
||||
retrofit2 = '2.9.0'
|
||||
coil = '2.0.0-rc03'
|
||||
parseRSS = '0.6.0'
|
||||
rome = '1.18.0'
|
||||
readability4j = '1.0.8'
|
||||
opmlParser = '2.2.0'
|
||||
androidSVG = '1.4'
|
||||
|
|
Loading…
Reference in New Issue
Block a user