Added model, and made some design changes

This commit is contained in:
Toldi Balázs Ádám 2021-10-04 19:11:29 +02:00
parent 62ed014799
commit c213fd0f23
8 changed files with 263 additions and 112 deletions

View file

@ -55,12 +55,13 @@ dependencies {
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation("io.coil-kt:coil-compose:1.3.1")
implementation "com.google.accompanist:accompanist-swiperefresh:0.19.0"
implementation "androidx.compose.material:material-icons-extended:$compose_version"
implementation("io.coil-kt:coil-compose:1.3.2")
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
implementation 'androidx.activity:activity-compose:1.3.1'
implementation "androidx.navigation:navigation-compose:2.4.0-alpha09"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
implementation "com.google.accompanist:accompanist-swiperefresh:0.19.0"
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

View file

@ -3,16 +3,12 @@ package eu.toldi.balazs.anotherfeedreader
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.ScrollView
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Menu
@ -20,15 +16,11 @@ import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Settings
import androidx.compose.runtime.*
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.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.fragment.app.Fragment
import coil.compose.rememberImagePainter
import com.google.accompanist.swiperefresh.SwipeRefresh
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
@ -36,16 +28,25 @@ 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.ui.theme.AnotherFeedReaderTheme
import kotlinx.coroutines.launch
import java.net.URL
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.stringResource
import eu.toldi.balazs.anotherfeedreader.entities.FeedData
class MainActivity : ComponentActivity() {
val feed = FeedGroup("magyar").apply {
addFeed(Feed(URL("https://hvg.hu/rss")))
addFeed(Feed(URL("https://telex.hu/rss")))
val feedData = FeedData().apply {
addFeed(FeedGroup("magyar").apply {
addFeed(Feed(URL("https://hvg.hu/rss")))
addFeed(Feed(URL("https://telex.hu/rss")))
})
addFeed(Feed(URL("https://www.theguardian.com/world/rss")))
}
override fun onCreate(savedInstanceState: Bundle?) {
@ -55,53 +56,62 @@ class MainActivity : ComponentActivity() {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
feed.updateFeed()
var articleList = remember { mutableStateOf(feed.articleList) }
feedData.updateFeed()
val articleList = remember { mutableStateOf(feedData.articleList) }
val scope = rememberCoroutineScope()
Log.w(null, articleList.value.size.toString())
Column {
MenuBar()
val isRefreshing by feed.isRefreshing.collectAsState()
val drawerState = rememberDrawerState(DrawerValue.Closed)
SideBar(articleList, drawerState = drawerState) {
SwipeRefresh(
state = rememberSwipeRefreshState(isRefreshing),
onRefresh = {
feed.updateFeed()
articleList.value = feed.articleList
},
) {
LazyColumn(
modifier = Modifier.fillMaxHeight()
) {
items(articleList.value.size) { index ->
val article = articleList.value[index]
Column(
Modifier
.clickable {
Log.i(null, article.title.toString())
WebView.webURL = article.url.toString()
WebView.barTitle = feed.name.toString()
startActivity(
Intent(
baseContext,
WebView::class.java
)
)
}
.padding(
horizontal = 3.dp
)
) {
showArticle(article = article, feed.name!!)
}
Column {
MenuBar {
scope.launch {
drawerState.open()
}
}
}
val isRefreshing by feedData.isRefreshing.collectAsState()
SwipeRefresh(
state = rememberSwipeRefreshState(isRefreshing),
onRefresh = {
feedData.updateFeed()
articleList.value = feedData.articleList
},
) {
LazyColumn(
modifier = Modifier.fillMaxHeight()
) {
items(articleList.value.size) { index ->
val article = articleList.value[index]
Column(
Modifier
.clickable {
Log.i(null, article.title.toString())
WebView.webURL = article.url.toString()
WebView.barTitle = feedData.name.toString()
startActivity(
Intent(
baseContext,
WebView::class.java
)
)
}
.padding(
horizontal = 3.dp
)
) {
showArticle(article = article)
}
}
}
}
}
}
}
}
@ -109,13 +119,13 @@ class MainActivity : ComponentActivity() {
}
@Composable
fun MenuBar() {
fun MenuBar(hamburgerClick: () -> Unit) {
TopAppBar(
title = {
Text("Feed reader")
Text(stringResource(id = R.string.app_name))
},
navigationIcon = {
IconButton(onClick = { }) {
IconButton(onClick = hamburgerClick) {
Icon(
Icons.Filled.Menu,
contentDescription = "Menu Hamburger"
@ -142,88 +152,143 @@ class MainActivity : ComponentActivity() {
}
@Composable
fun showArticle(article: Article, feedName: String) {
Row {
Column(Modifier.weight(1f)) {
Row {
if (article.author != null) {
Text(
article.author,
fontSize = 11.sp
)
} else Text("")
Text(
feedName,
fontSize = 11.sp,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 10.dp)
)
if (article.pubDate != null) {
Text(
article.pubDate.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)),
fontSize = 11.sp,
textAlign = TextAlign.Right,
//modifier = Modifier.width(350.dp)
)
} else Text("")
}
if (article.title != null) {
Text(
text = article.title.trim(),
fontSize= 20.sp,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
} else Text("")
if (article.description != null) {
Text(
text = article.description,
fontSize = 17.sp,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
}
}
if (article.imageURL != null)
fun showArticle(article: Article) {
Column {
if (article.imageURL != null) {
Image(
painter = rememberImagePainter(article.imageURL),
contentDescription = null,
contentScale = ContentScale.Crop,
alignment = Alignment.Center,
modifier = Modifier
.height(150.dp)
.fillMaxWidth()
.fillMaxHeight()
)
}
Row {
if (article.author != null) {
Text(
article.author,
fontSize = 11.sp
)
} else Text("")
article.feedName?.let {
Text(
it,
fontSize = 11.sp,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 10.dp),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
if (article.pubDate != null) {
Text(
article.pubDate.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)),
fontSize = 11.sp,
textAlign = TextAlign.Right,
//modifier = Modifier.width(350.dp)
)
} else Text("")
}
if (article.title != null) {
Text(
text = article.title.trim(),
fontSize = 20.sp,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
} else Text("")
if (article.description != null) {
Text(
text = article.description,
fontSize = 17.sp,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
}
}
}
@Composable
fun FeedSideBar(feed: Feed, articleList: MutableState<List<Article>>) {
Row(Modifier.fillMaxWidth()) {
feed.name?.let {
Text(
text = it,
fontSize = 26.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.clickable {
feedData.limit = if (feedData.limit == feed) {
null
} else feed
articleList.value = feedData.articleList
}
)
}
}
}
@Composable
fun SideBar(
articleList: MutableState<List<Article>>,
drawerState: DrawerState,
content: @Composable () -> Unit
) {
ModalDrawer(drawerState = drawerState, drawerContent = {
LazyColumn {
item {
Text(text=stringResource(id = R.string.all_feeds),
fontSize = 26.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.clickable {
feedData.limit = null
articleList.value = feedData.articleList
})
}
items(feedData.feedList.size) { index ->
FeedSideBar(feed = feedData.feedList[index], articleList)
}
}
}, content = content)
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
AnotherFeedReaderTheme {
Column() {
MenuBar()
MenuBar({})
val article = Article(
title = "Teszt Article",
description = "Ez az cikk azért létezik, hogy kipróbájam a cikkek megjelenítését.",
author = "Toldi Balázs",
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"
imageURL = "https://cloud.toldi.eu/index.php/apps/files_sharing/publicpreview/Aicw77SAJYBiM8B?x=1920&y=571&a=true&file=P2.JPG&scalingup=0",
feedName = "Teszt feed"
)
Column(
modifier = Modifier.padding(horizontal = 1.dp)
) {
showArticle(article, "Test feed")
showArticle(article)
showArticle(
Article(
title = "Teszt Article #2",
description = "Ez egy másik megjelenítést tesztelő cikk. Ebben egy nagyon hosszú szöveget akarok ábrázolni, aminek nem szabad egyben kiférnie. Legfeljebb 2 sort jeleníthet meg",
author = "Toldi Balázs",
pubDate = LocalDateTime.now().plusDays(-1)
), "Test2 feed"
pubDate = LocalDateTime.now().plusDays(-1),
feedName = "Teszt feed 2"
),
)
}
}
}
}
}
class FeedFragment : Fragment() {
}

View file

@ -1,10 +1,23 @@
package eu.toldi.balazs.anotherfeedreader.entities
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.w3c.dom.Document
import org.w3c.dom.Element
import org.w3c.dom.Node
import org.w3c.dom.NodeList
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import javax.xml.parsers.DocumentBuilder
import javax.xml.parsers.DocumentBuilderFactory
import android.R.xml
import java.io.StringReader
import org.xml.sax.InputSource
import java.net.URL
class Article(
@ -29,7 +42,9 @@ class Article(
*/
val author: String? = null,
val pubDate: LocalDateTime? = null
val pubDate: LocalDateTime? = null,
val feedName: String? = null
) {
/**
@ -52,7 +67,7 @@ class Article(
* Cikk beolvasása egy megadott Node objektumból. Egy hírcsatorna beolvasásakor könnyedén alkalmazható egy "item" Node-ra
* @param article Egy "item" Node egy RSS hírcsatornáról
*/
fun createFromNode(article: Node) : Article{
fun createFromNode(article: Node,feedName: String?) : Article{
val properties: NodeList = article.getChildNodes()
var title: String? = null
var url: String? = null
@ -85,7 +100,32 @@ class Article(
}
}
}
return Article(title, url, description, imageURL, author, pubDate)
if(imageURL == null && url != null) {
/* GlobalScope.launch {
val factory: DocumentBuilderFactory = DocumentBuilderFactory.newInstance()
val builder: DocumentBuilder = factory.newDocumentBuilder()
var doc: Document
withContext(Dispatchers.IO) {
val ips = InputSource()
ips.characterStream = StringReader(URL(url).readText().replace("<!doctype html>","<?xml version=\"1.0\" encoding=\"utf-8\"?>"))
doc = builder.parse(ips)
}
val metas: NodeList = doc.getElementsByTagName("meta")
for (i in 0 until metas.length) {
val meta: Node = metas.item(i)
val property = meta.attributes.getNamedItem("property")
if (property != null && property.textContent == "og:image"){
val imageProperty = meta.attributes.getNamedItem("content")
if(imageProperty != null)
imageURL = imageProperty.textContent
}
}
}*/
}
return Article(title, url, description, imageURL, author, pubDate,feedName)
}
}

View file

@ -35,7 +35,7 @@ open class Feed
/**
* A hírcsatornához tartozó cikkek listája
*/
var articleList: List<Article> = ArrayList()
open var articleList: List<Article> = ArrayList()
private val _isRefreshing = MutableStateFlow(false)
val isRefreshing: StateFlow<Boolean>
@ -79,7 +79,7 @@ open class Feed
for (i in 0 until items.length) {
val article: Node = items.item(i)
val a = Article.createFromNode(article)
val a = Article.createFromNode(article,name)
addArticle(a)
}
Log.e(null, articleCount.toString())

View file

@ -0,0 +1,20 @@
package eu.toldi.balazs.anotherfeedreader.entities
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import java.net.URL
class FeedContainer(feedURL : URL) {
private val innerFeed = Feed(feedURL)
private val _articles = MutableLiveData(innerFeed.articleList.drop(0))
val articles : LiveData<List<Article>> = _articles
fun updateFeed(){
innerFeed.updateFeed()
_articles.value = innerFeed.articleList.drop(0)
}
}

View file

@ -0,0 +1,14 @@
package eu.toldi.balazs.anotherfeedreader.entities
class FeedData : FeedGroup("MainFrame") {
var limit : Feed? = null
private var _articleList = emptyList<Article>()
override var articleList: List<Article>
get() =limit?.articleList ?: _articleList
set(value) {_articleList = value.sortedByDescending { it.pubDate }}
}

View file

@ -1,16 +1,24 @@
package eu.toldi.balazs.anotherfeedreader.entities
import android.util.Log
import java.util.ArrayList
import java.io.IOException
class FeedGroup(name: String) : Feed(name) {
open class FeedGroup(name: String) : Feed(name) {
/**
* A hírcsatornák listája
*/
var feedList: MutableList<Feed> = ArrayList()
private var _articleList = emptyList<Article>()
override var articleList: List<Article>
get() = _articleList
set(value) {
_articleList = value.sortedByDescending { it.pubDate }
}
/**
* Hírcsatorna hozzáadása a csoporthoz
* @param feed hozzáadandó hírcsatorna

View file

@ -1,4 +1,7 @@
<resources>
<string name="app_name">AnotherFeedReader</string>
<string name="title_activity_web_view">WebView</string>
<string name="feed_name">Feed reader</string>
<string name="all_feeds">All feeds</string>
<string name="bookmarks">Bookmarks</string>
</resources>