diff --git a/app/build.gradle b/app/build.gradle index 5061e40..f1a6164 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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" diff --git a/app/src/main/java/eu/toldi/balazs/anotherfeedreader/MainActivity.kt b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/MainActivity.kt index 0950803..f0931b0 100644 --- a/app/src/main/java/eu/toldi/balazs/anotherfeedreader/MainActivity.kt +++ b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/MainActivity.kt @@ -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()[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() + .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) } diff --git a/app/src/main/java/eu/toldi/balazs/anotherfeedreader/entities/Feed.kt b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/entities/Feed.kt index 35ac1e6..c0ff001 100644 --- a/app/src/main/java/eu/toldi/balazs/anotherfeedreader/entities/Feed.kt +++ b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/entities/Feed.kt @@ -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()) } diff --git a/app/src/main/java/eu/toldi/balazs/anotherfeedreader/entities/FeedGroup.kt b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/entities/FeedGroup.kt index 637fe01..32ce0bb 100644 --- a/app/src/main/java/eu/toldi/balazs/anotherfeedreader/entities/FeedGroup.kt +++ b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/entities/FeedGroup.kt @@ -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)) diff --git a/app/src/main/java/eu/toldi/balazs/anotherfeedreader/viewmodel/FeedViewModel.kt b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/viewmodel/FeedViewModel.kt index bae01cb..6e54e8e 100644 --- a/app/src/main/java/eu/toldi/balazs/anotherfeedreader/viewmodel/FeedViewModel.kt +++ b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/viewmodel/FeedViewModel.kt @@ -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> = mutableStateOf(listOf()) - val isRefereshing : MutableState = mutableStateOf(false) + val articles: MutableState> = mutableStateOf(listOf()) + val feedsState = mutableStateOf(emptyList()) + val isRefereshing: MutableState = 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 + } + } }