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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user