From 0ef3b6c0df34e1bf9c56404599e1c3dd5089de58 Mon Sep 17 00:00:00 2001 From: mkaocodes Date: Mon, 1 Jul 2024 12:23:27 +0300 Subject: [PATCH 01/11] - Minor Interface Changes --- app/src/main/java/dev/mkao/weaver/presentation/About/About.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/dev/mkao/weaver/presentation/About/About.kt b/app/src/main/java/dev/mkao/weaver/presentation/About/About.kt index 14f1a9b..9c44856 100644 --- a/app/src/main/java/dev/mkao/weaver/presentation/About/About.kt +++ b/app/src/main/java/dev/mkao/weaver/presentation/About/About.kt @@ -10,10 +10,12 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource @@ -34,6 +36,7 @@ fun About() { Box( modifier = Modifier .fillMaxWidth() + .clip(RoundedCornerShape(bottomStart = 10.dp, bottomEnd = 10.dp)) .height(260.dp) ) { Image( From de97c7d460d7631a5e5254138dad0701e6eca0f6 Mon Sep 17 00:00:00 2001 From: mkaocodes Date: Mon, 1 Jul 2024 12:24:27 +0300 Subject: [PATCH 02/11] - Minor Interface Changes/BookMarks --- .../presentation/Bookmarks/BookmarksScreen.kt | 198 +++++++++--------- .../presentation/common/SettingsScreen.kt | 14 +- 2 files changed, 102 insertions(+), 110 deletions(-) diff --git a/app/src/main/java/dev/mkao/weaver/presentation/Bookmarks/BookmarksScreen.kt b/app/src/main/java/dev/mkao/weaver/presentation/Bookmarks/BookmarksScreen.kt index 0734eaa..ee168a6 100644 --- a/app/src/main/java/dev/mkao/weaver/presentation/Bookmarks/BookmarksScreen.kt +++ b/app/src/main/java/dev/mkao/weaver/presentation/Bookmarks/BookmarksScreen.kt @@ -1,18 +1,18 @@ package dev.mkao.weaver.presentation.Bookmarks -import android.annotation.SuppressLint -import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Delete -import androidx.compose.material3.Card +import androidx.compose.material.icons.filled.Close import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -20,122 +20,122 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController +import coil.compose.AsyncImage +import coil.request.ImageRequest +import dev.mkao.weaver.R import dev.mkao.weaver.domain.model.Article -import dev.mkao.weaver.domain.model.Source +import dev.mkao.weaver.presentation.common.BottomNavigationBar +import dev.mkao.weaver.util.calculateElapsedTime +import dev.mkao.weaver.viewModels.SharedViewModel -@OptIn(ExperimentalMaterial3Api::class) -@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @Composable -fun BookmarksScreen( - bookmarkedArticles: List
, - onRemoveBookmark: (Article) -> Unit, - onArticleClick: (Article) -> Unit -) { - Scaffold( - topBar = { - CenterAlignedTopAppBar( - title = { Text("Bookmarks") }, - ) - }, - containerColor = Color(0xFFF0F0F0) // Light grey background - ) { - Column( - modifier = Modifier - .fillMaxSize() - .padding(end = 16.dp, start = 16.dp, top = 80.dp) - ) { - if (bookmarkedArticles.isEmpty()) { - Text( - text = "No bookmarks available", - fontSize = 18.sp, - modifier = Modifier.align(Alignment.CenterHorizontally) - ) - } else { - bookmarkedArticles.forEach { article -> - BookmarkedArticleCard( - article = article, - onRemoveBookmark = onRemoveBookmark, - onArticleClick = onArticleClick - ) - } - } +fun ArticleList(articles: List
, sharedViewModel: SharedViewModel, navController: NavController) { + LazyColumn { + items(articles) { article -> + ArticleItem(article, sharedViewModel, navController) } } } @Composable -fun BookmarkedArticleCard( - article: Article, - onRemoveBookmark: (Article) -> Unit, - onArticleClick: (Article) -> Unit -) { - Card( - shape = RoundedCornerShape(8.dp), +fun ArticleItem(article: Article, sharedViewModel: SharedViewModel, navController: NavController) { + val date = article.publishedAt + val elapsedtime = calculateElapsedTime(date) + Row( modifier = Modifier .fillMaxWidth() - .padding(vertical = 8.dp) - .clickable { onArticleClick(article) } + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically ) { - Row( + Box( modifier = Modifier - .padding(16.dp) - .fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically + .width(100.dp) + .height(80.dp) + .clip(RoundedCornerShape(10.dp)) + .padding(end = 10.dp) ) { - Column(modifier = Modifier.weight(1f)) { - Text(text = article.title, fontSize = 18.sp, color = Color.Black) - Spacer(modifier = Modifier.height(4.dp)) - Text(text = article.description ?: "", fontSize = 14.sp, color = Color.Gray) - } - IconButton(onClick = { onRemoveBookmark(article) }) { - Icon( - imageVector = Icons.Default.Delete, - contentDescription = "Remove bookmark", - tint = Color.Red + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(article.urlToImage) + .crossfade(true) + .build(), + contentDescription = article.title, + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize() + ) + } + Column(modifier = Modifier.weight(1f)) { + Text( + text = article.title, + fontWeight = FontWeight.Bold, + fontSize = 12.sp, + overflow = TextOverflow.Ellipsis, + maxLines = 1 + ) + article.description?.let { + Text( + text = it, + fontSize = 11.sp, + maxLines = 2, + overflow = TextOverflow.Ellipsis ) } + Text( + text = elapsedtime, + fontSize = 12.sp + ) + } + IconButton( + onClick = { + sharedViewModel.toggleBookmark(article) + } + ) { + Icon( + Icons.Default.Close, + contentDescription = stringResource(R.string.Delete) + ) } } } -@Preview +@OptIn(ExperimentalMaterial3Api::class) @Composable -fun BookmarksScreenPreview() { - val sampleArticles = listOf( - Article( - source = Source(id = "1", name = "Source 1"), - author = "Author 1", - title = "Sample Article 1", - content = "Content of the sample article 1", - description = "Description of the sample article 1", - url = "https://example.com/article1", - urlToImage = "https://example.com/image1.jpg", - publishedAt = "2023-05-27T12:00:00Z" - ), - Article( - source = Source(id = "2", name = "Source 2"), - author = "Author 2", - title = "Sample Article 2", - content = "Content of the sample article 2", - description = "Description of the sample article 2", - url = "https://example.com/article2", - urlToImage = "https://example.com/image2.jpg", - publishedAt = "2023-05-27T12:00:00Z" - ) - ) - - BookmarksScreen( - bookmarkedArticles = sampleArticles, - onRemoveBookmark = { /* Do something on remove */ }, - onArticleClick = { /* Do something on click */ } - ) -} +fun BookmarkScreen( + sharedViewModel: SharedViewModel = viewModel(), + navController: NavController +) { + val bookmarkedArticles = sharedViewModel.bookmarkedArticles.collectAsState().value -@Composable -fun Source(id: String, name: String) = Source(id, name, category ="", url = "") + Scaffold( + topBar = { + CenterAlignedTopAppBar( + title = { + Text( + stringResource(R.string.Favourites), + fontWeight = FontWeight.Bold + ) + } + ) + }, + bottomBar = { + BottomNavigationBar(navController = navController) + }, + ) { paddingValues -> + Column(modifier = Modifier.padding(paddingValues)) { + ArticleList(bookmarkedArticles, sharedViewModel, navController) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/dev/mkao/weaver/presentation/common/SettingsScreen.kt b/app/src/main/java/dev/mkao/weaver/presentation/common/SettingsScreen.kt index a4afecf..dee9705 100644 --- a/app/src/main/java/dev/mkao/weaver/presentation/common/SettingsScreen.kt +++ b/app/src/main/java/dev/mkao/weaver/presentation/common/SettingsScreen.kt @@ -14,13 +14,11 @@ import androidx.compose.foundation.text.BasicTextField import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowForward import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.LocalTextStyle -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -51,7 +49,7 @@ fun SettingsScreen(navController: NavHostController) { Scaffold( topBar = { CenterAlignedTopAppBar( - title = { Text("Settings", fontWeight = FontWeight.Bold) } + title = { Text(stringResource(R.string.settings), fontWeight = FontWeight.Bold) } ) }, bottomBar = { @@ -104,12 +102,7 @@ fun SettingsScreen(navController: NavHostController) { ) SectionTitle(title = stringResource(R.string.other)) - SettingsCard( - title = stringResource(R.string.privacy_policy), - trailingIcon = Icons.AutoMirrored.Filled.ArrowForward, - onTrailingIconClick = { launchPrivacyPolicyIntent(context) } - ) - HorizontalDivider(thickness = 1.dp, color = Color(0xFFFFE4B5)) + SettingsCard( title = stringResource(R.string.about_app), trailingIcon = Icons.AutoMirrored.Filled.ArrowForward, @@ -140,7 +133,6 @@ fun SettingsCard( ) { Card( shape = RoundedCornerShape(8.dp), - colors = CardDefaults.cardColors(MaterialTheme.colorScheme.primary), modifier = Modifier .fillMaxWidth() .padding(vertical = 8.dp) @@ -184,6 +176,6 @@ fun launchAppIntent(context: Context, packageName: String) { } fun launchPrivacyPolicyIntent(context: Context) { - val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://yourprivacyurl.com")) + val intent = Intent(Intent.ACTION_VIEW, Uri.parse("")) context.startActivity(intent) } \ No newline at end of file From 1f885409f7d3179a11582b3df1f92716f0926ae7 Mon Sep 17 00:00:00 2001 From: mkaocodes Date: Tue, 2 Jul 2024 12:25:46 +0300 Subject: [PATCH 03/11] - Resources updated/Refactoring --- app/src/main/java/dev/mkao/weaver/util/Dimens.kt | 3 ++- app/src/main/res/values-night/strings.xml | 6 ++++++ app/src/main/res/values/strings.xml | 6 ++++++ build.gradle | 2 +- 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/dev/mkao/weaver/util/Dimens.kt b/app/src/main/java/dev/mkao/weaver/util/Dimens.kt index e05e519..77b30cf 100644 --- a/app/src/main/java/dev/mkao/weaver/util/Dimens.kt +++ b/app/src/main/java/dev/mkao/weaver/util/Dimens.kt @@ -5,6 +5,7 @@ import androidx.compose.ui.unit.dp object Dimens { val ArticleCard = 124.dp val ExtraSmallPadding = 3.dp - val ExtraSmallPadding1 = 6.dp + val ExtraSmallPadding2 = 6.dp + val ExtraExtraPadding = 15.dp val MediumPadding1 = 24.dp } \ No newline at end of file diff --git a/app/src/main/res/values-night/strings.xml b/app/src/main/res/values-night/strings.xml index 95707d7..1c4e73c 100644 --- a/app/src/main/res/values-night/strings.xml +++ b/app/src/main/res/values-night/strings.xml @@ -29,4 +29,10 @@ Arrow forward Hey, check out this awesome app! Invite friends via + Your Saved Articles + Remove from bookmarks + Content Not Available + Other Sources + Source Image + Other Sources \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 88ebb8e..e0ffebd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -34,4 +34,10 @@ Arrow forward Hey, check out this awesome app! Invite friends via + Your Saved Articles + Remove from bookmarks + Content Not Available + Other Sources + Source Image + Other Sources \ No newline at end of file diff --git a/build.gradle b/build.gradle index e645686..594b908 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ buildscript { compose_ui_version = '1.2.1' } dependencies { - classpath 'com.android.tools.build:gradle:8.6.0-alpha05' + classpath 'com.android.tools.build:gradle:8.6.0-alpha08' } repositories { From a178aafed947f73de79b710d2e6381c3d1422aaf Mon Sep 17 00:00:00 2001 From: mkaocodes Date: Wed, 3 Jul 2024 12:28:37 +0300 Subject: [PATCH 04/11] - Interface Changes / Minor Code and UI adjustments --- .../weaver/presentation/common/CardArticle.kt | 22 +- .../presentation/common/CardTopSection.kt | 35 +-- .../presentation/common/CategoryChip.kt | 2 +- .../presentation/details/NewsArticleUi.kt | 68 +++--- .../weaver/presentation/home/ArticleScreen.kt | 4 +- .../weaver/presentation/home/TopSection.kt | 206 ++++++++++++------ 6 files changed, 210 insertions(+), 127 deletions(-) diff --git a/app/src/main/java/dev/mkao/weaver/presentation/common/CardArticle.kt b/app/src/main/java/dev/mkao/weaver/presentation/common/CardArticle.kt index b747187..1dcbb78 100644 --- a/app/src/main/java/dev/mkao/weaver/presentation/common/CardArticle.kt +++ b/app/src/main/java/dev/mkao/weaver/presentation/common/CardArticle.kt @@ -25,6 +25,7 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview @@ -50,17 +51,17 @@ fun CardArtiCle( modifier = Modifier .padding(1.dp) .fillMaxWidth() - .clickable { onReadFullStoryClicked() } + .clickable { onReadFullStoryClicked() } ) { Row( modifier = Modifier - .padding(5.dp) + .padding(2.dp) .fillMaxWidth(), verticalAlignment = Alignment.CenterVertically ) { AsyncImage( modifier = Modifier - .height(120.dp) + .height(140.dp) .width(120.dp), model = ImageRequest.Builder(LocalContext.current) .data(article.urlToImage) @@ -95,13 +96,14 @@ fun CardArtiCle( style = MaterialTheme.typography.bodyMedium.copy(), color = Color.Black, maxLines = 1, - fontSize = 14.sp, + fontWeight = FontWeight.Bold, + fontSize = 12.sp, overflow = TextOverflow.Ellipsis ) Spacer(modifier = Modifier.height(4.dp)) Text( - text = article.content?:"none", - fontSize = 14.sp, + text = article.content?: stringResource(R.string.Error404), + fontSize = 12.sp, maxLines = 2, color = Color.Gray ) @@ -113,28 +115,30 @@ fun CardArtiCle( verticalAlignment = Alignment.CenterVertically) { Text( - text = article.author?.split(",")?.firstOrNull() ?: "Source", + text = article.author?.split(",")?.firstOrNull() ?: stringResource(R.string.Contemporary_Sources), style = MaterialTheme.typography.bodySmall, fontWeight = FontWeight.Bold, overflow = TextOverflow.Ellipsis ) + Spacer(modifier = Modifier.width(Dimens.ExtraExtraPadding)) Text( text = ".", color = Color.Black, fontWeight = FontWeight.Bold, ) - Spacer(modifier = Modifier.width(15.dp)) + Spacer(modifier = Modifier.width(Dimens.ExtraExtraPadding)) Icon( painter = painterResource(id = R.drawable.ic_time), contentDescription = null, modifier = Modifier.size(15.dp), tint = colorResource(id = R.color.body) ) - Spacer(modifier = Modifier.width(Dimens.ExtraSmallPadding1)) + Spacer(modifier = Modifier.width(Dimens.ExtraSmallPadding)) Text( modifier = Modifier.align(alignment = Alignment.CenterVertically), text = date, + fontSize = 12.sp, style = MaterialTheme.typography.labelSmall.copy(fontWeight = FontWeight.Bold), color = Color.Black ) diff --git a/app/src/main/java/dev/mkao/weaver/presentation/common/CardTopSection.kt b/app/src/main/java/dev/mkao/weaver/presentation/common/CardTopSection.kt index 6061adf..2a0eeec 100644 --- a/app/src/main/java/dev/mkao/weaver/presentation/common/CardTopSection.kt +++ b/app/src/main/java/dev/mkao/weaver/presentation/common/CardTopSection.kt @@ -23,6 +23,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import coil.compose.AsyncImage @@ -40,13 +41,12 @@ fun CardArtiCleTop( Card( modifier = Modifier .padding(1.dp) - .height(250.dp) - .width(400.dp) + .height(220.dp) + .width(335.dp) .clickable { onReadFullStoryClicked() } ) { Box( - modifier = Modifier - .fillMaxSize() + modifier = Modifier.fillMaxSize() ) { AsyncImage( modifier = Modifier.fillMaxSize(), @@ -60,7 +60,7 @@ fun CardArtiCleTop( Box( modifier = Modifier .fillMaxWidth() - .height(200.dp) + .height(160.dp) .align(Alignment.BottomCenter) .background( Brush.verticalGradient( @@ -70,39 +70,40 @@ fun CardArtiCleTop( ) ) ) { - Row(modifier = Modifier - .wrapContentSize() - .padding(top = 40.dp,start = 15.dp, end = 15.dp)) { - CategoryChip1(category = "Sports") - } + Column( modifier = Modifier .fillMaxWidth() - .padding(top = 70.dp, start = 15.dp, end = 15.dp), + .padding(top = 5.dp, start = 15.dp, end = 15.dp), verticalArrangement = Arrangement.Bottom ) { - Row { + Row(modifier = Modifier + .wrapContentSize()) { + CategoryChip1(category = "Sports") + } + Row (modifier = Modifier.padding(bottom = 2.dp)){ Text( - text = "Trending .", - fontSize = 14.sp, + text = "Trending .", + fontSize = 12.sp, color = Color.White, modifier = Modifier.padding(end = 8.dp) ) Text( text = date, - fontSize = 14.sp, + fontSize = 12.sp, color = Color.White ) } Spacer(modifier = Modifier.height(8.dp)) Text( text = article.title, - fontSize = 22.sp, + fontSize = 16.sp, + maxLines = 3, + overflow = TextOverflow.Ellipsis, fontWeight = FontWeight.Bold, color = Color.White, modifier = Modifier.fillMaxWidth() ) - Spacer(modifier = Modifier.height(4.dp)) } } } diff --git a/app/src/main/java/dev/mkao/weaver/presentation/common/CategoryChip.kt b/app/src/main/java/dev/mkao/weaver/presentation/common/CategoryChip.kt index 309e5b7..7dbeb62 100644 --- a/app/src/main/java/dev/mkao/weaver/presentation/common/CategoryChip.kt +++ b/app/src/main/java/dev/mkao/weaver/presentation/common/CategoryChip.kt @@ -22,7 +22,7 @@ fun CategoryChip1( Text( text = category, color = Color.White, - fontSize = 16.sp, + fontSize = 12.sp, fontWeight = FontWeight.Bold, modifier = Modifier .padding(horizontal = 8.dp, vertical = 4.dp) diff --git a/app/src/main/java/dev/mkao/weaver/presentation/details/NewsArticleUi.kt b/app/src/main/java/dev/mkao/weaver/presentation/details/NewsArticleUi.kt index a335400..bba2d4d 100644 --- a/app/src/main/java/dev/mkao/weaver/presentation/details/NewsArticleUi.kt +++ b/app/src/main/java/dev/mkao/weaver/presentation/details/NewsArticleUi.kt @@ -26,6 +26,7 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -37,10 +38,12 @@ import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel import coil.compose.rememberAsyncImagePainter import dev.mkao.weaver.R import dev.mkao.weaver.domain.model.Article @@ -56,12 +59,12 @@ fun NewsArticleUi( article: Article?, onBackPressed: () -> Unit ) { - val sharedViewModel = SharedViewModel() - val selectedOne = sharedViewModel.selectedCategory - article?.let { news -> - var articleContent by remember { mutableStateOf("Loading...") } + val viewModel: SharedViewModel = hiltViewModel() + var articleContent by remember { mutableStateOf("Loading...") } + StatusbarEffect() - StatusbarEffect() + article?.let { news -> + val isBookmarked by viewModel.isArticleBookmarked(news.url).collectAsState(initial = false) LaunchedEffect(news.url) { articleContent = withContext(Dispatchers.IO) { @@ -80,13 +83,20 @@ fun NewsArticleUi( imageUrl = it, onBackPressed = onBackPressed, title = news.title, - publishedAt = news.publishedAt + publishedAt = news.publishedAt, + isBookmarked = isBookmarked, + onBookClicked = { + viewModel.toggleBookmark(news) + } ) } Column( modifier = Modifier .fillMaxSize() - .background(MaterialTheme.colorScheme.surface, shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp)) + .background( + MaterialTheme.colorScheme.surface, + shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp) + ) ) { ArticleContent( description = articleContent, @@ -107,10 +117,13 @@ fun ArticleImage( imageUrl: String, onBackPressed: () -> Unit, title: String, - publishedAt: String? + publishedAt: String?, + isBookmarked: Boolean, + onBookClicked :() -> Unit ) { val imagePainter = rememberAsyncImagePainter(imageUrl) val elapsedTime = calculateElapsedTime(publishedAt) + var isSelected by remember { mutableStateOf(false)} Box( modifier = Modifier @@ -193,7 +206,8 @@ fun ArticleImage( horizontalArrangement = Arrangement.End ) { IconButton( - onClick = { /* Handle favorite */ }, + onClick = { + onBookClicked() }, modifier = Modifier .background( Color.White.copy(alpha = 0.2f), @@ -201,14 +215,16 @@ fun ArticleImage( ) ) { Icon( - painter = painterResource(R.drawable.bookmark), + painter = painterResource(R.drawable.morehoriz), contentDescription = "Favorite", tint = Color.White ) } Spacer(modifier = Modifier.width(8.dp)) IconButton( - onClick = { /* Handle favorite */ }, + onClick = { + isSelected =!isSelected + onBookClicked() }, modifier = Modifier .background( Color.White.copy(alpha = 0.2f), @@ -216,9 +232,9 @@ fun ArticleImage( ) ) { Icon( - painter = painterResource(R.drawable.morehoriz), - contentDescription = "More", - tint = Color.White + painter = painterResource(R.drawable.bookmark), + contentDescription = "Favourites", + tint = if (isSelected) Color.Yellow else Color.White ) } } @@ -263,7 +279,7 @@ fun ArticleContent( ) { Image( painter = painterResource(R.drawable.ic_logo), - contentDescription = "Source Image", + contentDescription = stringResource(R.string.source_image), modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop ) @@ -273,7 +289,7 @@ fun ArticleContent( verticalArrangement = Arrangement.Center, ) { Text( - text = sourceName ?: "None", + text = sourceName ?: stringResource(R.string.other_Sources), fontSize = 20.sp, color = contentColor, fontWeight = FontWeight.Bold, @@ -321,23 +337,3 @@ fun CategoryChip( ) } } - -//@Preview(showBackground = true) -//@Composable -//fun PreviewNewsArticleUi() { -// val mockArticle = Article( -// source = Source(name = "Mock Source", url = "https://example.com/source_image.jpg", category = "", id = ""), -// author = "Author Name", -// title = "Sample News Title", -// description = "Sample news description.", -// url = "https://example.com", -// urlToImage = "https://example.com/news_image.jpg", -// publishedAt = "2023-05-05T12:34:56Z", -// content = "Sample content for the news article." -// ) -// -// NewsArticleUi( -// article = mockArticle, -// onBackPressed = {} -// ) -//} \ No newline at end of file diff --git a/app/src/main/java/dev/mkao/weaver/presentation/home/ArticleScreen.kt b/app/src/main/java/dev/mkao/weaver/presentation/home/ArticleScreen.kt index 90b82a2..67fd8e9 100644 --- a/app/src/main/java/dev/mkao/weaver/presentation/home/ArticleScreen.kt +++ b/app/src/main/java/dev/mkao/weaver/presentation/home/ArticleScreen.kt @@ -107,14 +107,14 @@ fun ArticleScreen( Text( modifier = Modifier.padding(horizontal = 10.dp), text = stringResource(R.string.discover), - fontSize = 34.sp, + fontSize = 32.sp, fontWeight = FontWeight.Bold, ) Spacer(modifier = Modifier.height(15.dp)) Text( modifier = Modifier.padding(horizontal = 10.dp), text = stringResource(R.string.fresh_stories_and_bold_ideas_to_help_you_live_curiously), - fontSize = 15.sp, + fontSize = 16.sp, fontWeight = FontWeight.Bold, ) } diff --git a/app/src/main/java/dev/mkao/weaver/presentation/home/TopSection.kt b/app/src/main/java/dev/mkao/weaver/presentation/home/TopSection.kt index e726d97..95cbf09 100644 --- a/app/src/main/java/dev/mkao/weaver/presentation/home/TopSection.kt +++ b/app/src/main/java/dev/mkao/weaver/presentation/home/TopSection.kt @@ -1,6 +1,9 @@ package dev.mkao.weaver.presentation.home +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row @@ -8,10 +11,11 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons @@ -29,12 +33,16 @@ import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.key +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight @@ -67,10 +75,8 @@ fun TopSection( val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = false) var isLoading by remember { mutableStateOf(true) } - StatusbarEffect() - if (shouldBottomSheetShow) { ModalBottomSheet( onDismissRequest = { shouldBottomSheetShow = false }, @@ -100,9 +106,7 @@ fun TopSection( .clip(shape = RoundedCornerShape(12.dp)), title = { /* Optional title content */ }, navigationIcon = { - IconButton( - onClick = { /* Handle menu icon click */ } - ) { + IconButton(onClick = { /* Handle menu icon click */ }) { Icon( imageVector = Icons.Outlined.Menu, contentDescription = stringResource(R.string.menu) @@ -110,17 +114,13 @@ fun TopSection( } }, actions = { - IconButton( - onClick = { /* Handle search icon click */ } - ) { - Icon(imageVector = Icons.Filled.Search, contentDescription = stringResource( - R.string.search - ) + IconButton(onClick = { /* Handle search icon click */ }) { + Icon( + imageVector = Icons.Filled.Search, + contentDescription = stringResource(R.string.search) ) } - IconButton( - onClick = { /* Handle notification icon click */ } - ) { + IconButton(onClick = { /* Handle notification icon click */ }) { Icon( imageVector = Icons.Filled.Notifications, contentDescription = stringResource(R.string.notifications) @@ -138,11 +138,11 @@ fun TopSection( .fillMaxSize() .padding(paddingValues) ) { - Spacer(modifier = Modifier.height(15.dp)) - - Row (modifier = Modifier - .fillMaxWidth() - .padding(16.dp)){ + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { Text( text = stringResource(R.string.Briefing), fontWeight = FontWeight.Bold, @@ -157,51 +157,43 @@ fun TopSection( ) } - if (state.isLoading) { - LazyRow( - modifier = Modifier.height(300.dp), - contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp) + if (isLoading) { + Box( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() ) { - items(5) { + repeat(3) { ArticleCardShimmerEffect( modifier = Modifier - .height(300.dp) - .width(550.dp) - .padding(horizontal = 8.dp) + .width(350.dp) + .padding(horizontal = 2.dp) + .align(when (it) { + 1 -> Alignment.CenterStart + else -> Alignment.CenterEnd + }) ) } } } else { - LazyRow( - modifier = Modifier.height(300.dp), - contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - items(state.sportsArticles) { article -> - CardArtiCleTop( - article = article, - onReadFullStoryClicked = { - shouldBottomSheetShow = true - onEvent(EventsHolder.OnArticleCardClicked(article)) - } - ) + AnimatedSportsArticlesCarousel( + articles = state.sportsArticles, + onReadFullStoryClicked = { article -> + shouldBottomSheetShow = true + onEvent(EventsHolder.OnArticleCardClicked(article)) } - } - } - LaunchedEffect(true) { - delay(4000) - isLoading = false + ) } - Spacer(modifier = Modifier.height(15.dp)) - Row (modifier = Modifier - .fillMaxWidth() - .padding(16.dp)){ - Text( - text = stringResource(R.string.recommendations), - fontWeight = FontWeight.Bold, - fontSize = 20.sp - ) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { + Text( + text = stringResource(R.string.recommendations), + fontWeight = FontWeight.Bold, + fontSize = 20.sp + ) Spacer(modifier = Modifier.weight(1f)) Text( text = stringResource(R.string.view_all), @@ -211,8 +203,8 @@ fun TopSection( ) } - if (state.isLoading) { - repeat(5) { + if (isLoading) { + repeat(6) { ArticleCardShimmerEffect( modifier = Modifier .fillMaxWidth() @@ -237,11 +229,101 @@ fun TopSection( } } } - LaunchedEffect(true) { - delay(4000) - isLoading = false - } } } ) + + LaunchedEffect(true) { + delay(4000) + isLoading = false + } +} +@Composable +fun AnimatedSportsArticlesCarousel( + articles: List
, + onReadFullStoryClicked: (Article) -> Unit +) { + if (articles.isEmpty()) { + // Handle empty state, perhaps show a message + Box( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(), + contentAlignment = Alignment.Center + ) { + Text("Please wait...") + } + return + } + + var currentIndex by remember { mutableIntStateOf(0) } + val animationProgress = remember { Animatable(0f) } + + LaunchedEffect(currentIndex) { + animationProgress.animateTo( + targetValue = 1f, + animationSpec = tween(durationMillis = 9000) + ) + currentIndex = (currentIndex + 1) % articles.size + animationProgress.snapTo(0f) + } + + Box( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + ) { + for (i in 0..2.coerceAtMost(articles.size - 1)) { + val index = (currentIndex + i) % articles.size + val article = articles[index] + + key(article.urlToImage) { + AnimatedArticleCard( + article = article, + position = i, + progress = animationProgress.value, + onReadFullStoryClicked = onReadFullStoryClicked + ) + } + } + } +} + +@Composable +fun AnimatedArticleCard( + article: Article, + position: Int, + progress: Float, + onReadFullStoryClicked: (Article) -> Unit +) { + val cardWidth = 335f + val spacing = 1f + val xOffset: Float + val scale: Float + + when (position) { + 0 -> { // Outgoing article + xOffset = -cardWidth + spacing + (progress * (cardWidth + spacing)) + scale = 0.8f + (progress * 0.2f) + } + 1 -> { // Central article + xOffset = spacing + (progress * (cardWidth + spacing)) + scale = 1f - (progress * 0.2f) + } + else -> { // Incoming article + xOffset = cardWidth + spacing + (progress * (cardWidth + spacing)) + scale = 0.8f + (progress * 0.2f) + } + } + + Box( + modifier = Modifier + .offset(x = xOffset.dp) + .scale(scale) + ) { + CardArtiCleTop( + article = article, + onReadFullStoryClicked = { onReadFullStoryClicked(article) } + ) + } } \ No newline at end of file From 0b57e79ea6642e0a6a461193efd0f4738a6a4d98 Mon Sep 17 00:00:00 2001 From: mkaocodes Date: Thu, 4 Jul 2024 12:31:33 +0300 Subject: [PATCH 05/11] - Fixed theming white Icons on status bar - Added Bookmarks Navigation --- .../mkao/weaver/presentation/common/StatusbarEffect.kt | 10 ++++++---- .../weaver/presentation/navigation/NewsNavGraph.kt | 4 +++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/dev/mkao/weaver/presentation/common/StatusbarEffect.kt b/app/src/main/java/dev/mkao/weaver/presentation/common/StatusbarEffect.kt index def4371..74c8b46 100644 --- a/app/src/main/java/dev/mkao/weaver/presentation/common/StatusbarEffect.kt +++ b/app/src/main/java/dev/mkao/weaver/presentation/common/StatusbarEffect.kt @@ -1,15 +1,17 @@ package dev.mkao.weaver.presentation.common -import androidx.compose.material3.MaterialTheme +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.luminance import com.google.accompanist.systemuicontroller.rememberSystemUiController + @Composable -fun StatusbarEffect() { +fun StatusbarEffect() { val systemUiController = rememberSystemUiController() - val useDarkIcons = MaterialTheme.colorScheme.primary.luminance() > 0.9f + val isDarkTheme = isSystemInDarkTheme() + val useDarkIcons = !isDarkTheme + SideEffect { systemUiController.setSystemBarsColor( color = Color.Transparent, diff --git a/app/src/main/java/dev/mkao/weaver/presentation/navigation/NewsNavGraph.kt b/app/src/main/java/dev/mkao/weaver/presentation/navigation/NewsNavGraph.kt index dc9d426..d229c5e 100644 --- a/app/src/main/java/dev/mkao/weaver/presentation/navigation/NewsNavGraph.kt +++ b/app/src/main/java/dev/mkao/weaver/presentation/navigation/NewsNavGraph.kt @@ -7,6 +7,7 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import dev.mkao.weaver.presentation.About.About +import dev.mkao.weaver.presentation.Bookmarks.BookmarkScreen import dev.mkao.weaver.presentation.common.SettingsScreen import dev.mkao.weaver.presentation.details.NewsArticleUi import dev.mkao.weaver.presentation.home.ArticleScreen @@ -56,7 +57,8 @@ fun NewsNavGraph(navController: NavHostController) { ) } composable(route = Screen.Bookmarks.route) { -// BookmarksScreen() + val bookmarkViewModel: SharedViewModel = hiltViewModel() + BookmarkScreen(sharedViewModel = bookmarkViewModel,navController) } composable(route = Screen.Settings.route) { SettingsScreen(navController) From dde7dd59692a6ff1aa8c782cd54c4568e91415ab Mon Sep 17 00:00:00 2001 From: mkaocodes Date: Fri, 5 Jul 2024 12:34:55 +0300 Subject: [PATCH 06/11] - Fixed Database Article saving/Added Saving/Delete --- .../dev/mkao/weaver/data/remote/NewsApi.kt | 2 ++ .../dev/mkao/weaver/data/remote/NewsDao.kt | 6 ++++-- .../mkao/weaver/data/remote/NewsDatabase.kt | 19 ++----------------- 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/dev/mkao/weaver/data/remote/NewsApi.kt b/app/src/main/java/dev/mkao/weaver/data/remote/NewsApi.kt index 911d504..7346ec4 100644 --- a/app/src/main/java/dev/mkao/weaver/data/remote/NewsApi.kt +++ b/app/src/main/java/dev/mkao/weaver/data/remote/NewsApi.kt @@ -14,11 +14,13 @@ interface NewsApi { @Query("apikey") apiKey: String = API_KEY, ): NewsApiResponse + @GET("everything") suspend fun searchRequest( @Query("q") query: String, @Query("apiKey") apiKey: String = API_KEY ): NewsApiResponse + companion object{ const val API_KEY = "2d8537ffd9a94e55b63b570ff2674a3a" const val BASE_URL = "https://newsapi.org/v2/" diff --git a/app/src/main/java/dev/mkao/weaver/data/remote/NewsDao.kt b/app/src/main/java/dev/mkao/weaver/data/remote/NewsDao.kt index 6e0df5c..a19e43a 100644 --- a/app/src/main/java/dev/mkao/weaver/data/remote/NewsDao.kt +++ b/app/src/main/java/dev/mkao/weaver/data/remote/NewsDao.kt @@ -13,9 +13,11 @@ interface NewsDao { suspend fun upsert(article: Article) @Query("SELECT * FROM Articles") - fun getArticles(): List
+ suspend fun getArticles(): List
@Delete suspend fun delete(article: Article) -} \ No newline at end of file + @Query("SELECT * FROM Articles WHERE isBookedMarked = 1") + suspend fun getBookedArticles(): List
+} diff --git a/app/src/main/java/dev/mkao/weaver/data/remote/NewsDatabase.kt b/app/src/main/java/dev/mkao/weaver/data/remote/NewsDatabase.kt index 212f125..529640a 100644 --- a/app/src/main/java/dev/mkao/weaver/data/remote/NewsDatabase.kt +++ b/app/src/main/java/dev/mkao/weaver/data/remote/NewsDatabase.kt @@ -1,27 +1,12 @@ package dev.mkao.weaver.data.remote -import android.content.Context import androidx.room.Database -import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.TypeConverters import dev.mkao.weaver.domain.model.Article @Database(entities = [Article::class], version = 1) @TypeConverters(NewsTypeConvertor::class) -abstract class NewsDatabase: RoomDatabase(){ - abstract fun articleDao(): NewsDao - - companion object { - @Volatile private var instance: NewsDatabase? = null - - fun getDatabase(context: Context): NewsDatabase = - instance ?: synchronized(this) { - instance ?: Room.databaseBuilder( - context.applicationContext, - NewsDatabase::class.java, - "articles" - ).build().also { instance = it } - } - } +abstract class NewsDatabase : RoomDatabase() { + abstract fun articleDao(): NewsDao } \ No newline at end of file From a775b99e168043ac048bb103cbad80594e2f2c90 Mon Sep 17 00:00:00 2001 From: mkaocodes Date: Fri, 5 Jul 2024 12:39:25 +0300 Subject: [PATCH 07/11] - Successfully integrated and functional Room DB(CRUD) --- .../weaver/data/repository/RepositoryImpl.kt | 47 +++++++++++---- .../dev/mkao/weaver/domain/model/Article.kt | 3 +- .../weaver/domain/repository/Repository.kt | 8 +-- .../mkao/weaver/viewModels/SharedViewModel.kt | 60 ++++++++++++++++++- 4 files changed, 100 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/dev/mkao/weaver/data/repository/RepositoryImpl.kt b/app/src/main/java/dev/mkao/weaver/data/repository/RepositoryImpl.kt index c026152..c97c09b 100644 --- a/app/src/main/java/dev/mkao/weaver/data/repository/RepositoryImpl.kt +++ b/app/src/main/java/dev/mkao/weaver/data/repository/RepositoryImpl.kt @@ -1,25 +1,32 @@ package dev.mkao.weaver.data.repository +import android.util.Log import dev.mkao.weaver.data.remote.NewsApi import dev.mkao.weaver.data.remote.NewsDao import dev.mkao.weaver.domain.model.Article import dev.mkao.weaver.domain.repository.Repository import dev.mkao.weaver.util.Assets +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext class RepositoryImpl( private val newsApi: NewsApi, private val newsDao: NewsDao ): Repository { - + override suspend fun getTopHeadlines(category: String): Assets> { return try { val response = newsApi.getTopHeadlines(category = category) val articles = response.articles + //cache the articles + articles.forEach { newsDao.upsert(it) } Assets.Success(filterRemovedArticles(articles)) } catch (e: Exception) { - Assets.Error(message = "404! Api Error") + //Retrieve articles from DB if API Error + val articles = newsDao.getArticles() + Assets.Success(filterRemovedArticles(articles)) } } @@ -28,6 +35,8 @@ class RepositoryImpl( // Use the newsApi to fetch search results using the provided query val response = newsApi.searchRequest(query = query) val articles = response.articles + //Cache the searched articles + articles.forEach { newsDao.upsert(it) } // Return the search results as Success after filtering Assets.Success(filterRemovedArticles(articles)) @@ -37,23 +46,39 @@ class RepositoryImpl( } } - override suspend fun upsertArticle(article: Article) { - newsDao.upsert(article) - } override suspend fun deleteArticle(article: Article) { - newsDao.delete(article) + val unBookedMarked = article.copy(isBookedMarked = false) + newsDao.upsert(unBookedMarked) // Update instead of delete + Log.d("RepositoryImpl", "Article deleted: ${article.title}") } - override suspend fun getArticle(url: String): Article? { - TODO("Not yet implemented") + override suspend fun getArticles(): Assets> { + return try { + withContext(Dispatchers.IO) { + val articles = newsDao.getArticles() + Assets.Success(articles) + } + } catch (e: Exception) { + Assets.Error(message = "Error") + } + } + + override suspend fun insertedArticle(article: Article) { + val bookedMarked = article.copy(isBookedMarked = true) + newsDao.upsert(bookedMarked) + Log.d("RepositoryImpl", "Article inserted/updated: ${article.title}") } - override fun getArticles(): Assets> { + + + override suspend fun getBookedArticles(): Assets> { return try { - val articles = newsDao.getArticles() + val articles = newsDao.getBookedArticles() + Log.d("RepositoryImpl", "Retrieved ${articles.size} bookmarked articles") Assets.Success(articles) - } catch (e: Exception) { + } catch (e:Exception){ + Log.e("RepositoryImpl", "Error retrieving bookmarked articles", e) Assets.Error(message = "Error") } } diff --git a/app/src/main/java/dev/mkao/weaver/domain/model/Article.kt b/app/src/main/java/dev/mkao/weaver/domain/model/Article.kt index a4b0a07..dded799 100644 --- a/app/src/main/java/dev/mkao/weaver/domain/model/Article.kt +++ b/app/src/main/java/dev/mkao/weaver/domain/model/Article.kt @@ -8,12 +8,13 @@ import kotlinx.android.parcel.Parcelize @Parcelize @Entity(tableName = "Articles") data class Article( + @PrimaryKey val url: String, val source: Source, val author: String?, val title: String, val content: String?, val description: String?, - @PrimaryKey val url: String, + var isBookedMarked: Boolean = false, val urlToImage: String?, val publishedAt: String, ): Parcelable \ No newline at end of file diff --git a/app/src/main/java/dev/mkao/weaver/domain/repository/Repository.kt b/app/src/main/java/dev/mkao/weaver/domain/repository/Repository.kt index c59e65e..65c3c1c 100644 --- a/app/src/main/java/dev/mkao/weaver/domain/repository/Repository.kt +++ b/app/src/main/java/dev/mkao/weaver/domain/repository/Repository.kt @@ -9,11 +9,11 @@ interface Repository { suspend fun searchRequest(query: String): Assets> - suspend fun upsertArticle(article: Article) - suspend fun deleteArticle(article: Article) - fun getArticles(): Assets> + suspend fun getArticles(): Assets> + + suspend fun insertedArticle(article: Article) - suspend fun getArticle(url: String): Article? + suspend fun getBookedArticles(): Assets> } \ No newline at end of file diff --git a/app/src/main/java/dev/mkao/weaver/viewModels/SharedViewModel.kt b/app/src/main/java/dev/mkao/weaver/viewModels/SharedViewModel.kt index de5b450..b75cb3a 100644 --- a/app/src/main/java/dev/mkao/weaver/viewModels/SharedViewModel.kt +++ b/app/src/main/java/dev/mkao/weaver/viewModels/SharedViewModel.kt @@ -1,26 +1,82 @@ package dev.mkao.weaver.viewModels + +import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel import dev.mkao.weaver.domain.model.Article +import dev.mkao.weaver.domain.repository.Repository +import dev.mkao.weaver.util.Assets +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class SharedViewModel @Inject constructor( + private val repository: Repository +) : ViewModel() { -class SharedViewModel : ViewModel() { private val _selectedArticle = MutableStateFlow(null) val selectedArticle: StateFlow = _selectedArticle private val _selectedCategory = MutableStateFlow(null) val selectedCategory: StateFlow = _selectedCategory + private val _bookmarkedArticles = MutableStateFlow>(emptyList()) + val bookmarkedArticles: StateFlow> = _bookmarkedArticles.asStateFlow() + + init { + fetchBookedArticles() + } + + private fun fetchBookedArticles() { + viewModelScope.launch { + when (val result = repository.getBookedArticles()) { + is Assets.Success -> _bookmarkedArticles.value = result.data!! + is Assets.Error -> _bookmarkedArticles.value = emptyList() + } + } + } + fun selectArticle(article: Article) { viewModelScope.launch { _selectedArticle.value = article } } + fun selectCategory(category: String) { viewModelScope.launch { _selectedCategory.value = category } } -} \ No newline at end of file + + fun toggleBookmark(article: Article) { + viewModelScope.launch { + val updatedArticle = article.copy(isBookedMarked = !article.isBookedMarked) + if (updatedArticle.isBookedMarked) { + repository.insertedArticle(article) + Log.d("SharedViewModel", "Article bookmarked: ${updatedArticle.title}") + } else { + repository.deleteArticle(article) + Log.d("SharedViewModel", "Article unbookmarked: ${updatedArticle.title}") + } + fetchBookedArticles() // Refresh the list after toggling + } + } + fun isArticleBookmarked(url: String): Flow = flow { + when (val result = repository.getBookedArticles()) { + is Assets.Success -> { + result.data?.let { emit(it.any { it.url == url }) } + Log.d("SharedViewModel", "Loaded ${result.data?.size} bookmarked articles") + } + is Assets.Error -> { + emit(false) + Log.e("SharedViewModel", "Error loading bookmarked articles: ${result.message}") + } + } + } +} From 4d6d3d95d7f6020d94bb416fa3be307a18436c11 Mon Sep 17 00:00:00 2001 From: mkaocodes Date: Fri, 5 Jul 2024 12:47:04 +0300 Subject: [PATCH 08/11] - Minor Fixes --- .../java/dev/mkao/weaver/presentation/details/NewsArticleUi.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/dev/mkao/weaver/presentation/details/NewsArticleUi.kt b/app/src/main/java/dev/mkao/weaver/presentation/details/NewsArticleUi.kt index bba2d4d..67e639f 100644 --- a/app/src/main/java/dev/mkao/weaver/presentation/details/NewsArticleUi.kt +++ b/app/src/main/java/dev/mkao/weaver/presentation/details/NewsArticleUi.kt @@ -223,7 +223,7 @@ fun ArticleImage( Spacer(modifier = Modifier.width(8.dp)) IconButton( onClick = { - isSelected =!isSelected + isSelected =! isSelected onBookClicked() }, modifier = Modifier .background( From cec411db97dc46cbcf20b785ae8f1a3447ff6a53 Mon Sep 17 00:00:00 2001 From: mkaocodes Date: Fri, 5 Jul 2024 12:57:14 +0300 Subject: [PATCH 09/11] - Minor code fixes --- .../mkao/weaver/presentation/Bookmarks/BookmarksScreen.kt | 4 ++-- .../dev/mkao/weaver/presentation/details/NewsArticleUi.kt | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/dev/mkao/weaver/presentation/Bookmarks/BookmarksScreen.kt b/app/src/main/java/dev/mkao/weaver/presentation/Bookmarks/BookmarksScreen.kt index ee168a6..5896b05 100644 --- a/app/src/main/java/dev/mkao/weaver/presentation/Bookmarks/BookmarksScreen.kt +++ b/app/src/main/java/dev/mkao/weaver/presentation/Bookmarks/BookmarksScreen.kt @@ -45,13 +45,13 @@ import dev.mkao.weaver.viewModels.SharedViewModel fun ArticleList(articles: List
, sharedViewModel: SharedViewModel, navController: NavController) { LazyColumn { items(articles) { article -> - ArticleItem(article, sharedViewModel, navController) + ArticleItem(article, sharedViewModel) } } } @Composable -fun ArticleItem(article: Article, sharedViewModel: SharedViewModel, navController: NavController) { +fun ArticleItem(article: Article, sharedViewModel: SharedViewModel) { val date = article.publishedAt val elapsedtime = calculateElapsedTime(date) Row( diff --git a/app/src/main/java/dev/mkao/weaver/presentation/details/NewsArticleUi.kt b/app/src/main/java/dev/mkao/weaver/presentation/details/NewsArticleUi.kt index 67e639f..799fb70 100644 --- a/app/src/main/java/dev/mkao/weaver/presentation/details/NewsArticleUi.kt +++ b/app/src/main/java/dev/mkao/weaver/presentation/details/NewsArticleUi.kt @@ -84,7 +84,6 @@ fun NewsArticleUi( onBackPressed = onBackPressed, title = news.title, publishedAt = news.publishedAt, - isBookmarked = isBookmarked, onBookClicked = { viewModel.toggleBookmark(news) } @@ -118,8 +117,7 @@ fun ArticleImage( onBackPressed: () -> Unit, title: String, publishedAt: String?, - isBookmarked: Boolean, - onBookClicked :() -> Unit + onBookClicked: () -> Unit ) { val imagePainter = rememberAsyncImagePainter(imageUrl) val elapsedTime = calculateElapsedTime(publishedAt) @@ -223,7 +221,7 @@ fun ArticleImage( Spacer(modifier = Modifier.width(8.dp)) IconButton( onClick = { - isSelected =! isSelected + isSelected = !isSelected onBookClicked() }, modifier = Modifier .background( From bf6bb498f4107ca2f062942e506468afaa57449e Mon Sep 17 00:00:00 2001 From: mkaocodes Date: Fri, 5 Jul 2024 13:01:25 +0300 Subject: [PATCH 10/11] - Code cleaning --- app/src/main/java/dev/mkao/weaver/data/remote/NewsApi.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/dev/mkao/weaver/data/remote/NewsApi.kt b/app/src/main/java/dev/mkao/weaver/data/remote/NewsApi.kt index 7346ec4..8051a22 100644 --- a/app/src/main/java/dev/mkao/weaver/data/remote/NewsApi.kt +++ b/app/src/main/java/dev/mkao/weaver/data/remote/NewsApi.kt @@ -22,7 +22,7 @@ interface NewsApi { ): NewsApiResponse companion object{ - const val API_KEY = "2d8537ffd9a94e55b63b570ff2674a3a" + const val API_KEY = "ADD YOUR API KEY HERE" const val BASE_URL = "https://newsapi.org/v2/" } } \ No newline at end of file From 8aadd8a2d1c72965f040aa0c2508ef9900ecb152 Mon Sep 17 00:00:00 2001 From: mkaocodes Date: Fri, 5 Jul 2024 13:03:11 +0300 Subject: [PATCH 11/11] - Code cleaning --- app/src/main/java/dev/mkao/weaver/data/remote/NewsApi.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/dev/mkao/weaver/data/remote/NewsApi.kt b/app/src/main/java/dev/mkao/weaver/data/remote/NewsApi.kt index 8051a22..6cae0dc 100644 --- a/app/src/main/java/dev/mkao/weaver/data/remote/NewsApi.kt +++ b/app/src/main/java/dev/mkao/weaver/data/remote/NewsApi.kt @@ -6,7 +6,7 @@ import retrofit2.http.Query interface NewsApi { //base url for the news api - //https://newsapi.org/v2/top-headlines?country=us&category=business&apiKey=2d8537ffd9a94e55b63b570ff2674a3a + //https://newsapi.org/v2/top-headlines?country=us&category=business&apiKey={} @GET("top-headlines") suspend fun getTopHeadlines( @Query("category") category: String,