diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml new file mode 100644 index 0000000..f47ae74 --- /dev/null +++ b/.idea/deploymentTargetDropDown.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file 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 4eca205..e43e404 100644 --- a/app/src/main/java/eu/toldi/balazs/anotherfeedreader/MainActivity.kt +++ b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/MainActivity.kt @@ -6,13 +6,11 @@ import android.util.Log import android.widget.ScrollView import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.compose.foundation.Image import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.scrollable -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.rememberScrollState import androidx.compose.material.* @@ -22,14 +20,21 @@ import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Settings import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.fragment.app.Fragment +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 java.net.URL import java.time.LocalDateTime @@ -38,7 +43,10 @@ import java.time.format.FormatStyle class MainActivity : ComponentActivity() { - val feed = Feed(URL("https://hvg.hu/rss")) + val feed = FeedGroup("magyar").apply { + addFeed(Feed(URL("https://hvg.hu/rss"))) + addFeed(Feed(URL("https://telex.hu/rss"))) + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -47,34 +55,50 @@ class MainActivity : ComponentActivity() { // A surface container using the 'background' color from the theme Surface(color = MaterialTheme.colors.background) { - var articleCount = remember { mutableStateOf(feed.articleCount) } - Log.w(null,articleCount.toString()) + feed.updateFeed() + var articleList = remember { mutableStateOf(feed.articleList) } + + + Log.w(null, articleList.value.size.toString()) Column { MenuBar() - val scrollState = rememberScrollState() - LazyColumn( - modifier = Modifier.scrollable( - state = scrollState, - orientation = Orientation.Vertical - ) + val isRefreshing by feed.isRefreshing.collectAsState() + SwipeRefresh( + state = rememberSwipeRefreshState(isRefreshing), + onRefresh = { + feed.updateFeed() + articleList.value = feed.articleList + }, ) { - items(articleCount.value) { index -> - Column( - Modifier - .clickable { - Log.i(null, feed[index].title.toString()) - WebView.webURL = feed[index].url.toString() - WebView.barTitle = feed.name.toString() - startActivity(Intent(baseContext, WebView::class.java)) - } - .padding( - horizontal = 3.dp - ) - ) { - showArticle(article = feed[index], feed.name!!) - } + LazyColumn( + modifier = Modifier.fillMaxHeight() + ) { + items(articleList.value.size) { index -> + val article = articleList.value[index] + Column( + + Modifier + .clickable { + Log.i(null, article.title.toString()) + WebView.webURL = article.url.toString() + WebView.barTitle = feed.name.toString() + startActivity( + Intent( + baseContext, + WebView::class.java + ) + ) + } + .padding( + horizontal = 3.dp + ) + ) { + showArticle(article = article, feed.name!!) + } + + } } } @@ -119,44 +143,52 @@ class MainActivity : ComponentActivity() { @Composable fun showArticle(article: Article, feedName: String) { - Column(modifier = Modifier.padding(all = 5.dp)) { - Row { - if (article.author != null) { + Row { + Column(Modifier.weight(1f)) { + Row { + if (article.author != null) { + Text( + article.author, + fontSize = 11.sp + ) + } else Text("") Text( - article.author, - fontSize = 11.sp - ) - } else Text("") - Text( - feedName, - fontSize = 11.sp, - textAlign = TextAlign.Center, - modifier = Modifier.padding(horizontal = 10.dp) - ) - if (article.pubDate != null) { - Text( - article.pubDate.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)), + feedName, fontSize = 11.sp, - textAlign = TextAlign.Right, - modifier = Modifier.width(400.dp) + textAlign = TextAlign.Center, + modifier = Modifier.padding(horizontal = 10.dp) + ) + if (article.pubDate != null) { + Text( + article.pubDate.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)), + fontSize = 11.sp, + textAlign = TextAlign.Right, + //modifier = Modifier.width(350.dp) + ) + } else Text("") + } + 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 + ) + } } - if (article.title != null) { - Text( - text = article.title.trim(), - maxLines = 2, - overflow = TextOverflow.Ellipsis + if (article.imageURL != null) + Image( + painter = rememberImagePainter(article.imageURL), + contentDescription = null, ) - } else Text("") - if (article.description != null) { - Text( - text = article.description, - fontSize = 12.sp, - maxLines = 2, - overflow = TextOverflow.Ellipsis - ) - } } } @@ -170,10 +202,11 @@ class MainActivity : ComponentActivity() { 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 = LocalDateTime.now() + pubDate = LocalDateTime.now(), + imageURL = "https://cloud.toldi.eu/index.php/apps/files_sharing/publicpreview/Aicw77SAJYBiM8B?x=1920&y=571&a=true&file=P2.JPG&scalingup=0" ) Column( - modifier = Modifier.padding( horizontal = 1.dp) + modifier = Modifier.padding(horizontal = 1.dp) ) { showArticle(article, "Test feed") showArticle( diff --git a/app/src/main/java/eu/toldi/balazs/anotherfeedreader/WebView.kt b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/WebView.kt index 3cc983f..09ae7d4 100644 --- a/app/src/main/java/eu/toldi/balazs/anotherfeedreader/WebView.kt +++ b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/WebView.kt @@ -5,10 +5,9 @@ import android.webkit.WebView import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Column -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Surface -import androidx.compose.material.Text -import androidx.compose.material.TopAppBar +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.OpenInBrowser import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.viewinterop.AndroidView @@ -17,6 +16,14 @@ import eu.toldi.balazs.anotherfeedreader.WebView.Companion.webURL import eu.toldi.balazs.anotherfeedreader.ui.theme.AnotherFeedReaderTheme import eu.toldi.balazs.anotherfeedreader.views.CustomWebViewClient +import android.net.Uri + +import android.content.Intent +import androidx.compose.material.icons.filled.ArrowLeft +import androidx.core.content.ContextCompat +import androidx.core.content.ContextCompat.startActivity + + class WebView : ComponentActivity() { companion object { var webURL = "http://example.rog" @@ -25,44 +32,70 @@ class WebView : ComponentActivity() { 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) { Column() { TopBar(topTitle = barTitle) - ShowWebView("Android") + ShowWebView("Android") } } } } } -} -@Composable -fun TopBar(topTitle: String) { - TopAppBar ( - title = { Text(text = topTitle) } - ) -} -@Composable -fun ShowWebView(name: String) { - AndroidView(factory = { ctx -> - WebView(ctx).apply { - loadUrl(webURL) - webViewClient = CustomWebViewClient() - } - }) -} + @Composable + fun TopBar(topTitle: String) { + + TopAppBar( + title = { Text(text = topTitle) }, + navigationIcon = { + IconButton(onClick = { + finish() + }) { + Icon( + Icons.Filled.ArrowLeft, + contentDescription = "Back to feed" + ) + } + }, + actions = { + IconButton(onClick = { + + val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(webURL)) + startActivity(browserIntent) + }) { + Icon( + Icons.Filled.OpenInBrowser, + contentDescription = "Open in browser" + ) + } + } + ) + } + + @Composable + fun ShowWebView(name: String) { + AndroidView(factory = { ctx -> + WebView(ctx).apply { + loadUrl(webURL) + webViewClient = CustomWebViewClient() + } + }) + } + + @Preview(showBackground = true) + @Composable + fun DefaultPreview() { + AnotherFeedReaderTheme { + Column { + TopBar("WebView") + ShowWebView("Android") + } -@Preview(showBackground = true) -@Composable -fun DefaultPreview() { - AnotherFeedReaderTheme { - Column { - TopBar("WebView") - ShowWebView("Android") } - } } \ No newline at end of file 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 e20370d..8bc23f9 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 @@ -3,6 +3,9 @@ package eu.toldi.balazs.anotherfeedreader.entities import android.util.Log import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.w3c.dom.Document @@ -13,7 +16,7 @@ import javax.xml.parsers.DocumentBuilder import javax.xml.parsers.DocumentBuilderFactory -class Feed +open class Feed ( /** * A hírcsatorna elérési útvonala @@ -26,12 +29,17 @@ class Feed * A hírcsatorna neve */ var name: String? = null - + constructor(name : String) : this(URL("http://example.com")) { + this.name = name + } /** * A hírcsatornához tartozó cikkek listája */ - val articleList: MutableList
= ArrayList() + var articleList: List
= ArrayList() + private val _isRefreshing = MutableStateFlow(false) + val isRefreshing: StateFlow + get() = _isRefreshing.asStateFlow() init { updateFeed() } @@ -40,7 +48,7 @@ class Feed * A hírcsatornán található cikkek számának lekérése * @return a hírcsatorna található cikkek száma */ - val articleCount: Int + open val articleCount: Int get() = articleList.size /** @@ -57,8 +65,9 @@ class Feed * Lekéri a hírcsatorna elérési útvonalán található összes új cikket és hozzáadja a cikkek listájához. * */ - fun updateFeed() { + open fun updateFeed() { GlobalScope.launch { + _isRefreshing.emit(true) val factory: DocumentBuilderFactory = DocumentBuilderFactory.newInstance() val builder: DocumentBuilder = factory.newDocumentBuilder() var doc: Document @@ -74,6 +83,7 @@ class Feed addArticle(a) } Log.e(null, articleCount.toString()) + _isRefreshing.emit(false) } } @@ -96,7 +106,7 @@ class Feed * Megjegyzés: cikket csak akkor veszi fel,ha az még nem található a listájában */ fun addArticle(a: Article) { - if (!hasAricle(a)) articleList.add(a) + if (!hasAricle(a)) articleList += a } 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 new file mode 100644 index 0000000..4ac8d00 --- /dev/null +++ b/app/src/main/java/eu/toldi/balazs/anotherfeedreader/entities/FeedGroup.kt @@ -0,0 +1,107 @@ +package eu.toldi.balazs.anotherfeedreader.entities + +import java.util.ArrayList + +import java.io.IOException + + +class FeedGroup(name: String) : Feed(name) { + /** + * A hírcsatornák listája + */ + var feedList: MutableList = ArrayList() + + /** + * Hírcsatorna hozzáadása a csoporthoz + * @param feed hozzáadandó hírcsatorna + * Megjegyzés: Meglévő csatorna nem adható hozzá mégegyszer + */ + fun addFeed(feed: Feed) { + if (!feedExists(feed)) { + feedList.add(feed) + for (i in 0 until feed.articleCount) { + if (!hasAricle(feed[i])) + articleList += feed[i] + } + } + } + + /** + * Ellenőrzi,hogy a megadott hírcsatorna metalálható-e benne + * @param f az ellenőrizendő csatorna + * @return logikai érték az alapján,hogy a csatorna megtalálható-e a csoportban + * Megjegyzés: Csak abban az esetben tér vissza igazzal,ha a két objektum megegyezik egy a csoportban találhatóval + */ + fun feedExists(f: Feed): Boolean { + for (i in feedList.indices) { + if (f === feedList[i]) return true + } + return false + } + + /** + * @see Feed.updateFeed + * @throws IOException + */ + override fun updateFeed() { + if(feedList == null) feedList = ArrayList() + if (articleList == null) articleList = ArrayList() + for (f in feedList) { + f.updateFeed() + for (i in 0 until f.articleCount) { + if (!hasAricle(f[i])) + articleList += f[i] + } + } + articleList = articleList.sortedByDescending { it.pubDate } + } + + /** + * @see Feed.getArticleCount + * @return + */ + override val articleCount: Int + get() { + var result = 0 + for (f in feedList) { + result += f.articleCount + } + return result + } + + /** + * Hírcsatorna törlése a csoportból + * @param feed a törlendő hírcsatorna + * Megjegyzés: Ez törli az össze a csatornához tartozó cikket. + */ + fun removeFeed(feed: Feed) { + var found = false + for (i in feedList.indices) { + //Csak ha PONTOSAN megegyezik + if (feedList[i] === feed) { + feedList.removeAt(i) + found = true + break + } + } + if (found) { + val similar: MutableList
= ArrayList(articleList) + val different: MutableList
= ArrayList() + different.addAll(articleList) + different.addAll(feed.articleList) + similar.retainAll(feed.articleList) + different.removeAll(similar) + articleList = different + } + } + + /** + * Egy hírcsatorna áthelyezése a listában. Átrendezésre használatos. + * @param f az áthelyezendő hírcsatorna + * @param index a hírcsatorna új helye + */ + fun setLocation(f: Feed, index: Int) { + feedList.remove(f) + feedList.add(index, f) + } +}