Added ability to add new feeds
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: Toldi Balázs Ádám <balazs@toldi.eu>
This commit is contained in:
parent
ed51d77546
commit
6620652bf8
5 changed files with 188 additions and 45 deletions
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue