Added FeedGroups

Signed-off-by: Toldi Balázs Ádám <balazs@toldi.eu>
This commit is contained in:
Toldi Balázs Ádám 2021-10-03 21:26:49 +02:00
parent 3d57fe4526
commit ee16e5fe9b
5 changed files with 297 additions and 97 deletions

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<targetSelectedWithDropDown>
<Target>
<type value="QUICK_BOOT_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="$USER_HOME$/.android/avd/Pixel_3a_XL_API_30.avd" />
</Key>
</deviceKey>
</Target>
</targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2021-10-02T16:31:04.808303Z" />
</component>
</project>

View file

@ -6,13 +6,11 @@ import android.util.Log
import android.widget.ScrollView import android.widget.ScrollView
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.* import androidx.compose.material.*
@ -22,14 +20,21 @@ import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.res.painterResource
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.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 androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import coil.compose.rememberImagePainter
import com.google.accompanist.swiperefresh.SwipeRefresh
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.ui.theme.AnotherFeedReaderTheme import eu.toldi.balazs.anotherfeedreader.ui.theme.AnotherFeedReaderTheme
import java.net.URL import java.net.URL
import java.time.LocalDateTime import java.time.LocalDateTime
@ -38,7 +43,10 @@ import java.time.format.FormatStyle
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
val feed = Feed(URL("https://hvg.hu/rss")) val feed = FeedGroup("magyar").apply {
addFeed(Feed(URL("https://hvg.hu/rss")))
addFeed(Feed(URL("https://telex.hu/rss")))
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -47,36 +55,52 @@ class MainActivity : ComponentActivity() {
// A surface container using the 'background' color from the theme // A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) { Surface(color = MaterialTheme.colors.background) {
var articleCount = remember { mutableStateOf(feed.articleCount) } feed.updateFeed()
Log.w(null,articleCount.toString()) var articleList = remember { mutableStateOf(feed.articleList) }
Log.w(null, articleList.value.size.toString())
Column { Column {
MenuBar() MenuBar()
val scrollState = rememberScrollState() val isRefreshing by feed.isRefreshing.collectAsState()
SwipeRefresh(
state = rememberSwipeRefreshState(isRefreshing),
onRefresh = {
feed.updateFeed()
articleList.value = feed.articleList
},
) {
LazyColumn( LazyColumn(
modifier = Modifier.scrollable( modifier = Modifier.fillMaxHeight()
state = scrollState,
orientation = Orientation.Vertical
)
) { ) {
items(articleCount.value) { index -> items(articleList.value.size) { index ->
val article = articleList.value[index]
Column( Column(
Modifier Modifier
.clickable { .clickable {
Log.i(null, feed[index].title.toString()) Log.i(null, article.title.toString())
WebView.webURL = feed[index].url.toString() WebView.webURL = article.url.toString()
WebView.barTitle = feed.name.toString() WebView.barTitle = feed.name.toString()
startActivity(Intent(baseContext, WebView::class.java)) startActivity(
Intent(
baseContext,
WebView::class.java
)
)
} }
.padding( .padding(
horizontal = 3.dp horizontal = 3.dp
) )
) { ) {
showArticle(article = feed[index], feed.name!!) showArticle(article = article, feed.name!!)
} }
} }
} }
}
} }
} }
@ -119,7 +143,8 @@ class MainActivity : ComponentActivity() {
@Composable @Composable
fun showArticle(article: Article, feedName: String) { fun showArticle(article: Article, feedName: String) {
Column(modifier = Modifier.padding(all = 5.dp)) { Row {
Column(Modifier.weight(1f)) {
Row { Row {
if (article.author != null) { if (article.author != null) {
Text( Text(
@ -138,13 +163,14 @@ class MainActivity : ComponentActivity() {
article.pubDate.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)), article.pubDate.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)),
fontSize = 11.sp, fontSize = 11.sp,
textAlign = TextAlign.Right, textAlign = TextAlign.Right,
modifier = Modifier.width(400.dp) //modifier = Modifier.width(350.dp)
) )
} else Text("") } else Text("")
} }
if (article.title != null) { if (article.title != null) {
Text( Text(
text = article.title.trim(), text = article.title.trim(),
fontSize= 20.sp,
maxLines = 2, maxLines = 2,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
@ -152,12 +178,18 @@ class MainActivity : ComponentActivity() {
if (article.description != null) { if (article.description != null) {
Text( Text(
text = article.description, text = article.description,
fontSize = 12.sp, fontSize = 17.sp,
maxLines = 2, maxLines = 2,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
} }
} }
if (article.imageURL != null)
Image(
painter = rememberImagePainter(article.imageURL),
contentDescription = null,
)
}
} }
@Preview(showBackground = true) @Preview(showBackground = true)
@ -170,10 +202,11 @@ class MainActivity : ComponentActivity() {
title = "Teszt Article", title = "Teszt Article",
description = "Ez az cikk azért létezik, hogy kipróbájam a cikkek megjelenítését.", description = "Ez az cikk azért létezik, hogy kipróbájam a cikkek megjelenítését.",
author = "Toldi Balázs", author = "Toldi Balázs",
pubDate = LocalDateTime.now() pubDate = LocalDateTime.now(),
imageURL = "https://cloud.toldi.eu/index.php/apps/files_sharing/publicpreview/Aicw77SAJYBiM8B?x=1920&y=571&a=true&file=P2.JPG&scalingup=0"
) )
Column( Column(
modifier = Modifier.padding( horizontal = 1.dp) modifier = Modifier.padding(horizontal = 1.dp)
) { ) {
showArticle(article, "Test feed") showArticle(article, "Test feed")
showArticle( showArticle(

View file

@ -5,10 +5,9 @@ import android.webkit.WebView
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.material.MaterialTheme import androidx.compose.material.*
import androidx.compose.material.Surface import androidx.compose.material.icons.Icons
import androidx.compose.material.Text import androidx.compose.material.icons.filled.OpenInBrowser
import androidx.compose.material.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
@ -17,6 +16,14 @@ import eu.toldi.balazs.anotherfeedreader.WebView.Companion.webURL
import eu.toldi.balazs.anotherfeedreader.ui.theme.AnotherFeedReaderTheme import eu.toldi.balazs.anotherfeedreader.ui.theme.AnotherFeedReaderTheme
import eu.toldi.balazs.anotherfeedreader.views.CustomWebViewClient import eu.toldi.balazs.anotherfeedreader.views.CustomWebViewClient
import android.net.Uri
import android.content.Intent
import androidx.compose.material.icons.filled.ArrowLeft
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.startActivity
class WebView : ComponentActivity() { class WebView : ComponentActivity() {
companion object { companion object {
var webURL = "http://example.rog" var webURL = "http://example.rog"
@ -25,8 +32,10 @@ class WebView : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { setContent {
AnotherFeedReaderTheme { AnotherFeedReaderTheme {
// A surface container using the 'background' color from the theme // A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) { Surface(color = MaterialTheme.colors.background) {
Column() { Column() {
@ -37,27 +46,50 @@ class WebView : ComponentActivity() {
} }
} }
} }
}
@Composable
fun TopBar(topTitle: String) {
TopAppBar (
title = { Text(text = topTitle) }
)
}
@Composable @Composable
fun ShowWebView(name: String) { fun TopBar(topTitle: String) {
TopAppBar(
title = { Text(text = topTitle) },
navigationIcon = {
IconButton(onClick = {
finish()
}) {
Icon(
Icons.Filled.ArrowLeft,
contentDescription = "Back to feed"
)
}
},
actions = {
IconButton(onClick = {
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(webURL))
startActivity(browserIntent)
}) {
Icon(
Icons.Filled.OpenInBrowser,
contentDescription = "Open in browser"
)
}
}
)
}
@Composable
fun ShowWebView(name: String) {
AndroidView(factory = { ctx -> AndroidView(factory = { ctx ->
WebView(ctx).apply { WebView(ctx).apply {
loadUrl(webURL) loadUrl(webURL)
webViewClient = CustomWebViewClient() webViewClient = CustomWebViewClient()
} }
}) })
} }
@Preview(showBackground = true) @Preview(showBackground = true)
@Composable @Composable
fun DefaultPreview() { fun DefaultPreview() {
AnotherFeedReaderTheme { AnotherFeedReaderTheme {
Column { Column {
TopBar("WebView") TopBar("WebView")
@ -65,4 +97,5 @@ fun DefaultPreview() {
} }
} }
}
} }

View file

@ -3,6 +3,9 @@ package eu.toldi.balazs.anotherfeedreader.entities
import android.util.Log import android.util.Log
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.w3c.dom.Document import org.w3c.dom.Document
@ -13,7 +16,7 @@ import javax.xml.parsers.DocumentBuilder
import javax.xml.parsers.DocumentBuilderFactory import javax.xml.parsers.DocumentBuilderFactory
class Feed open class Feed
( (
/** /**
* A hírcsatorna elérési útvonala * A hírcsatorna elérési útvonala
@ -26,12 +29,17 @@ class Feed
* A hírcsatorna neve * A hírcsatorna neve
*/ */
var name: String? = null var name: String? = null
constructor(name : String) : this(URL("http://example.com")) {
this.name = name
}
/** /**
* A hírcsatornához tartozó cikkek listája * A hírcsatornához tartozó cikkek listája
*/ */
val articleList: MutableList<Article> = ArrayList() var articleList: List<Article> = ArrayList()
private val _isRefreshing = MutableStateFlow(false)
val isRefreshing: StateFlow<Boolean>
get() = _isRefreshing.asStateFlow()
init { init {
updateFeed() updateFeed()
} }
@ -40,7 +48,7 @@ class Feed
* A hírcsatornán található cikkek számának lekérése * A hírcsatornán található cikkek számának lekérése
* @return a hírcsatorna található cikkek száma * @return a hírcsatorna található cikkek száma
*/ */
val articleCount: Int open val articleCount: Int
get() = articleList.size get() = articleList.size
/** /**
@ -57,8 +65,9 @@ class Feed
* Lekéri a hírcsatorna elérési útvonalán található összes új cikket és hozzáadja a cikkek listájához. * Lekéri a hírcsatorna elérési útvonalán található összes új cikket és hozzáadja a cikkek listájához.
* *
*/ */
fun updateFeed() { open fun updateFeed() {
GlobalScope.launch { GlobalScope.launch {
_isRefreshing.emit(true)
val factory: DocumentBuilderFactory = DocumentBuilderFactory.newInstance() val factory: DocumentBuilderFactory = DocumentBuilderFactory.newInstance()
val builder: DocumentBuilder = factory.newDocumentBuilder() val builder: DocumentBuilder = factory.newDocumentBuilder()
var doc: Document var doc: Document
@ -74,6 +83,7 @@ class Feed
addArticle(a) addArticle(a)
} }
Log.e(null, articleCount.toString()) Log.e(null, articleCount.toString())
_isRefreshing.emit(false)
} }
} }
@ -96,7 +106,7 @@ class Feed
* Megjegyzés: cikket csak akkor veszi fel,ha az még nem található a listájában * Megjegyzés: cikket csak akkor veszi fel,ha az még nem található a listájában
*/ */
fun addArticle(a: Article) { fun addArticle(a: Article) {
if (!hasAricle(a)) articleList.add(a) if (!hasAricle(a)) articleList += a
} }

View file

@ -0,0 +1,107 @@
package eu.toldi.balazs.anotherfeedreader.entities
import java.util.ArrayList
import java.io.IOException
class FeedGroup(name: String) : Feed(name) {
/**
* A hírcsatornák listája
*/
var feedList: MutableList<Feed> = ArrayList()
/**
* Hírcsatorna hozzáadása a csoporthoz
* @param feed hozzáadandó hírcsatorna
* Megjegyzés: Meglévő csatorna nem adható hozzá mégegyszer
*/
fun addFeed(feed: Feed) {
if (!feedExists(feed)) {
feedList.add(feed)
for (i in 0 until feed.articleCount) {
if (!hasAricle(feed[i]))
articleList += feed[i]
}
}
}
/**
* Ellenőrzi,hogy a megadott hírcsatorna metalálható-e benne
* @param f az ellenőrizendő csatorna
* @return logikai érték az alapján,hogy a csatorna megtalálható-e a csoportban
* Megjegyzés: Csak abban az esetben tér vissza igazzal,ha a két objektum megegyezik egy a csoportban találhatóval
*/
fun feedExists(f: Feed): Boolean {
for (i in feedList.indices) {
if (f === feedList[i]) return true
}
return false
}
/**
* @see Feed.updateFeed
* @throws IOException
*/
override fun updateFeed() {
if(feedList == null) feedList = ArrayList()
if (articleList == null) articleList = ArrayList()
for (f in feedList) {
f.updateFeed()
for (i in 0 until f.articleCount) {
if (!hasAricle(f[i]))
articleList += f[i]
}
}
articleList = articleList.sortedByDescending { it.pubDate }
}
/**
* @see Feed.getArticleCount
* @return
*/
override val articleCount: Int
get() {
var result = 0
for (f in feedList) {
result += f.articleCount
}
return result
}
/**
* Hírcsatorna törlése a csoportból
* @param feed a törlendő hírcsatorna
* Megjegyzés: Ez törli az össze a csatornához tartozó cikket.
*/
fun removeFeed(feed: Feed) {
var found = false
for (i in feedList.indices) {
//Csak ha PONTOSAN megegyezik
if (feedList[i] === feed) {
feedList.removeAt(i)
found = true
break
}
}
if (found) {
val similar: MutableList<Article> = ArrayList(articleList)
val different: MutableList<Article> = ArrayList()
different.addAll(articleList)
different.addAll(feed.articleList)
similar.retainAll(feed.articleList)
different.removeAll(similar)
articleList = different
}
}
/**
* Egy hírcsatorna áthelyezése a listában. Átrendezésre használatos.
* @param f az áthelyezendő hírcsatorna
* @param index a hírcsatorna új helye
*/
fun setLocation(f: Feed, index: Int) {
feedList.remove(f)
feedList.add(index, f)
}
}