AnotherFeedReader/app/src/main/java/eu/toldi/balazs/anotherfeedreader/MainActivity.kt
Balazs Toldi a4b3e7e80a
All checks were successful
continuous-integration/drone/push Build is passing
Big progress on persistent data storage
Signed-off-by: Balazs Toldi <balazs@toldi.eu>
2021-10-23 23:54:47 +02:00

539 lines
No EOL
22 KiB
Kotlin

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.activity.viewModels
import androidx.compose.foundation.Image
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.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
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
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.rememberImagePainter
import com.google.accompanist.swiperefresh.SwipeRefresh
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
import eu.toldi.balazs.anotherfeedreader.entities.Article
import eu.toldi.balazs.anotherfeedreader.entities.Feed
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.Job
import kotlinx.coroutines.launch
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
class MainActivity : ComponentActivity() {
private val viewModel: FeedViewModel by viewModels()
var syncJob: Job? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AnotherFeedReaderTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
viewModel.updateFeeds()
var isAddFeedDialogOpen by remember {
mutableStateOf(false)
}
val scope = rememberCoroutineScope()
val drawerState = rememberDrawerState(DrawerValue.Closed)
val articleList = viewModel.articles.observeAsState(emptyList())
Scaffold(
floatingActionButton = {
FloatingActionButton(
onClick = { isAddFeedDialogOpen = true },
backgroundColor = MaterialTheme.colors.primary
) {
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.updateFeeds()
},
) {
LazyColumn(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
) {
items(articleList.value.size) { index ->
val articleAndFeed = articleList.value[index]
articleAndFeed.articles.sortedByDescending { it.pubDate }
.forEach {
showArticle(articleAndFeed.feed, article = it)
}
}
}
}
if (isAddFeedDialogOpen) {
showAddFeedDialog { isAddFeedDialogOpen = false }
}
}
}
}
}
}
}
}
@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 viewModel.feedsState.value!!.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")
}
viewModel.feedsState.value!!.filterIsInstance<FeedGroup>()
.forEachIndexed { index, s ->
DropdownMenuItem(onClick = {
selectedIndex = index + 1
expanded = false
}) {
viewModel.feedsState.value!![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(null,text)
if (selectedIndex == 0)
viewModel.addFeed(feed)
else {
var counter = 0
viewModel.feedsState.value!!.forEachIndexed { index, feedGroup ->
if (feedGroup is FeedGroup) {
counter++
if (counter == selectedIndex) {
feedGroup.addFeed(feed)
}
}
}
}
viewModel.changeFeedList()
} catch (e: Exception) {
} finally {
toClose()
}
},
modifier = Modifier.padding(all = 8.dp),
) {
Text("Add Feed")
}
}
}
)
}
@Composable
fun MenuBar(hamburgerClick: () -> Unit) {
TopAppBar(
title = {
Text(stringResource(id = R.string.app_name))
},
navigationIcon = {
IconButton(onClick = hamburgerClick) {
Icon(
Icons.Filled.Menu,
contentDescription = "Menu Hamburger"
)
}
},
actions = {
Row {
IconButton(onClick = {}) {
Icon(
Icons.Filled.Search,
contentDescription = "Search Article"
)
}
IconButton(onClick = {}) {
Icon(
Icons.Filled.Settings,
contentDescription = "Settings"
)
}
}
}
)
}
@Composable
fun showArticle(feed: Feed, article: Article) {
val haptic = LocalHapticFeedback.current
Card(
Modifier
.pointerInput(Unit) {
detectTapGestures(
onTap = {
WebView.webURL = article.url.toString()
WebView.barTitle =
feed.name ?: ""
startActivity(
Intent(
baseContext,
WebView::class.java
)
)
},
onLongPress = {
val share =
Intent.createChooser(Intent().apply {
action = Intent.ACTION_SEND
putExtra(
Intent.EXTRA_TEXT,
article.url
)
putExtra(
Intent.EXTRA_TITLE,
article.title
)
type = "text/plain"
}, null)
startActivity(share)
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
}
)
}
.padding(
start = 6.dp,
top = 2.dp,
bottom = 6.dp,
end = 6.dp
),
elevation = 10.dp,
backgroundColor = MaterialTheme.colors.surface,
shape = RoundedCornerShape(10.dp)
) {
Column {
Row(
modifier = Modifier.padding(horizontal = 3.dp, vertical = 6.dp),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
val painter = if (feed.faviconURL != null) {
rememberImagePainter(feed.faviconURL)
} else {
painterResource(id = R.drawable.ic_launcher_background)
}
Image(
painter = painter,
contentDescription = null,
modifier = Modifier
.size(30.dp)
.clip(CircleShape)
)
article.author?.let {
Text(
text = (fun(): String {
if (it.length > 25)
return it.dropLast(it.length - 25)
else return it
}).invoke(),
fontSize = 11.sp,
modifier = Modifier.padding(horizontal = 2.dp)
)
}
feed.name?.let {
Text(
text = (fun(): String {
return if (it.length > 25)
return it.dropLast(it.length - 25)
else return it
}).invoke(),
fontSize = 11.sp,
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 10.dp),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
article.pubDate?.let {
Text(
it.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)),
fontSize = 11.sp,
textAlign = TextAlign.Right,
//modifier = Modifier.width(350.dp)
)
}
}
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(
text = article.title!!.trim(),
fontSize = 20.sp,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
} else Text("")
if (article.description != null) {
Text(
text = article.description!!,
fontSize = 17.sp,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
}
}
}
}
@Composable
fun FeedSideBar(feed: Feed) {
Column() {
var collapsed by remember {
mutableStateOf(true)
}
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 5.dp, horizontal = 4.dp)
.clickable {
/*feeds.limit = if (feeds.limit == feed) {
null
} else feed*/
viewModel.changeFeed()
},
elevation = 10.dp,
backgroundColor = MaterialTheme.colors.surface,
) {
Row {
if (feed is FeedGroup) {
IconButton(onClick = {
collapsed = collapsed.not()
}) {
if (collapsed)
Icon(
Icons.Filled.KeyboardArrowDown,
contentDescription = "Show feeds contained"
)
else
Icon(
Icons.Filled.KeyboardArrowUp,
contentDescription = "Hide feeds contained"
)
}
}
feed.name?.let {
Text(
text = it,
fontSize = 26.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(all = 4.dp)
)
}
}
}
if (!collapsed) {
val feedGroup = feed as FeedGroup
feedGroup.feedList.forEach {
Log.w(null, it.name.toString())
Card(
modifier = Modifier
.fillMaxWidth()
.padding(start = 25.dp, top = 5.dp, end = 4.dp, bottom = 5.dp)
.clickable {
/*feeds.limit = if (feeds.limit == it) {
null
} else it*/
viewModel.changeFeed()
},
elevation = 10.dp,
backgroundColor = MaterialTheme.colors.surface,
) {
it.name?.let {
Text(
text = it,
fontSize = 26.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(all = 4.dp)
)
}
}
}
}
}
}
@Composable
fun SideBar(
drawerState: DrawerState,
content: @Composable () -> Unit
) {
val feedList = viewModel.feedsState?.value ?: emptyList()
ModalDrawer(drawerState = drawerState, drawerContent = {
LazyColumn(modifier = Modifier.fillMaxHeight()) {
item {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 5.dp, horizontal = 4.dp)
.clickable {
//feeds.limit = null
viewModel.changeFeed()
},
elevation = 10.dp,
backgroundColor = MaterialTheme.colors.surface,
) {
Text(
text = stringResource(id = R.string.all_feeds),
fontSize = 26.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(all = 4.dp)
)
}
}
items(feedList.size) { index ->
FeedSideBar(feed = feedList[index])
}
}
}, content = content)
}
/*
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
AnotherFeedReaderTheme {
Column() {
MenuBar({})
val article = Article(null,
title = "Teszt Article",
description = "Ez az cikk azért létezik, hogy kipróbájam a cikkek megjelenítését.",
author = "Toldi Balázs",
pubDate_stamp = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC),
feed = Feed("Test 1")
)
Column(
modifier = Modifier.padding(horizontal = 1.dp)
) {
showArticle(article)
showArticle(
Article(null,
title = "Teszt Article #2",
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_stamp = LocalDateTime.now().plusDays(-1).toEpochSecond(
ZoneOffset.UTC),
feed = Feed("Test 1")
)
)
}
}
}
}*/
}