diff --git a/.idea/misc.xml b/.idea/misc.xml index 52292e5..c7b43db 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -26,6 +26,10 @@ + + + + diff --git a/app/src/main/java/eu/toldi/balazs/anotherfeedreader/MainActivity.kt b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/MainActivity.kt index 20b992d..4630091 100644 --- a/app/src/main/java/eu/toldi/balazs/anotherfeedreader/MainActivity.kt +++ b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/MainActivity.kt @@ -29,7 +29,6 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign 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.sp 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.Feed 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.viewmodel.FeedViewModel -import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job 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.FormatStyle @@ -104,8 +97,11 @@ class MainActivity : ComponentActivity() { ) { items(articleList.value.size) { index -> - val article = articleList.value[index] - showArticle(article = article) + val articleAndFeed = articleList.value[index] + articleAndFeed.articles.sortedByDescending { it.pubDate } + .forEach { + showArticle(articleAndFeed.feed, article = it) + } } } } @@ -150,7 +146,7 @@ class MainActivity : ComponentActivity() { ) { Text( - text = if (selectedIndex == 0) "None" else viewModel.feedsState.value.filterIsInstance()[selectedIndex - 1].name.toString(), + text = if (selectedIndex == 0) "None" else viewModel.feedsState.value!!.filterIsInstance()[selectedIndex - 1].name.toString(), modifier = Modifier .fillMaxWidth() .clickable(onClick = { expanded = true }) @@ -166,13 +162,14 @@ class MainActivity : ComponentActivity() { }) { Text(text = "None") } - viewModel.feedsState.value.filterIsInstance() + + viewModel.feedsState.value!!.filterIsInstance() .forEachIndexed { index, s -> DropdownMenuItem(onClick = { selectedIndex = index + 1 expanded = false }) { - viewModel.feedsState.value[index].name?.let { + viewModel.feedsState.value!![index].name?.let { Text(text = it) } } @@ -196,12 +193,11 @@ class MainActivity : ComponentActivity() { onClick = { try { val feed = Feed(null,text) - syncJob = GlobalScope.launch(IO) { feed.updateFeed() } if (selectedIndex == 0) viewModel.addFeed(feed) else { var counter = 0 - viewModel.feedsState.value.forEachIndexed { index, feedGroup -> + viewModel.feedsState.value!!.forEachIndexed { index, feedGroup -> if (feedGroup is FeedGroup) { counter++ if (counter == selectedIndex) { @@ -260,12 +256,23 @@ class MainActivity : ComponentActivity() { } @Composable - fun showArticle(article: Article) { + fun showArticle(feed: Feed, article: Article) { val haptic = LocalHapticFeedback.current Card( Modifier .pointerInput(Unit) { detectTapGestures( + onTap = { + WebView.webURL = article.url.toString() + WebView.barTitle = + feed.name ?: "" + startActivity( + Intent( + baseContext, + WebView::class.java + ) + ) + }, onLongPress = { val share = Intent.createChooser(Intent().apply { @@ -290,20 +297,7 @@ class MainActivity : ComponentActivity() { top = 2.dp, bottom = 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, backgroundColor = MaterialTheme.colors.surface, shape = RoundedCornerShape(10.dp) @@ -314,8 +308,8 @@ class MainActivity : ComponentActivity() { horizontalArrangement = Arrangement.Start, verticalAlignment = Alignment.CenterVertically ) { - val painter = if (article.feed?.faviconURL != null) { - rememberImagePainter(article.feed.faviconURL) + val painter = if (feed.faviconURL != null) { + rememberImagePainter(feed.faviconURL) } else { painterResource(id = R.drawable.ic_launcher_background) } @@ -337,7 +331,7 @@ class MainActivity : ComponentActivity() { modifier = Modifier.padding(horizontal = 2.dp) ) } - article.feed?.name?.let { + feed.name?.let { Text( text = (fun(): String { return if (it.length > 25) @@ -476,7 +470,7 @@ class MainActivity : ComponentActivity() { drawerState: DrawerState, content: @Composable () -> Unit ) { - val feedList = viewModel.feedsState.value + val feedList = viewModel.feedsState?.value ?: emptyList() ModalDrawer(drawerState = drawerState, drawerContent = { LazyColumn(modifier = Modifier.fillMaxHeight()) { item { @@ -507,7 +501,7 @@ class MainActivity : ComponentActivity() { }, content = content) } - +/* @Preview(showBackground = true) @Composable fun DefaultPreview() { @@ -539,7 +533,7 @@ class MainActivity : ComponentActivity() { } } - } + }*/ } \ No newline at end of file diff --git a/app/src/main/java/eu/toldi/balazs/anotherfeedreader/entities/Article.kt b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/entities/Article.kt index 0549980..3fedd71 100644 --- a/app/src/main/java/eu/toldi/balazs/anotherfeedreader/entities/Article.kt +++ b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/entities/Article.kt @@ -5,8 +5,6 @@ import androidx.room.Entity import androidx.room.Ignore import androidx.room.PrimaryKey import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import org.jsoup.Jsoup import org.w3c.dom.Element @@ -74,7 +72,7 @@ data class Article( } - @ColumnInfo(name = "feedId") + @ColumnInfo(name = "containingFeedId") var feedId: Long = 0 companion object { /** diff --git a/app/src/main/java/eu/toldi/balazs/anotherfeedreader/entities/Feed.kt b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/entities/Feed.kt index 06b25c5..bed7775 100644 --- a/app/src/main/java/eu/toldi/balazs/anotherfeedreader/entities/Feed.kt +++ b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/entities/Feed.kt @@ -1,23 +1,12 @@ package eu.toldi.balazs.anotherfeedreader.entities -import android.util.Log import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.Ignore 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 javax.xml.parsers.DocumentBuilder -import javax.xml.parsers.DocumentBuilderFactory -@Entity +@Entity(tableName = "feed") open class Feed ( @PrimaryKey(autoGenerate = true) @@ -34,7 +23,7 @@ open class Feed /** * A hírcsatorna neve */ - @ColumnInfo(name = "title") + @ColumnInfo(name = "name") var name: String? = null @Ignore val link = URL(feedURL) @@ -46,6 +35,7 @@ open class Feed /** * A hírcsatornához tartozó cikkek listája */ + @Ignore open var articleList: List
= emptyList() /** @@ -64,42 +54,6 @@ open class Feed 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 * @param a a keresett cikk diff --git a/app/src/main/java/eu/toldi/balazs/anotherfeedreader/entities/FeedGroup.kt b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/entities/FeedGroup.kt index 32ce0bb..743bce3 100644 --- a/app/src/main/java/eu/toldi/balazs/anotherfeedreader/entities/FeedGroup.kt +++ b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/entities/FeedGroup.kt @@ -1,6 +1,5 @@ package eu.toldi.balazs.anotherfeedreader.entities -import java.io.IOException import java.util.* @@ -45,24 +44,6 @@ open class FeedGroup(name: String) : Feed(name) { 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 * @return diff --git a/app/src/main/java/eu/toldi/balazs/anotherfeedreader/repository/FeedRepository.kt b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/repository/FeedRepository.kt index 3ccd747..e10fbd2 100644 --- a/app/src/main/java/eu/toldi/balazs/anotherfeedreader/repository/FeedRepository.kt +++ b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/repository/FeedRepository.kt @@ -1,58 +1,92 @@ 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 eu.toldi.balazs.anotherfeedreader.entities.Article 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.sqlite.ArticleDAO +import eu.toldi.balazs.anotherfeedreader.sqlite.FeedsAndArticles +import eu.toldi.balazs.anotherfeedreader.sqlite.FeedsAndArticlesDAO +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers.IO 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 { - addFeed(Feed(feedURL = "https://hvg.hu/rss")) - addFeed(Feed(feedURL = "https://telex.hu/rss")) - addFeed(Feed(feedURL = "https://24.hu/feed")) - addFeed(Feed(feedURL = "https://444.hu/feed")) - }) - addFeed(Feed(feedURL = "https://www.theguardian.com/world/rss")) + + private val _isRefereshing: MutableState = mutableStateOf(false) + + public val isRefeshing: State + get() = _isRefereshing + + fun getAllArticles(): LiveData> { + return feedsAndArticleDAO.getAll() } - fun getAllArticles(): LiveData> { - return articleDAO.getAll() + suspend fun instert(feed: Feed, article: Article) { + feedsAndArticleDAO.insertArticle(feed, article) } - suspend fun instert(article: Article){ - articleDAO.insertAll(article) - } - suspend fun delete(article: Article){ - articleDAO.delete(article) - } - - fun getFeedList() : List = feedData.feedList + fun getFeedList(): LiveData> = feedsAndArticleDAO.getFeeds() suspend fun updateFeeds() { - var done = false - feedData.limit?.let { - it.updateFeed() - done = true - } - if (!done) - feedData.updateFeed() + withContext(IO) { + try { + _isRefereshing.value = true + val feeds = feedsAndArticleDAO.getFeedsList() + feeds?.forEach { feed -> + updateFeed(feed) + } + _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() } } \ No newline at end of file diff --git a/app/src/main/java/eu/toldi/balazs/anotherfeedreader/sqlite/AppDatabase.kt b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/sqlite/AppDatabase.kt index 0cd23dc..fed2d73 100644 --- a/app/src/main/java/eu/toldi/balazs/anotherfeedreader/sqlite/AppDatabase.kt +++ b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/sqlite/AppDatabase.kt @@ -5,10 +5,12 @@ import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase 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 fun articleDao(): ArticleDAO + abstract fun feedsAndArticlesDao(): FeedsAndArticlesDAO companion object { @Volatile diff --git a/app/src/main/java/eu/toldi/balazs/anotherfeedreader/sqlite/FeedsAndArticles.kt b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/sqlite/FeedsAndArticles.kt index 7c2480a..023a45a 100644 --- a/app/src/main/java/eu/toldi/balazs/anotherfeedreader/sqlite/FeedsAndArticles.kt +++ b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/sqlite/FeedsAndArticles.kt @@ -9,7 +9,7 @@ data class FeedsAndArticles( @Embedded val feed: Feed, @Relation( parentColumn = "feedId", - entityColumn = "feedId" + entityColumn = "containingFeedId" ) val articles: List
) \ No newline at end of file diff --git a/app/src/main/java/eu/toldi/balazs/anotherfeedreader/sqlite/FeedsAndArticlesDAO.kt b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/sqlite/FeedsAndArticlesDAO.kt index d9c358f..d798668 100644 --- a/app/src/main/java/eu/toldi/balazs/anotherfeedreader/sqlite/FeedsAndArticlesDAO.kt +++ b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/sqlite/FeedsAndArticlesDAO.kt @@ -3,22 +3,35 @@ package eu.toldi.balazs.anotherfeedreader.sqlite import androidx.lifecycle.LiveData import androidx.room.* import eu.toldi.balazs.anotherfeedreader.entities.Article +import eu.toldi.balazs.anotherfeedreader.entities.Feed @Dao interface FeedsAndArticlesDAO { @Transaction - @Query("SELECT * FROM feed") + @Query("SELECT * FROM feed INNER JOIN article ON feed.feedId = article.containingFeedId ORDER BY article.pubDate") fun getAll(): LiveData> - @Query("SELECT * FROM article WHERE articleId IN (:articleIds)") - fun loadAllByIds(articleIds: IntArray): List
+ @Query("SELECT * FROM feed") + fun getFeeds(): LiveData> - @Query("SELECT * FROM article WHERE url=:url") - fun findArticleByURL(url: String): Article? + @Query("SELECT * FROM feed") + fun getFeedsList(): List + + + fun insertArticle(feed: Feed, article: Article) { + feed.feedId?.let { article.feedId = it } + _insertArticles(listOf(article)) + } @Insert - fun insertArticles(vararg articles: Article) + fun _insertArticles(articles: List
) + + @Update + fun updateFeed(feed: Feed) + + @Insert + fun insertFeed(feed: Feed) @Delete fun deleteArticle(article: Article) diff --git a/app/src/main/java/eu/toldi/balazs/anotherfeedreader/viewmodel/FeedViewModel.kt b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/viewmodel/FeedViewModel.kt index b098e64..38ba8d5 100644 --- a/app/src/main/java/eu/toldi/balazs/anotherfeedreader/viewmodel/FeedViewModel.kt +++ b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/viewmodel/FeedViewModel.kt @@ -1,62 +1,55 @@ package eu.toldi.balazs.anotherfeedreader.viewmodel import android.app.Application -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.State import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData -import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import eu.toldi.balazs.anotherfeedreader.entities.Article 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.sqlite.AppDatabase +import eu.toldi.balazs.anotherfeedreader.sqlite.FeedsAndArticles +import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import java.net.URL class FeedViewModel(application: Application) : AndroidViewModel(application) { private val feedRepo: FeedRepository - val articles: LiveData> - val feedsState = mutableStateOf(emptyList()) - val isRefereshing: MutableState = mutableStateOf(false) + val articles: LiveData> + val feedsState: LiveData> + val isRefereshing: State init { - val articleDAO = AppDatabase.getInstance(application).articleDao() - feedRepo = FeedRepository(articleDAO = articleDAO) + val feedsAndArticlesDao = AppDatabase.getInstance(application).feedsAndArticlesDao() + feedRepo = FeedRepository(feedsAndArticleDAO = feedsAndArticlesDao) articles = feedRepo.getAllArticles() - viewModelScope.launch { - feedsState.value = feedRepo.getFeedList() - } + feedsState = feedRepo.getFeedList() + isRefereshing = feedRepo.isRefeshing } fun updateFeeds(): Job { - return viewModelScope.launch { - isRefereshing.value = true + return viewModelScope.launch(IO) { feedRepo.updateFeeds() - isRefereshing.value = false } } fun changeFeed() { viewModelScope.launch { - isRefereshing.value = true - ///articles.value = feedRepo.getFeedList() - isRefereshing.value = false } } fun changeFeedList() { viewModelScope.launch { - feedsState.value = feedRepo.getFeedList() + //feedsState.value = feedRepo.getFeedList().value } } - fun addFeed(feed: Feed){ - feedRepo.addFeed(feed) - feedsState.value = feedRepo.getFeedList() + fun addFeed(feed: Feed) { + viewModelScope.launch(IO) { + feedRepo.addFeed(feed) + } } + + }