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 "com.google.accompanist:accompanist-swiperefresh:0.19.0"
|
||||
implementation "org.jsoup:jsoup:1.13.1"
|
||||
implementation "net.mm2d.touchicon:touchicon:0.9.1"
|
||||
testImplementation 'junit:junit:4.+'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
|
|
|
@ -2,7 +2,6 @@ package eu.toldi.balazs.anotherfeedreader
|
|||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.Image
|
||||
|
@ -10,6 +9,7 @@ import androidx.compose.foundation.clickable
|
|||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
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.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
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
|
||||
|
@ -62,11 +64,10 @@ class MainActivity : ComponentActivity() {
|
|||
Surface(color = MaterialTheme.colors.background) {
|
||||
|
||||
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 articleList = remember { mutableStateOf(feedData.articleList) }
|
||||
SideBar(articleList, drawerState = drawerState) {
|
||||
|
||||
|
||||
|
@ -143,7 +144,7 @@ class MainActivity : ComponentActivity() {
|
|||
val haptic = LocalHapticFeedback.current
|
||||
Card(
|
||||
Modifier
|
||||
.pointerInput(Unit) {
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures(
|
||||
onLongPress = {
|
||||
val share =
|
||||
|
@ -164,51 +165,58 @@ class MainActivity : ComponentActivity() {
|
|||
}
|
||||
)
|
||||
}
|
||||
.padding(
|
||||
start = 6.dp,
|
||||
top = 2.dp,
|
||||
bottom = 6.dp,
|
||||
end = 6.dp
|
||||
)
|
||||
.clickable {
|
||||
|
||||
WebView.webURL = article.url.toString()
|
||||
WebView.barTitle =
|
||||
article.feedName.toString()
|
||||
article.feed.name.toString()
|
||||
startActivity(
|
||||
Intent(
|
||||
baseContext,
|
||||
WebView::class.java
|
||||
)
|
||||
)
|
||||
}
|
||||
.padding(
|
||||
start = 6.dp,
|
||||
top = 2.dp,
|
||||
bottom = 6.dp,
|
||||
end = 6.dp
|
||||
),
|
||||
|
||||
},
|
||||
elevation = 10.dp,
|
||||
backgroundColor = MaterialTheme.colors.surface
|
||||
) {
|
||||
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(
|
||||
painter = rememberImagePainter(article.imageURL),
|
||||
painter = painter,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
alignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
.height(200.dp)
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight()
|
||||
.size(30.dp)
|
||||
.clip(CircleShape)
|
||||
)
|
||||
}
|
||||
Row {
|
||||
if (article.author != null) {
|
||||
article.author?.let {
|
||||
Text(
|
||||
text = (fun(): String {
|
||||
if (article.author.length > 25)
|
||||
return article.author.dropLast(article.author.length - 25)
|
||||
else return article.author
|
||||
if (it.length > 25)
|
||||
return it.dropLast(it.length - 25)
|
||||
else return it
|
||||
}).invoke(),
|
||||
fontSize = 11.sp
|
||||
fontSize = 11.sp,
|
||||
modifier = Modifier.padding(horizontal = 2.dp)
|
||||
)
|
||||
} else Text("")
|
||||
article.feedName?.let {
|
||||
}
|
||||
article.feed.name?.let {
|
||||
Text(
|
||||
text = (fun(): String {
|
||||
return if (it.length > 25)
|
||||
|
@ -222,14 +230,26 @@ class MainActivity : ComponentActivity() {
|
|||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
if (article.pubDate != null) {
|
||||
article.pubDate?.let {
|
||||
Text(
|
||||
article.pubDate.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)),
|
||||
it.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)),
|
||||
fontSize = 11.sp,
|
||||
textAlign = TextAlign.Right,
|
||||
//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) {
|
||||
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.",
|
||||
author = "Toldi Balázs",
|
||||
pubDate = LocalDateTime.now(),
|
||||
feedName = "Teszt feed"
|
||||
feed = Feed("Test 1")
|
||||
)
|
||||
Column(
|
||||
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",
|
||||
author = "Toldi Balázs",
|
||||
pubDate = LocalDateTime.now().plusDays(-1),
|
||||
feedName = "Teszt feed 2"
|
||||
),
|
||||
feed = Feed("Test 1")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ class Article(
|
|||
|
||||
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
|
||||
* @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()
|
||||
var title: 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
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import net.mm2d.touchicon.TouchIconExtractor
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.Node
|
||||
import org.w3c.dom.NodeList
|
||||
|
@ -23,14 +22,17 @@ open class Feed
|
|||
val link: URL
|
||||
) {
|
||||
|
||||
var faviconURL: String? = null
|
||||
|
||||
/**
|
||||
* A hírcsatorna neve
|
||||
*/
|
||||
var name: String? = null
|
||||
constructor(name : String) : this(URL("http://example.com")) {
|
||||
|
||||
constructor(name: String) : this(URL("http://example.com")) {
|
||||
this.name = name
|
||||
}
|
||||
|
||||
/**
|
||||
* A hírcsatornához tartozó cikkek listája
|
||||
*/
|
||||
|
@ -39,6 +41,7 @@ open class Feed
|
|||
|
||||
val isRefreshing: StateFlow<Boolean>
|
||||
get() = _isRefreshing.asStateFlow()
|
||||
|
||||
init {
|
||||
updateFeed()
|
||||
}
|
||||
|
@ -69,19 +72,30 @@ open class Feed
|
|||
_isRefreshing.emit(true)
|
||||
val factory: DocumentBuilderFactory = DocumentBuilderFactory.newInstance()
|
||||
val builder: DocumentBuilder = factory.newDocumentBuilder()
|
||||
var doc: Document
|
||||
withContext(Dispatchers.IO) {
|
||||
doc = builder.parse(link.toString())
|
||||
var doc: Document = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
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")
|
||||
|
||||
for (i in 0 until items.length) {
|
||||
val article: Node = items.item(i)
|
||||
val a = Article.createFromNode(article,name)
|
||||
val a = Article.createFromNode(article, this@Feed)
|
||||
addArticle(a)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue