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()[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() .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") ) ) } } } }*/ }