Added ability to add new feeds
All checks were successful
continuous-integration/drone/push Build is passing

Signed-off-by: Toldi Balázs Ádám <balazs@toldi.eu>
This commit is contained in:
Toldi Balázs Ádám 2021-10-12 13:09:39 +02:00
parent ed51d77546
commit 6620652bf8
5 changed files with 188 additions and 45 deletions

View file

@ -62,7 +62,7 @@ dependencies {
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-alpha10"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1'
implementation "com.google.accompanist:accompanist-swiperefresh:0.19.0"
implementation "org.jsoup:jsoup:1.13.1"
implementation "net.mm2d.touchicon:touchicon:0.9.1"

View file

@ -40,6 +40,9 @@ import eu.toldi.balazs.anotherfeedreader.entities.FeedData
import eu.toldi.balazs.anotherfeedreader.entities.FeedGroup
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
@ -58,6 +61,7 @@ class MainActivity : ComponentActivity() {
addFeed(Feed(URL("https://www.theguardian.com/world/rss")))
}
lateinit var viewModel: FeedViewModel
var syncJob: Job? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
@ -68,40 +72,55 @@ class MainActivity : ComponentActivity() {
feedData = feeds
}
viewModel.updateFeed()
viewModel.changeFeedList()
var isAddFeedDialogOpen by remember {
mutableStateOf(false)
}
val scope = rememberCoroutineScope()
val drawerState = rememberDrawerState(DrawerValue.Closed)
val articleList = viewModel.articles.value
SideBar(drawerState = drawerState) {
Column {
MenuBar {
scope.launch {
drawerState.open()
}
}
val isRefreshing = viewModel.isRefereshing.value
SwipeRefresh(
state = rememberSwipeRefreshState(isRefreshing),
onRefresh = {
viewModel.updateFeed()
},
Scaffold(
floatingActionButton = {
FloatingActionButton(
onClick = { isAddFeedDialogOpen = true },
backgroundColor = MaterialTheme.colors.primary
) {
LazyColumn(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
) {
items(articleList.size) { index ->
val article = articleList[index]
showArticle(article = article)
Icon(Icons.Filled.Add, contentDescription = "Add feed")
}
}
) {
SideBar(drawerState = drawerState) {
Column {
MenuBar {
scope.launch {
drawerState.open()
}
}
}
val isRefreshing = viewModel.isRefereshing.value
SwipeRefresh(
state = rememberSwipeRefreshState(isRefreshing),
onRefresh = {
viewModel.updateFeed()
},
) {
LazyColumn(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
) {
items(articleList.size) { index ->
val article = articleList[index]
showArticle(article = article)
}
}
}
if (isAddFeedDialogOpen) {
showAddFeedDialog { isAddFeedDialogOpen = false }
}
}
}
}
}
@ -109,6 +128,112 @@ class MainActivity : ComponentActivity() {
}
}
@Composable
fun showAddFeedDialog(toClose: () -> Unit) {
var text by remember { mutableStateOf("") }
var selectedIndex by remember { mutableStateOf(0) }
AlertDialog(onDismissRequest = { toClose() },
title = {
Text(text = "Add feed")
},
text = {
Column() {
OutlinedTextField(
value = text,
onValueChange = {
text = it
},
label = { Text("Feed URL") },
modifier = Modifier.padding(vertical = 4.dp)
)
Row() {
Text("Group:")
var expanded by remember {
mutableStateOf(false)
}
Box(
modifier = Modifier
.wrapContentSize(Alignment.TopStart)
) {
Text(
text = if (selectedIndex == 0) "None" else feeds.feedList.filterIsInstance<FeedGroup>()[selectedIndex - 1].name.toString(),
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = { expanded = true })
)
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
) {
DropdownMenuItem(onClick = {
selectedIndex = 0
expanded = false
}) {
Text(text = "None")
}
feeds.feedList.filterIsInstance<FeedGroup>()
.forEachIndexed { index, s ->
DropdownMenuItem(onClick = {
selectedIndex = index + 1
expanded = false
}) {
feeds.feedList[index].name?.let {
Text(text = it)
}
}
}
}
}
}
},
buttons = {
Row(
modifier = Modifier.padding(all = 8.dp),
horizontalArrangement = Arrangement.Center
) {
Button(
onClick = toClose,
modifier = Modifier.padding(all = 8.dp),
) {
Text("Cancel")
}
Button(
onClick = {
try {
val feed = Feed(URL(text))
syncJob = GlobalScope.launch(IO) { feed.updateFeed() }
if (selectedIndex == 0)
feeds.addFeed(feed)
else {
var counter = 0
feeds.feedList.forEachIndexed { index, feedGroup ->
if (feedGroup is FeedGroup) {
counter++
if (counter == selectedIndex) {
feedGroup.addFeed(feed)
}
}
}
}
viewModel.feedData = feeds
viewModel.changeFeedList()
} catch (e: Exception) {
} finally {
toClose()
}
},
modifier = Modifier.padding(all = 8.dp),
) {
Text("Add Feed")
}
}
}
)
}
@Composable
fun MenuBar(hamburgerClick: () -> Unit) {
TopAppBar(
@ -359,9 +484,9 @@ class MainActivity : ComponentActivity() {
drawerState: DrawerState,
content: @Composable () -> Unit
) {
val feedList = viewModel.feedsState.value
ModalDrawer(drawerState = drawerState, drawerContent = {
LazyColumn {
LazyColumn(modifier = Modifier.fillMaxHeight()) {
item {
Card(
modifier = Modifier
@ -383,10 +508,11 @@ class MainActivity : ComponentActivity() {
)
}
}
items(feeds.feedList.size) { index ->
FeedSideBar(feed = feeds.feedList[index])
items(feedList.size) { index ->
FeedSideBar(feed = feedList[index])
}
}
}, content = content)
}

View file

@ -2,9 +2,6 @@ package eu.toldi.balazs.anotherfeedreader.entities
import android.util.Log
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import net.mm2d.touchicon.TouchIconExtractor
import org.w3c.dom.Document
import org.w3c.dom.Node
@ -83,7 +80,10 @@ open class Feed
faviconURL = withContext(Dispatchers.IO) {
val extractor = TouchIconExtractor()
extractor.fromPage("https://" + link.host)[0].url
val icons = extractor.fromPage("https://" + link.host)
if (icons.size > 0)
icons.maxByOrNull { it.inferArea() }?.url
else null
}
Log.e(null, faviconURL.toString())
}

View file

@ -25,9 +25,9 @@ open class FeedGroup(name: String) : Feed(name) {
fun addFeed(feed: Feed) {
if (!feedExists(feed)) {
feedList.add(feed)
for (i in 0 until feed.articleCount) {
if (!hasAricle(feed[i]))
articleList += feed[i]
feed.articleList.forEach { article ->
if (articleList.firstOrNull { it.url == article.url } == null)
articleList += article
}
}
}
@ -50,9 +50,10 @@ open class FeedGroup(name: String) : Feed(name) {
* @throws IOException
*/
override suspend fun updateFeed() {
if(feedList == null) feedList = ArrayList()
if (feedList == null) feedList = ArrayList()
if (articleList == null) articleList = ArrayList()
for (f in feedList) {
feedList.forEach { f ->
f.updateFeed()
f.articleList.forEach {
if (!hasAricle(it))

View file

@ -5,25 +5,35 @@ import androidx.compose.runtime.mutableStateOf
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 kotlinx.coroutines.Job
import kotlinx.coroutines.launch
class FeedViewModel : ViewModel() {
var feedData = FeedData()
val articles : MutableState<List<Article>> = mutableStateOf(listOf())
val isRefereshing : MutableState<Boolean> = mutableStateOf(false)
val articles: MutableState<List<Article>> = mutableStateOf(listOf())
val feedsState = mutableStateOf(emptyList<Feed>())
val isRefereshing: MutableState<Boolean> = mutableStateOf(false)
init {
viewModelScope.launch {
articles.value = feedData.articleList
feedsState.value = feedData.feedList
}
}
fun updateFeed() {
viewModelScope.launch {
fun updateFeed(): Job {
return viewModelScope.launch {
isRefereshing.value = true
feedData.updateFeed()
var done = false
feedData.limit?.let {
it.updateFeed()
done = true
}
if (!done)
feedData.updateFeed()
articles.value = feedData.articleList
isRefereshing.value = false
}
@ -36,4 +46,10 @@ class FeedViewModel : ViewModel() {
isRefereshing.value = false
}
}
fun changeFeedList() {
viewModelScope.launch {
feedsState.value = feedData.feedList
}
}
}