Added more design elements and fixed some bugs
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
22cd651405
commit
1770781655
4 changed files with 83 additions and 48 deletions
|
@ -62,6 +62,7 @@ dependencies {
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0'
|
||||||
implementation "com.google.accompanist:accompanist-swiperefresh:0.19.0"
|
implementation "com.google.accompanist:accompanist-swiperefresh:0.19.0"
|
||||||
implementation "org.jsoup:jsoup:1.13.1"
|
implementation "org.jsoup:jsoup:1.13.1"
|
||||||
|
implementation "net.mm2d.touchicon:touchicon:0.9.1"
|
||||||
testImplementation 'junit:junit:4.+'
|
testImplementation 'junit:junit:4.+'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||||
|
|
|
@ -2,7 +2,6 @@ package eu.toldi.balazs.anotherfeedreader
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
|
||||||
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.Image
|
||||||
|
@ -10,6 +9,7 @@ import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.gestures.detectTapGestures
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Menu
|
import androidx.compose.material.icons.filled.Menu
|
||||||
|
@ -18,10 +18,12 @@ import androidx.compose.material.icons.filled.Settings
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
|
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
|
||||||
|
@ -62,11 +64,10 @@ class MainActivity : ComponentActivity() {
|
||||||
Surface(color = MaterialTheme.colors.background) {
|
Surface(color = MaterialTheme.colors.background) {
|
||||||
|
|
||||||
feedData.updateFeed()
|
feedData.updateFeed()
|
||||||
val articleList = remember { mutableStateOf(feedData.articleList) }
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
Log.w(null, articleList.value.size.toString())
|
val scope = rememberCoroutineScope()
|
||||||
val drawerState = rememberDrawerState(DrawerValue.Closed)
|
val drawerState = rememberDrawerState(DrawerValue.Closed)
|
||||||
|
val articleList = remember { mutableStateOf(feedData.articleList) }
|
||||||
SideBar(articleList, drawerState = drawerState) {
|
SideBar(articleList, drawerState = drawerState) {
|
||||||
|
|
||||||
|
|
||||||
|
@ -143,7 +144,7 @@ class MainActivity : ComponentActivity() {
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
Card(
|
Card(
|
||||||
Modifier
|
Modifier
|
||||||
.pointerInput(Unit) {
|
.pointerInput(Unit) {
|
||||||
detectTapGestures(
|
detectTapGestures(
|
||||||
onLongPress = {
|
onLongPress = {
|
||||||
val share =
|
val share =
|
||||||
|
@ -164,51 +165,58 @@ class MainActivity : ComponentActivity() {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
.padding(
|
||||||
|
start = 6.dp,
|
||||||
|
top = 2.dp,
|
||||||
|
bottom = 6.dp,
|
||||||
|
end = 6.dp
|
||||||
|
)
|
||||||
.clickable {
|
.clickable {
|
||||||
|
|
||||||
WebView.webURL = article.url.toString()
|
WebView.webURL = article.url.toString()
|
||||||
WebView.barTitle =
|
WebView.barTitle =
|
||||||
article.feedName.toString()
|
article.feed.name.toString()
|
||||||
startActivity(
|
startActivity(
|
||||||
Intent(
|
Intent(
|
||||||
baseContext,
|
baseContext,
|
||||||
WebView::class.java
|
WebView::class.java
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
|
||||||
.padding(
|
},
|
||||||
start = 6.dp,
|
|
||||||
top = 2.dp,
|
|
||||||
bottom = 6.dp,
|
|
||||||
end = 6.dp
|
|
||||||
),
|
|
||||||
elevation = 10.dp,
|
elevation = 10.dp,
|
||||||
backgroundColor = MaterialTheme.colors.surface
|
backgroundColor = MaterialTheme.colors.surface
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
if (article.imageURL != null) {
|
Row(
|
||||||
|
modifier = Modifier.padding(horizontal = 3.dp, vertical = 6.dp),
|
||||||
|
horizontalArrangement = Arrangement.Start,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
val painter = if (article.feed.faviconURL != null) {
|
||||||
|
rememberImagePainter(article.feed.faviconURL)
|
||||||
|
} else {
|
||||||
|
painterResource(id = R.drawable.ic_launcher_background)
|
||||||
|
}
|
||||||
Image(
|
Image(
|
||||||
painter = rememberImagePainter(article.imageURL),
|
painter = painter,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
contentScale = ContentScale.Crop,
|
|
||||||
alignment = Alignment.Center,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(200.dp)
|
.size(30.dp)
|
||||||
.fillMaxWidth()
|
.clip(CircleShape)
|
||||||
.fillMaxHeight()
|
|
||||||
)
|
)
|
||||||
}
|
article.author?.let {
|
||||||
Row {
|
|
||||||
if (article.author != null) {
|
|
||||||
Text(
|
Text(
|
||||||
text = (fun(): String {
|
text = (fun(): String {
|
||||||
if (article.author.length > 25)
|
if (it.length > 25)
|
||||||
return article.author.dropLast(article.author.length - 25)
|
return it.dropLast(it.length - 25)
|
||||||
else return article.author
|
else return it
|
||||||
}).invoke(),
|
}).invoke(),
|
||||||
fontSize = 11.sp
|
fontSize = 11.sp,
|
||||||
|
modifier = Modifier.padding(horizontal = 2.dp)
|
||||||
)
|
)
|
||||||
} else Text("")
|
}
|
||||||
article.feedName?.let {
|
article.feed.name?.let {
|
||||||
Text(
|
Text(
|
||||||
text = (fun(): String {
|
text = (fun(): String {
|
||||||
return if (it.length > 25)
|
return if (it.length > 25)
|
||||||
|
@ -222,14 +230,26 @@ class MainActivity : ComponentActivity() {
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (article.pubDate != null) {
|
article.pubDate?.let {
|
||||||
Text(
|
Text(
|
||||||
article.pubDate.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)),
|
it.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)),
|
||||||
fontSize = 11.sp,
|
fontSize = 11.sp,
|
||||||
textAlign = TextAlign.Right,
|
textAlign = TextAlign.Right,
|
||||||
//modifier = Modifier.width(350.dp)
|
//modifier = Modifier.width(350.dp)
|
||||||
)
|
)
|
||||||
} else Text("")
|
}
|
||||||
|
}
|
||||||
|
if (article.imageURL != null) {
|
||||||
|
Image(
|
||||||
|
painter = rememberImagePainter(article.imageURL),
|
||||||
|
contentDescription = null,
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
alignment = Alignment.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.height(200.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.fillMaxHeight()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (article.title != null) {
|
if (article.title != null) {
|
||||||
Text(
|
Text(
|
||||||
|
@ -308,7 +328,7 @@ class MainActivity : ComponentActivity() {
|
||||||
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(),
|
||||||
feedName = "Teszt feed"
|
feed = Feed("Test 1")
|
||||||
)
|
)
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(horizontal = 1.dp)
|
modifier = Modifier.padding(horizontal = 1.dp)
|
||||||
|
@ -320,8 +340,8 @@ class MainActivity : ComponentActivity() {
|
||||||
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",
|
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",
|
author = "Toldi Balázs",
|
||||||
pubDate = LocalDateTime.now().plusDays(-1),
|
pubDate = LocalDateTime.now().plusDays(-1),
|
||||||
feedName = "Teszt feed 2"
|
feed = Feed("Test 1")
|
||||||
),
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ class Article(
|
||||||
|
|
||||||
val pubDate: LocalDateTime? = null,
|
val pubDate: LocalDateTime? = null,
|
||||||
|
|
||||||
val feedName: String? = null
|
val feed: Feed
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,7 +56,7 @@ class Article(
|
||||||
* Cikk beolvasása egy megadott Node objektumból. Egy hírcsatorna beolvasásakor könnyedén alkalmazható egy "item" Node-ra
|
* 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
|
* @param article Egy "item" Node egy RSS hírcsatornáról
|
||||||
*/
|
*/
|
||||||
fun createFromNode(article: Node, feedName: String?): Article {
|
fun createFromNode(article: Node, feed: Feed): Article {
|
||||||
val properties: NodeList = article.getChildNodes()
|
val properties: NodeList = article.getChildNodes()
|
||||||
var title: String? = null
|
var title: String? = null
|
||||||
var url: String? = null
|
var url: String? = null
|
||||||
|
@ -105,7 +105,7 @@ class Article(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Article(title, url, description, imageURL, author, pubDate, feedName)
|
return Article(title, url, description, imageURL, author, pubDate, feed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
package eu.toldi.balazs.anotherfeedreader.entities
|
package eu.toldi.balazs.anotherfeedreader.entities
|
||||||
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import android.util.Log
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import net.mm2d.touchicon.TouchIconExtractor
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import org.w3c.dom.Document
|
import org.w3c.dom.Document
|
||||||
import org.w3c.dom.Node
|
import org.w3c.dom.Node
|
||||||
import org.w3c.dom.NodeList
|
import org.w3c.dom.NodeList
|
||||||
|
@ -23,14 +22,17 @@ open class Feed
|
||||||
val link: URL
|
val link: URL
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
var faviconURL: String? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A hírcsatorna neve
|
* A hírcsatorna neve
|
||||||
*/
|
*/
|
||||||
var name: String? = null
|
var name: String? = null
|
||||||
constructor(name : String) : this(URL("http://example.com")) {
|
|
||||||
|
constructor(name: String) : this(URL("http://example.com")) {
|
||||||
this.name = name
|
this.name = name
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A hírcsatornához tartozó cikkek listája
|
* A hírcsatornához tartozó cikkek listája
|
||||||
*/
|
*/
|
||||||
|
@ -39,6 +41,7 @@ open class Feed
|
||||||
|
|
||||||
val isRefreshing: StateFlow<Boolean>
|
val isRefreshing: StateFlow<Boolean>
|
||||||
get() = _isRefreshing.asStateFlow()
|
get() = _isRefreshing.asStateFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
updateFeed()
|
updateFeed()
|
||||||
}
|
}
|
||||||
|
@ -69,19 +72,30 @@ open class Feed
|
||||||
_isRefreshing.emit(true)
|
_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 = withContext(Dispatchers.IO) {
|
||||||
withContext(Dispatchers.IO) {
|
try {
|
||||||
doc = builder.parse(link.toString())
|
builder.parse(link.toString())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(null, e.stackTraceToString())
|
||||||
|
builder.newDocument()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
name = doc.getElementsByTagName("title").item(0).getTextContent()
|
if (doc.getElementsByTagName("title").length > 0)
|
||||||
|
name = doc.getElementsByTagName("title").item(0).getTextContent()
|
||||||
val items: NodeList = doc.getElementsByTagName("item")
|
val items: NodeList = doc.getElementsByTagName("item")
|
||||||
|
|
||||||
for (i in 0 until items.length) {
|
for (i in 0 until items.length) {
|
||||||
val article: Node = items.item(i)
|
val article: Node = items.item(i)
|
||||||
val a = Article.createFromNode(article,name)
|
val a = Article.createFromNode(article, this@Feed)
|
||||||
addArticle(a)
|
addArticle(a)
|
||||||
}
|
}
|
||||||
articleList.sortedByDescending { it.pubDate }
|
articleList.sortedByDescending { it.pubDate }
|
||||||
|
|
||||||
|
faviconURL = withContext(Dispatchers.IO) {
|
||||||
|
val extractor = TouchIconExtractor()
|
||||||
|
extractor.fromPage("https://" + link.host)[0].url
|
||||||
|
}
|
||||||
|
Log.e(null, faviconURL.toString())
|
||||||
_isRefreshing.emit(false)
|
_isRefreshing.emit(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue