Big progress on persistent data storage
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: Balazs Toldi <balazs@toldi.eu>
This commit is contained in:
parent
90da45609f
commit
a4b3e7e80a
10 changed files with 147 additions and 174 deletions
|
@ -26,6 +26,10 @@
|
||||||
<entry key="../../../../../layout/compose-model-1633889971370.xml" value="0.5796296296296296" />
|
<entry key="../../../../../layout/compose-model-1633889971370.xml" value="0.5796296296296296" />
|
||||||
<entry key="../../../../../layout/compose-model-1633894374457.xml" value="0.575925925925926" />
|
<entry key="../../../../../layout/compose-model-1633894374457.xml" value="0.575925925925926" />
|
||||||
<entry key="../../../../../layout/compose-model-1633894712445.xml" value="0.5" />
|
<entry key="../../../../../layout/compose-model-1633894712445.xml" value="0.5" />
|
||||||
|
<entry key="../../../../../layout/compose-model-1634049545704.xml" value="0.5722222222222222" />
|
||||||
|
<entry key="../../../../../layout/compose-model-1634049671897.xml" value="0.2777777777777778" />
|
||||||
|
<entry key="../../../../../layout/compose-model-1634139145792.xml" value="0.8684834123222749" />
|
||||||
|
<entry key="../../../../../layout/compose-model-1635019781952.xml" value="0.5435185185185185" />
|
||||||
<entry key="app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml" value="0.21458333333333332" />
|
<entry key="app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml" value="0.21458333333333332" />
|
||||||
</map>
|
</map>
|
||||||
</option>
|
</option>
|
||||||
|
|
|
@ -29,7 +29,6 @@ import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import coil.compose.rememberImagePainter
|
import coil.compose.rememberImagePainter
|
||||||
|
@ -38,16 +37,10 @@ import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
||||||
import eu.toldi.balazs.anotherfeedreader.entities.Article
|
import eu.toldi.balazs.anotherfeedreader.entities.Article
|
||||||
import eu.toldi.balazs.anotherfeedreader.entities.Feed
|
import eu.toldi.balazs.anotherfeedreader.entities.Feed
|
||||||
import eu.toldi.balazs.anotherfeedreader.entities.FeedGroup
|
import eu.toldi.balazs.anotherfeedreader.entities.FeedGroup
|
||||||
import eu.toldi.balazs.anotherfeedreader.sqlite.Repo
|
|
||||||
import eu.toldi.balazs.anotherfeedreader.ui.theme.AnotherFeedReaderTheme
|
import eu.toldi.balazs.anotherfeedreader.ui.theme.AnotherFeedReaderTheme
|
||||||
import eu.toldi.balazs.anotherfeedreader.viewmodel.FeedViewModel
|
import eu.toldi.balazs.anotherfeedreader.viewmodel.FeedViewModel
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.net.URL
|
|
||||||
import java.time.LocalDateTime
|
|
||||||
import java.time.ZoneOffset
|
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import java.time.format.FormatStyle
|
import java.time.format.FormatStyle
|
||||||
|
|
||||||
|
@ -104,8 +97,11 @@ class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
) {
|
) {
|
||||||
items(articleList.value.size) { index ->
|
items(articleList.value.size) { index ->
|
||||||
val article = articleList.value[index]
|
val articleAndFeed = articleList.value[index]
|
||||||
showArticle(article = article)
|
articleAndFeed.articles.sortedByDescending { it.pubDate }
|
||||||
|
.forEach {
|
||||||
|
showArticle(articleAndFeed.feed, article = it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,7 +146,7 @@ class MainActivity : ComponentActivity() {
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
|
|
||||||
text = if (selectedIndex == 0) "None" else viewModel.feedsState.value.filterIsInstance<FeedGroup>()[selectedIndex - 1].name.toString(),
|
text = if (selectedIndex == 0) "None" else viewModel.feedsState.value!!.filterIsInstance<FeedGroup>()[selectedIndex - 1].name.toString(),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable(onClick = { expanded = true })
|
.clickable(onClick = { expanded = true })
|
||||||
|
@ -166,13 +162,14 @@ class MainActivity : ComponentActivity() {
|
||||||
}) {
|
}) {
|
||||||
Text(text = "None")
|
Text(text = "None")
|
||||||
}
|
}
|
||||||
viewModel.feedsState.value.filterIsInstance<FeedGroup>()
|
|
||||||
|
viewModel.feedsState.value!!.filterIsInstance<FeedGroup>()
|
||||||
.forEachIndexed { index, s ->
|
.forEachIndexed { index, s ->
|
||||||
DropdownMenuItem(onClick = {
|
DropdownMenuItem(onClick = {
|
||||||
selectedIndex = index + 1
|
selectedIndex = index + 1
|
||||||
expanded = false
|
expanded = false
|
||||||
}) {
|
}) {
|
||||||
viewModel.feedsState.value[index].name?.let {
|
viewModel.feedsState.value!![index].name?.let {
|
||||||
Text(text = it)
|
Text(text = it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -196,12 +193,11 @@ class MainActivity : ComponentActivity() {
|
||||||
onClick = {
|
onClick = {
|
||||||
try {
|
try {
|
||||||
val feed = Feed(null,text)
|
val feed = Feed(null,text)
|
||||||
syncJob = GlobalScope.launch(IO) { feed.updateFeed() }
|
|
||||||
if (selectedIndex == 0)
|
if (selectedIndex == 0)
|
||||||
viewModel.addFeed(feed)
|
viewModel.addFeed(feed)
|
||||||
else {
|
else {
|
||||||
var counter = 0
|
var counter = 0
|
||||||
viewModel.feedsState.value.forEachIndexed { index, feedGroup ->
|
viewModel.feedsState.value!!.forEachIndexed { index, feedGroup ->
|
||||||
if (feedGroup is FeedGroup) {
|
if (feedGroup is FeedGroup) {
|
||||||
counter++
|
counter++
|
||||||
if (counter == selectedIndex) {
|
if (counter == selectedIndex) {
|
||||||
|
@ -260,12 +256,23 @@ class MainActivity : ComponentActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun showArticle(article: Article) {
|
fun showArticle(feed: Feed, article: Article) {
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
Card(
|
Card(
|
||||||
Modifier
|
Modifier
|
||||||
.pointerInput(Unit) {
|
.pointerInput(Unit) {
|
||||||
detectTapGestures(
|
detectTapGestures(
|
||||||
|
onTap = {
|
||||||
|
WebView.webURL = article.url.toString()
|
||||||
|
WebView.barTitle =
|
||||||
|
feed.name ?: ""
|
||||||
|
startActivity(
|
||||||
|
Intent(
|
||||||
|
baseContext,
|
||||||
|
WebView::class.java
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
onLongPress = {
|
onLongPress = {
|
||||||
val share =
|
val share =
|
||||||
Intent.createChooser(Intent().apply {
|
Intent.createChooser(Intent().apply {
|
||||||
|
@ -290,20 +297,7 @@ class MainActivity : ComponentActivity() {
|
||||||
top = 2.dp,
|
top = 2.dp,
|
||||||
bottom = 6.dp,
|
bottom = 6.dp,
|
||||||
end = 6.dp
|
end = 6.dp
|
||||||
)
|
),
|
||||||
.clickable {
|
|
||||||
|
|
||||||
WebView.webURL = article.url.toString()
|
|
||||||
WebView.barTitle =
|
|
||||||
article.feed?.name.toString() ?: ""
|
|
||||||
startActivity(
|
|
||||||
Intent(
|
|
||||||
baseContext,
|
|
||||||
WebView::class.java
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
},
|
|
||||||
elevation = 10.dp,
|
elevation = 10.dp,
|
||||||
backgroundColor = MaterialTheme.colors.surface,
|
backgroundColor = MaterialTheme.colors.surface,
|
||||||
shape = RoundedCornerShape(10.dp)
|
shape = RoundedCornerShape(10.dp)
|
||||||
|
@ -314,8 +308,8 @@ class MainActivity : ComponentActivity() {
|
||||||
horizontalArrangement = Arrangement.Start,
|
horizontalArrangement = Arrangement.Start,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
val painter = if (article.feed?.faviconURL != null) {
|
val painter = if (feed.faviconURL != null) {
|
||||||
rememberImagePainter(article.feed.faviconURL)
|
rememberImagePainter(feed.faviconURL)
|
||||||
} else {
|
} else {
|
||||||
painterResource(id = R.drawable.ic_launcher_background)
|
painterResource(id = R.drawable.ic_launcher_background)
|
||||||
}
|
}
|
||||||
|
@ -337,7 +331,7 @@ class MainActivity : ComponentActivity() {
|
||||||
modifier = Modifier.padding(horizontal = 2.dp)
|
modifier = Modifier.padding(horizontal = 2.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
article.feed?.name?.let {
|
feed.name?.let {
|
||||||
Text(
|
Text(
|
||||||
text = (fun(): String {
|
text = (fun(): String {
|
||||||
return if (it.length > 25)
|
return if (it.length > 25)
|
||||||
|
@ -476,7 +470,7 @@ class MainActivity : ComponentActivity() {
|
||||||
drawerState: DrawerState,
|
drawerState: DrawerState,
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
val feedList = viewModel.feedsState.value
|
val feedList = viewModel.feedsState?.value ?: emptyList()
|
||||||
ModalDrawer(drawerState = drawerState, drawerContent = {
|
ModalDrawer(drawerState = drawerState, drawerContent = {
|
||||||
LazyColumn(modifier = Modifier.fillMaxHeight()) {
|
LazyColumn(modifier = Modifier.fillMaxHeight()) {
|
||||||
item {
|
item {
|
||||||
|
@ -507,7 +501,7 @@ class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
}, content = content)
|
}, content = content)
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
@Preview(showBackground = true)
|
@Preview(showBackground = true)
|
||||||
@Composable
|
@Composable
|
||||||
fun DefaultPreview() {
|
fun DefaultPreview() {
|
||||||
|
@ -539,7 +533,7 @@ class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -5,8 +5,6 @@ import androidx.room.Entity
|
||||||
import androidx.room.Ignore
|
import androidx.room.Ignore
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.jsoup.Jsoup
|
import org.jsoup.Jsoup
|
||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
|
@ -74,7 +72,7 @@ data class Article(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ColumnInfo(name = "feedId")
|
@ColumnInfo(name = "containingFeedId")
|
||||||
var feedId: Long = 0
|
var feedId: Long = 0
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,23 +1,12 @@
|
||||||
package eu.toldi.balazs.anotherfeedreader.entities
|
package eu.toldi.balazs.anotherfeedreader.entities
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.room.ColumnInfo
|
import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.Ignore
|
import androidx.room.Ignore
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import eu.toldi.balazs.anotherfeedreader.MainActivity
|
|
||||||
import eu.toldi.balazs.anotherfeedreader.sqlite.Repo
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
|
||||||
import net.mm2d.touchicon.TouchIconExtractor
|
|
||||||
import org.w3c.dom.Document
|
|
||||||
import org.w3c.dom.Node
|
|
||||||
import org.w3c.dom.NodeList
|
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import javax.xml.parsers.DocumentBuilder
|
|
||||||
import javax.xml.parsers.DocumentBuilderFactory
|
|
||||||
|
|
||||||
@Entity
|
@Entity(tableName = "feed")
|
||||||
open class Feed
|
open class Feed
|
||||||
(
|
(
|
||||||
@PrimaryKey(autoGenerate = true)
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
@ -34,7 +23,7 @@ open class Feed
|
||||||
/**
|
/**
|
||||||
* A hírcsatorna neve
|
* A hírcsatorna neve
|
||||||
*/
|
*/
|
||||||
@ColumnInfo(name = "title")
|
@ColumnInfo(name = "name")
|
||||||
var name: String? = null
|
var name: String? = null
|
||||||
@Ignore
|
@Ignore
|
||||||
val link = URL(feedURL)
|
val link = URL(feedURL)
|
||||||
|
@ -46,6 +35,7 @@ open class Feed
|
||||||
/**
|
/**
|
||||||
* A hírcsatornához tartozó cikkek listája
|
* A hírcsatornához tartozó cikkek listája
|
||||||
*/
|
*/
|
||||||
|
@Ignore
|
||||||
open var articleList: List<Article> = emptyList()
|
open var articleList: List<Article> = emptyList()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -64,42 +54,6 @@ open class Feed
|
||||||
return articleList[i]
|
return articleList[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Hírcsatorna frissítése.
|
|
||||||
* Lekéri a hírcsatorna elérési útvonalán található összes új cikket és hozzáadja a cikkek listájához.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
open suspend fun updateFeed() {
|
|
||||||
val factory: DocumentBuilderFactory = DocumentBuilderFactory.newInstance()
|
|
||||||
val builder: DocumentBuilder = factory.newDocumentBuilder()
|
|
||||||
var doc: Document = withContext(Dispatchers.IO) {
|
|
||||||
try {
|
|
||||||
builder.parse(link.toString())
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(null, e.stackTraceToString())
|
|
||||||
builder.newDocument()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (doc.getElementsByTagName("title").length > 0)
|
|
||||||
name = doc.getElementsByTagName("title").item(0).getTextContent()
|
|
||||||
val items: NodeList = doc.getElementsByTagName("item")
|
|
||||||
for (i in 0 until items.length) {
|
|
||||||
val article: Node = items.item(i)
|
|
||||||
val a = withContext(Dispatchers.IO) {Article.createFromNode(article, this@Feed)}
|
|
||||||
addArticle(a)
|
|
||||||
}
|
|
||||||
articleList.sortedByDescending { it.pubDate }
|
|
||||||
|
|
||||||
faviconURL = withContext(Dispatchers.IO) {
|
|
||||||
val extractor = TouchIconExtractor()
|
|
||||||
val icons = extractor.fromPage("https://" + link.host)
|
|
||||||
if (icons.size > 0)
|
|
||||||
icons.maxByOrNull { it.inferArea() }?.url
|
|
||||||
else null
|
|
||||||
}
|
|
||||||
Log.e(null, faviconURL.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Megnézi hogy egy magadott cikk megtalálható-e a hírcsatornán
|
* Megnézi hogy egy magadott cikk megtalálható-e a hírcsatornán
|
||||||
* @param a a keresett cikk
|
* @param a a keresett cikk
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package eu.toldi.balazs.anotherfeedreader.entities
|
package eu.toldi.balazs.anotherfeedreader.entities
|
||||||
|
|
||||||
import java.io.IOException
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
@ -45,24 +44,6 @@ open class FeedGroup(name: String) : Feed(name) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @see Feed.updateFeed
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
override suspend fun updateFeed() {
|
|
||||||
if (feedList == null) feedList = ArrayList()
|
|
||||||
if (articleList == null) articleList = ArrayList()
|
|
||||||
|
|
||||||
feedList.forEach { f ->
|
|
||||||
f.updateFeed()
|
|
||||||
f.articleList.forEach {
|
|
||||||
if (!hasAricle(it))
|
|
||||||
articleList += it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
articleList = articleList.sortedByDescending { it.pubDate }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see Feed.getArticleCount
|
* @see Feed.getArticleCount
|
||||||
* @return
|
* @return
|
||||||
|
|
|
@ -1,58 +1,92 @@
|
||||||
package eu.toldi.balazs.anotherfeedreader.repository
|
package eu.toldi.balazs.anotherfeedreader.repository
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import eu.toldi.balazs.anotherfeedreader.entities.Article
|
import eu.toldi.balazs.anotherfeedreader.entities.Article
|
||||||
import eu.toldi.balazs.anotherfeedreader.entities.Feed
|
import eu.toldi.balazs.anotherfeedreader.entities.Feed
|
||||||
import eu.toldi.balazs.anotherfeedreader.entities.FeedData
|
import eu.toldi.balazs.anotherfeedreader.sqlite.FeedsAndArticles
|
||||||
import eu.toldi.balazs.anotherfeedreader.entities.FeedGroup
|
import eu.toldi.balazs.anotherfeedreader.sqlite.FeedsAndArticlesDAO
|
||||||
import eu.toldi.balazs.anotherfeedreader.sqlite.ArticleDAO
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.net.URL
|
import net.mm2d.touchicon.TouchIconExtractor
|
||||||
|
import org.w3c.dom.Document
|
||||||
|
import org.w3c.dom.Node
|
||||||
|
import org.w3c.dom.NodeList
|
||||||
|
import javax.xml.parsers.DocumentBuilder
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory
|
||||||
|
|
||||||
class FeedRepository(private val articleDAO: ArticleDAO) {
|
class FeedRepository(private val feedsAndArticleDAO: FeedsAndArticlesDAO) {
|
||||||
|
|
||||||
val feedData = FeedData().apply {
|
|
||||||
addFeed(FeedGroup("magyar").apply {
|
private val _isRefereshing: MutableState<Boolean> = mutableStateOf(false)
|
||||||
addFeed(Feed(feedURL = "https://hvg.hu/rss"))
|
|
||||||
addFeed(Feed(feedURL = "https://telex.hu/rss"))
|
public val isRefeshing: State<Boolean>
|
||||||
addFeed(Feed(feedURL = "https://24.hu/feed"))
|
get() = _isRefereshing
|
||||||
addFeed(Feed(feedURL = "https://444.hu/feed"))
|
|
||||||
})
|
fun getAllArticles(): LiveData<List<FeedsAndArticles>> {
|
||||||
addFeed(Feed(feedURL = "https://www.theguardian.com/world/rss"))
|
return feedsAndArticleDAO.getAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAllArticles(): LiveData<List<Article>> {
|
suspend fun instert(feed: Feed, article: Article) {
|
||||||
return articleDAO.getAll()
|
feedsAndArticleDAO.insertArticle(feed, article)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun instert(article: Article){
|
|
||||||
articleDAO.insertAll(article)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun delete(article: Article){
|
fun getFeedList(): LiveData<List<Feed>> = feedsAndArticleDAO.getFeeds()
|
||||||
articleDAO.delete(article)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getFeedList() : List<Feed> = feedData.feedList
|
|
||||||
|
|
||||||
suspend fun updateFeeds() {
|
suspend fun updateFeeds() {
|
||||||
var done = false
|
withContext(IO) {
|
||||||
feedData.limit?.let {
|
try {
|
||||||
it.updateFeed()
|
_isRefereshing.value = true
|
||||||
done = true
|
val feeds = feedsAndArticleDAO.getFeedsList()
|
||||||
}
|
feeds?.forEach { feed ->
|
||||||
if (!done)
|
updateFeed(feed)
|
||||||
feedData.updateFeed()
|
}
|
||||||
|
_isRefereshing.value = false
|
||||||
|
} catch (e: Exception) {
|
||||||
|
|
||||||
feedData.articleList.forEach {
|
}
|
||||||
withContext(IO){
|
|
||||||
instert(it)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun updateFeed(feed: Feed) {
|
||||||
|
val factory: DocumentBuilderFactory = DocumentBuilderFactory.newInstance()
|
||||||
|
val builder: DocumentBuilder = factory.newDocumentBuilder()
|
||||||
|
var doc: Document = withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
builder.parse(feed.link.toString())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(null, e.stackTraceToString())
|
||||||
|
builder.newDocument()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (doc.getElementsByTagName("title").length > 0)
|
||||||
|
feed.name = doc.getElementsByTagName("title").item(0).getTextContent()
|
||||||
|
val items: NodeList = doc.getElementsByTagName("item")
|
||||||
|
for (i in 0 until items.length) {
|
||||||
|
val article: Node = items.item(i)
|
||||||
|
val a = withContext(Dispatchers.IO) { Article.createFromNode(article, feed = feed) }
|
||||||
|
feedsAndArticleDAO.insertArticle(feed, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
feed.faviconURL = withContext(Dispatchers.IO) {
|
||||||
|
val extractor = TouchIconExtractor()
|
||||||
|
val icons = extractor.fromPage("https://" + feed.link.host)
|
||||||
|
if (icons.size > 0)
|
||||||
|
icons.maxByOrNull { it.inferArea() }?.url
|
||||||
|
else null
|
||||||
|
}
|
||||||
|
feedsAndArticleDAO.updateFeed(feed)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addFeed(feed: Feed) {
|
|
||||||
feedData.addFeed(feed)
|
suspend fun addFeed(feed: Feed) {
|
||||||
|
feedsAndArticleDAO.insertFeed(feed)
|
||||||
|
updateFeeds()
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -5,10 +5,12 @@ import androidx.room.Database
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
import androidx.room.RoomDatabase
|
import androidx.room.RoomDatabase
|
||||||
import eu.toldi.balazs.anotherfeedreader.entities.Article
|
import eu.toldi.balazs.anotherfeedreader.entities.Article
|
||||||
|
import eu.toldi.balazs.anotherfeedreader.entities.Feed
|
||||||
|
|
||||||
@Database(entities = [Article::class], version = 1)
|
@Database(entities = [Article::class, Feed::class], version = 4)
|
||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
abstract fun articleDao(): ArticleDAO
|
abstract fun articleDao(): ArticleDAO
|
||||||
|
abstract fun feedsAndArticlesDao(): FeedsAndArticlesDAO
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@Volatile
|
@Volatile
|
||||||
|
|
|
@ -9,7 +9,7 @@ data class FeedsAndArticles(
|
||||||
@Embedded val feed: Feed,
|
@Embedded val feed: Feed,
|
||||||
@Relation(
|
@Relation(
|
||||||
parentColumn = "feedId",
|
parentColumn = "feedId",
|
||||||
entityColumn = "feedId"
|
entityColumn = "containingFeedId"
|
||||||
)
|
)
|
||||||
val articles: List<Article>
|
val articles: List<Article>
|
||||||
)
|
)
|
|
@ -3,22 +3,35 @@ package eu.toldi.balazs.anotherfeedreader.sqlite
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.room.*
|
import androidx.room.*
|
||||||
import eu.toldi.balazs.anotherfeedreader.entities.Article
|
import eu.toldi.balazs.anotherfeedreader.entities.Article
|
||||||
|
import eu.toldi.balazs.anotherfeedreader.entities.Feed
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
interface FeedsAndArticlesDAO {
|
interface FeedsAndArticlesDAO {
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
@Query("SELECT * FROM feed")
|
@Query("SELECT * FROM feed INNER JOIN article ON feed.feedId = article.containingFeedId ORDER BY article.pubDate")
|
||||||
fun getAll(): LiveData<List<FeedsAndArticles>>
|
fun getAll(): LiveData<List<FeedsAndArticles>>
|
||||||
|
|
||||||
@Query("SELECT * FROM article WHERE articleId IN (:articleIds)")
|
@Query("SELECT * FROM feed")
|
||||||
fun loadAllByIds(articleIds: IntArray): List<Article>
|
fun getFeeds(): LiveData<List<Feed>>
|
||||||
|
|
||||||
@Query("SELECT * FROM article WHERE url=:url")
|
@Query("SELECT * FROM feed")
|
||||||
fun findArticleByURL(url: String): Article?
|
fun getFeedsList(): List<Feed>
|
||||||
|
|
||||||
|
|
||||||
|
fun insertArticle(feed: Feed, article: Article) {
|
||||||
|
feed.feedId?.let { article.feedId = it }
|
||||||
|
_insertArticles(listOf(article))
|
||||||
|
}
|
||||||
|
|
||||||
@Insert
|
@Insert
|
||||||
fun insertArticles(vararg articles: Article)
|
fun _insertArticles(articles: List<Article>)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
fun updateFeed(feed: Feed)
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
fun insertFeed(feed: Feed)
|
||||||
|
|
||||||
@Delete
|
@Delete
|
||||||
fun deleteArticle(article: Article)
|
fun deleteArticle(article: Article)
|
||||||
|
|
|
@ -1,62 +1,55 @@
|
||||||
package eu.toldi.balazs.anotherfeedreader.viewmodel
|
package eu.toldi.balazs.anotherfeedreader.viewmodel
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import eu.toldi.balazs.anotherfeedreader.entities.Article
|
|
||||||
import eu.toldi.balazs.anotherfeedreader.entities.Feed
|
import eu.toldi.balazs.anotherfeedreader.entities.Feed
|
||||||
import eu.toldi.balazs.anotherfeedreader.entities.FeedData
|
|
||||||
import eu.toldi.balazs.anotherfeedreader.entities.FeedGroup
|
|
||||||
import eu.toldi.balazs.anotherfeedreader.repository.FeedRepository
|
import eu.toldi.balazs.anotherfeedreader.repository.FeedRepository
|
||||||
import eu.toldi.balazs.anotherfeedreader.sqlite.AppDatabase
|
import eu.toldi.balazs.anotherfeedreader.sqlite.AppDatabase
|
||||||
|
import eu.toldi.balazs.anotherfeedreader.sqlite.FeedsAndArticles
|
||||||
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.net.URL
|
|
||||||
|
|
||||||
class FeedViewModel(application: Application) : AndroidViewModel(application) {
|
class FeedViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
|
|
||||||
private val feedRepo: FeedRepository
|
private val feedRepo: FeedRepository
|
||||||
val articles: LiveData<List<Article>>
|
val articles: LiveData<List<FeedsAndArticles>>
|
||||||
val feedsState = mutableStateOf(emptyList<Feed>())
|
val feedsState: LiveData<List<Feed>>
|
||||||
val isRefereshing: MutableState<Boolean> = mutableStateOf(false)
|
val isRefereshing: State<Boolean>
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val articleDAO = AppDatabase.getInstance(application).articleDao()
|
val feedsAndArticlesDao = AppDatabase.getInstance(application).feedsAndArticlesDao()
|
||||||
feedRepo = FeedRepository(articleDAO = articleDAO)
|
feedRepo = FeedRepository(feedsAndArticleDAO = feedsAndArticlesDao)
|
||||||
articles = feedRepo.getAllArticles()
|
articles = feedRepo.getAllArticles()
|
||||||
viewModelScope.launch {
|
feedsState = feedRepo.getFeedList()
|
||||||
feedsState.value = feedRepo.getFeedList()
|
isRefereshing = feedRepo.isRefeshing
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateFeeds(): Job {
|
fun updateFeeds(): Job {
|
||||||
return viewModelScope.launch {
|
return viewModelScope.launch(IO) {
|
||||||
isRefereshing.value = true
|
|
||||||
feedRepo.updateFeeds()
|
feedRepo.updateFeeds()
|
||||||
isRefereshing.value = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun changeFeed() {
|
fun changeFeed() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
isRefereshing.value = true
|
|
||||||
///articles.value = feedRepo.getFeedList()
|
|
||||||
isRefereshing.value = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun changeFeedList() {
|
fun changeFeedList() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
feedsState.value = feedRepo.getFeedList()
|
//feedsState.value = feedRepo.getFeedList().value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addFeed(feed: Feed){
|
fun addFeed(feed: Feed) {
|
||||||
feedRepo.addFeed(feed)
|
viewModelScope.launch(IO) {
|
||||||
feedsState.value = feedRepo.getFeedList()
|
feedRepo.addFeed(feed)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue