diff --git a/androidApp/src/main/kotlin/com/prof18/feedflow/addfeed/AddFeedScreen.android.kt b/androidApp/src/main/kotlin/com/prof18/feedflow/addfeed/AddFeedScreen.android.kt index 8ccd5a7b..e1c44e1a 100644 --- a/androidApp/src/main/kotlin/com/prof18/feedflow/addfeed/AddFeedScreen.android.kt +++ b/androidApp/src/main/kotlin/com/prof18/feedflow/addfeed/AddFeedScreen.android.kt @@ -36,6 +36,7 @@ fun AddFeedScreen( val context = LocalContext.current val isAddDone by viewModel.isAddDoneState.collectAsStateWithLifecycle() + val isInvalidUrl by viewModel.isInvalidRssFeed.collectAsStateWithLifecycle() if (isAddDone) { feedName = "" @@ -48,6 +49,7 @@ fun AddFeedScreen( AddFeedScreenContent( feedName = feedName, feedUrl = feedUrl, + isInvalidUrl = isInvalidUrl, onFeedNameUpdated = { name -> feedName = name viewModel.updateFeedNameTextFieldValue(name) @@ -68,6 +70,7 @@ fun AddFeedScreen( private fun AddFeedScreenContent( feedName: String, feedUrl: String, + isInvalidUrl: Boolean, onFeedNameUpdated: (String) -> Unit, onFeedUrlUpdated: (String) -> Unit, addFeed: () -> Unit, @@ -99,6 +102,7 @@ private fun AddFeedScreenContent( feedName = feedName, onFeedNameUpdated = onFeedNameUpdated, feedUrl = feedUrl, + isInvalidUrl = isInvalidUrl, onFeedUrlUpdated = onFeedUrlUpdated, addFeed = addFeed, ) @@ -115,6 +119,23 @@ private fun AddScreenContentPreview() { onFeedNameUpdated = {}, onFeedUrlUpdated = {}, addFeed = { }, + isInvalidUrl = false, + navigateBack = {}, + ) + } +} + +@FeedFlowPreview +@Composable +private fun AddScreenContentInvalidUrlPreview() { + FeedFlowTheme { + AddFeedScreenContent( + feedName = "My Feed", + feedUrl = "https://www.ablog.com/feed", + isInvalidUrl = true, + onFeedNameUpdated = {}, + onFeedUrlUpdated = {}, + addFeed = { }, navigateBack = {}, ) } diff --git a/androidApp/src/main/kotlin/com/prof18/feedflow/home/HomeScreen.android.kt b/androidApp/src/main/kotlin/com/prof18/feedflow/home/HomeScreen.android.kt index ff3d8875..ad464384 100644 --- a/androidApp/src/main/kotlin/com/prof18/feedflow/home/HomeScreen.android.kt +++ b/androidApp/src/main/kotlin/com/prof18/feedflow/home/HomeScreen.android.kt @@ -60,7 +60,6 @@ import kotlinx.coroutines.launch import org.koin.androidx.compose.koinViewModel import org.koin.compose.koinInject -@OptIn(ExperimentalMaterialApi::class) @Suppress("LongMethod") @Composable internal fun HomeScreen( diff --git a/desktopApp/src/jvmMain/kotlin/com/prof18/feedflow/about/AboutContent.kt b/desktopApp/src/jvmMain/kotlin/com/prof18/feedflow/about/AboutContent.kt index 26c3e1bd..b37d59b0 100644 --- a/desktopApp/src/jvmMain/kotlin/com/prof18/feedflow/about/AboutContent.kt +++ b/desktopApp/src/jvmMain/kotlin/com/prof18/feedflow/about/AboutContent.kt @@ -12,7 +12,6 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.rememberScrollbarAdapter -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider diff --git a/desktopApp/src/jvmMain/kotlin/com/prof18/feedflow/addfeed/AddFeedScreen.desktop.kt b/desktopApp/src/jvmMain/kotlin/com/prof18/feedflow/addfeed/AddFeedScreen.desktop.kt index 84c73d4a..ae68bee9 100644 --- a/desktopApp/src/jvmMain/kotlin/com/prof18/feedflow/addfeed/AddFeedScreen.desktop.kt +++ b/desktopApp/src/jvmMain/kotlin/com/prof18/feedflow/addfeed/AddFeedScreen.desktop.kt @@ -1,7 +1,6 @@ package com.prof18.feedflow.addfeed import androidx.compose.desktop.ui.tooling.preview.Preview -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -25,6 +24,7 @@ fun AddFeedScreen( val viewModel = desktopViewModel { DI.koin.get() } val isAddDone by viewModel.isAddDoneState.collectAsState() + val isInvalidUrl by viewModel.isInvalidRssFeed.collectAsState() if (isAddDone) { feedName = "" @@ -35,6 +35,7 @@ fun AddFeedScreen( AddFeedScreenContent( feedName = feedName, feedUrl = feedUrl, + isInvalidUrl = isInvalidUrl, onFeedNameUpdated = { name -> feedName = name viewModel.updateFeedNameTextFieldValue(name) @@ -50,10 +51,10 @@ fun AddFeedScreen( } @Composable -@OptIn(ExperimentalMaterial3Api::class) private fun AddFeedScreenContent( feedName: String, feedUrl: String, + isInvalidUrl: Boolean, onFeedNameUpdated: (String) -> Unit, onFeedUrlUpdated: (String) -> Unit, addFeed: () -> Unit, @@ -62,6 +63,7 @@ private fun AddFeedScreenContent( AddFeedsContent( paddingValues = paddingValues, feedName = feedName, + isInvalidUrl = isInvalidUrl, onFeedNameUpdated = onFeedNameUpdated, feedUrl = feedUrl, onFeedUrlUpdated = onFeedUrlUpdated, @@ -77,6 +79,7 @@ private fun AddScreenContentPreview() { AddFeedScreenContent( feedName = "My Feed", feedUrl = "https://www.ablog.com/feed", + isInvalidUrl = false, onFeedNameUpdated = {}, onFeedUrlUpdated = {}, addFeed = { }, @@ -93,6 +96,22 @@ private fun AddScreenContentDarkPreview() { AddFeedScreenContent( feedName = "My Feed", feedUrl = "https://www.ablog.com/feed", + isInvalidUrl = false, + onFeedNameUpdated = {}, + onFeedUrlUpdated = {}, + addFeed = { }, + ) + } +} + +@Preview +@Composable +private fun AddScreenContentInvalidUrlPreview() { + FeedFlowTheme { + AddFeedScreenContent( + feedName = "My Feed", + feedUrl = "https://www.ablog.com/feed", + isInvalidUrl = true, onFeedNameUpdated = {}, onFeedUrlUpdated = {}, addFeed = { }, diff --git a/i18n/src/commonMain/resources/MR/base/strings.xml b/i18n/src/commonMain/resources/MR/base/strings.xml index 12fce600..27e82e8d 100644 --- a/i18n/src/commonMain/resources/MR/base/strings.xml +++ b/i18n/src/commonMain/resources/MR/base/strings.xml @@ -44,4 +44,5 @@ Open website Something went wrong during the retrieval of the feeds. Please retry or restart the app :( Force feeds refresh + The link you provided is not a valid RSS feed diff --git a/iosApp/Source/Settings/AddFeed/AddFeedScreen.swift b/iosApp/Source/Settings/AddFeed/AddFeedScreen.swift index ee4c1482..68d67fae 100644 --- a/iosApp/Source/Settings/AddFeed/AddFeedScreen.swift +++ b/iosApp/Source/Settings/AddFeed/AddFeedScreen.swift @@ -11,17 +11,18 @@ import shared import KMPNativeCoroutinesAsync struct AddFeedScreen: View { - + @EnvironmentObject var appState: AppState @Environment(\.presentationMode) var presentationMode - + @State private var feedName = "" @State private var feedURL = "" - + @State private var isInvalidUrl = false + @StateObject var addFeedViewModel: AddFeedViewModel = KotlinDependencies.shared.getAddFeedViewModel() - + var body: some View { - VStack { + VStack(alignment: .leading) { TextField( MR.strings().feed_name.localized, text: $feedName @@ -29,35 +30,47 @@ struct AddFeedScreen: View { .textFieldStyle(RoundedBorderTextFieldStyle()) .padding(.top, Spacing.regular) .padding(.horizontal, Spacing.regular) - + TextField( MR.strings().feed_url.localized, text: $feedURL ) .keyboardType(.webSearch) + .border(isInvalidUrl ? Color.red : Color.clear) .textFieldStyle(RoundedBorderTextFieldStyle()) .padding(.top, Spacing.regular) .padding(.horizontal, Spacing.regular) - - Button( - action: { - addFeedViewModel.addFeed() - }, - label: { - Text(MR.strings().add_feed.localized) - } - ) - .buttonStyle(.bordered) - .padding(.top, Spacing.regular) - - Spacer() - } - .toolbar { - ToolbarItem(placement: .navigationBarLeading) { - Text(MR.strings().add_feed.localized) - .font(.title2) + + if isInvalidUrl { + Text(MR.strings().invalid_rss_url.localized) + .padding(.horizontal, Spacing.regular) + .frame(alignment: .leading) + .font(.caption) + .foregroundColor(.red) + } + + HStack { + Spacer() + + Button( + action: { + addFeedViewModel.addFeed() + }, + label: { + Text(MR.strings().add_feed.localized) + } + ) + .frame(alignment: .center) + .buttonStyle(.bordered) + .padding(.top, Spacing.regular) + + Spacer() } + + Spacer() } + .navigationTitle(MR.strings().add_feed.localized) + .navigationBarTitleDisplayMode(.inline) .onChange(of: feedName) { value in addFeedViewModel.updateFeedNameTextFieldValue(feedNameTextFieldValue: value) } @@ -88,6 +101,22 @@ struct AddFeedScreen: View { ) } } + .task { + do { + let stream = asyncSequence(for: addFeedViewModel.isInvalidRssFeedFlow) + for try await isInvalidRss in stream { + self.isInvalidUrl = isInvalidRss as? Bool ?? false + } + } catch { + self.appState.snackbarQueue.append( + SnackbarData( + title: MR.strings().generic_error_message.localized, + subtitle: nil, + showBanner: true + ) + ) + } + } } } diff --git a/shared/src/androidMain/kotlin/com/prof18/feedflow/domain/opml/OpmlFeedHandler.kt b/shared/src/androidMain/kotlin/com/prof18/feedflow/domain/opml/OpmlFeedHandler.kt index c44ae488..8b210354 100644 --- a/shared/src/androidMain/kotlin/com/prof18/feedflow/domain/opml/OpmlFeedHandler.kt +++ b/shared/src/androidMain/kotlin/com/prof18/feedflow/domain/opml/OpmlFeedHandler.kt @@ -16,7 +16,7 @@ import java.io.Reader internal actual class OpmlFeedHandler( private val dispatcherProvider: DispatcherProvider, ) { - actual suspend fun importFeed(opmlInput: OpmlInput): List = + actual suspend fun generateFeedSources(opmlInput: OpmlInput): List = withContext(dispatcherProvider.default) { val inputStream = opmlInput.inputStream val feedSources = mutableListOf() diff --git a/shared/src/androidUnitTest/kotlin/com/prof18/feedflow/OPMLFeedParserTest.kt b/shared/src/androidUnitTest/kotlin/com/prof18/feedflow/OPMLFeedParserTest.kt index 12b3cb47..f14bf764 100644 --- a/shared/src/androidUnitTest/kotlin/com/prof18/feedflow/OPMLFeedParserTest.kt +++ b/shared/src/androidUnitTest/kotlin/com/prof18/feedflow/OPMLFeedParserTest.kt @@ -24,13 +24,13 @@ class OPMLFeedParserTest { @Test fun `The number of feeds are correct`() = runTest { - val feedSources = parser.importFeed(opmlInput) + val feedSources = parser.generateFeedSources(opmlInput) assertTrue(feedSources.size == 6) } @Test fun `The number of feed in category are correct`() = runTest { - val feedSources = parser.importFeed(opmlInput) + val feedSources = parser.generateFeedSources(opmlInput) val techFeeds = feedSources.filter { it.category == "Tech" } val basketFeeds = feedSources.filter { it.category == "Basket" } @@ -43,7 +43,7 @@ class OPMLFeedParserTest { @Test fun `The feeds are parsed correctly`() = runTest { - val feedSources = parser.importFeed(opmlInput) + val feedSources = parser.generateFeedSources(opmlInput) assertEquals("Hacker News", feedSources[0].title) assertEquals("https://news.ycombinator.com/rss", feedSources[0].url) @@ -75,7 +75,7 @@ class OPMLFeedParserTest { val opmlInput = OpmlInput( inputStream = opmlWithText.byteInputStream(), ) - val feedSources = parser.importFeed(opmlInput) + val feedSources = parser.generateFeedSources(opmlInput) assertTrue(feedSources.isNotEmpty()) } } diff --git a/shared/src/commonMain/kotlin/com/prof18/feedflow/di/Koin.kt b/shared/src/commonMain/kotlin/com/prof18/feedflow/di/Koin.kt index d9548ef0..05e66704 100644 --- a/shared/src/commonMain/kotlin/com/prof18/feedflow/di/Koin.kt +++ b/shared/src/commonMain/kotlin/com/prof18/feedflow/di/Koin.kt @@ -1,6 +1,5 @@ package com.prof18.feedflow.di -import co.touchlab.kermit.ExperimentalKermitApi import co.touchlab.kermit.Logger import co.touchlab.kermit.StaticConfig import co.touchlab.kermit.platformLogWriter @@ -38,7 +37,6 @@ fun initKoin( } } -@OptIn(ExperimentalKermitApi::class) private fun getLoggingModule(appEnvironment: AppEnvironment): Module = module { val loggers = mutableListOf(platformLogWriter()) @@ -79,6 +77,8 @@ private val coreModule = module { databaseHelper = get(), opmlFeedHandler = get(), settingsHelper = get(), + rssParser = get(), + logger = getWith("FeedManagerRepositoryImpl"), ) } diff --git a/shared/src/commonMain/kotlin/com/prof18/feedflow/domain/feed/manager/FeedManagerRepository.kt b/shared/src/commonMain/kotlin/com/prof18/feedflow/domain/feed/manager/FeedManagerRepository.kt index f2a77c80..beeaa479 100644 --- a/shared/src/commonMain/kotlin/com/prof18/feedflow/domain/feed/manager/FeedManagerRepository.kt +++ b/shared/src/commonMain/kotlin/com/prof18/feedflow/domain/feed/manager/FeedManagerRepository.kt @@ -10,6 +10,7 @@ interface FeedManagerRepository { suspend fun addFeedsFromFile(opmlInput: OpmlInput) suspend fun getFeeds(): Flow> suspend fun addFeed(url: String, name: String) + suspend fun checkIfValidRss(url: String): Boolean suspend fun exportFeedsAsOpml(opmlOutput: OpmlOutput) suspend fun deleteFeed(feedSource: FeedSource) fun getFavouriteBrowserId(): String? diff --git a/shared/src/commonMain/kotlin/com/prof18/feedflow/domain/feed/manager/FeedManagerRepositoryImpl.kt b/shared/src/commonMain/kotlin/com/prof18/feedflow/domain/feed/manager/FeedManagerRepositoryImpl.kt index 69bae3c3..7be2f76f 100644 --- a/shared/src/commonMain/kotlin/com/prof18/feedflow/domain/feed/manager/FeedManagerRepositoryImpl.kt +++ b/shared/src/commonMain/kotlin/com/prof18/feedflow/domain/feed/manager/FeedManagerRepositoryImpl.kt @@ -1,5 +1,6 @@ package com.prof18.feedflow.domain.feed.manager +import co.touchlab.kermit.Logger import com.prof18.feedflow.core.model.FeedSource import com.prof18.feedflow.data.DatabaseHelper import com.prof18.feedflow.data.SettingsHelper @@ -8,17 +9,22 @@ import com.prof18.feedflow.domain.model.ParsedFeedSource import com.prof18.feedflow.domain.opml.OpmlFeedHandler import com.prof18.feedflow.domain.opml.OpmlInput import com.prof18.feedflow.domain.opml.OpmlOutput +import com.prof18.rssparser.RssParser import kotlinx.coroutines.flow.Flow internal class FeedManagerRepositoryImpl( private val databaseHelper: DatabaseHelper, private val opmlFeedHandler: OpmlFeedHandler, private val settingsHelper: SettingsHelper, + private val rssParser: RssParser, + private val logger: Logger, ) : FeedManagerRepository { override suspend fun addFeedsFromFile(opmlInput: OpmlInput) { - val feeds = opmlFeedHandler.importFeed(opmlInput) + val feeds = opmlFeedHandler.generateFeedSources(opmlInput) val categories = feeds.mapNotNull { it.category }.distinct() + // TODO: check url and returns + databaseHelper.insertCategories(categories) databaseHelper.insertFeedSource(feeds) } @@ -54,4 +60,15 @@ internal class FeedManagerRepositoryImpl( override fun setFavouriteBrowser(browser: Browser) { settingsHelper.saveFavouriteBrowserId(browser.id) } + + @Suppress("TooGenericExceptionCaught") + override suspend fun checkIfValidRss(url: String): Boolean { + return try { + rssParser.getRssChannel(url) + true + } catch (e: Throwable) { + logger.d { "Wrong url input: $e" } + false + } + } } diff --git a/shared/src/commonMain/kotlin/com/prof18/feedflow/domain/opml/OpmlFeedHandler.kt b/shared/src/commonMain/kotlin/com/prof18/feedflow/domain/opml/OpmlFeedHandler.kt index 1206c0a7..d6fad373 100644 --- a/shared/src/commonMain/kotlin/com/prof18/feedflow/domain/opml/OpmlFeedHandler.kt +++ b/shared/src/commonMain/kotlin/com/prof18/feedflow/domain/opml/OpmlFeedHandler.kt @@ -4,6 +4,6 @@ import com.prof18.feedflow.core.model.FeedSource import com.prof18.feedflow.domain.model.ParsedFeedSource internal expect class OpmlFeedHandler { - suspend fun importFeed(opmlInput: OpmlInput): List + suspend fun generateFeedSources(opmlInput: OpmlInput): List suspend fun exportFeed(opmlOutput: OpmlOutput, feedSources: List) } diff --git a/shared/src/commonMain/kotlin/com/prof18/feedflow/presentation/AddFeedViewModel.kt b/shared/src/commonMain/kotlin/com/prof18/feedflow/presentation/AddFeedViewModel.kt index c2b8cf8c..0fd482f0 100644 --- a/shared/src/commonMain/kotlin/com/prof18/feedflow/presentation/AddFeedViewModel.kt +++ b/shared/src/commonMain/kotlin/com/prof18/feedflow/presentation/AddFeedViewModel.kt @@ -17,12 +17,17 @@ class AddFeedViewModel( private var feedUrl: String = "" private val isAddDoneMutableState: MutableStateFlow = MutableStateFlow(false) + private val isInvalidRssFeedMutableState: MutableStateFlow = MutableStateFlow(false) @NativeCoroutinesState val isAddDoneState = isAddDoneMutableState.asStateFlow() + @NativeCoroutinesState + val isInvalidRssFeed = isInvalidRssFeedMutableState.asStateFlow() + fun updateFeedUrlTextFieldValue(feedUrlTextFieldValue: String) { feedUrl = feedUrlTextFieldValue + isInvalidRssFeedMutableState.update { false } } fun updateFeedNameTextFieldValue(feedNameTextFieldValue: String) { @@ -32,12 +37,17 @@ class AddFeedViewModel( fun addFeed() { scope.launch { if (feedUrl.isNotEmpty() && feedName.isNotEmpty()) { - feedManagerRepository.addFeed( - url = feedUrl, - name = feedName, - ) - isAddDoneMutableState.update { true } - feedRetrieverRepository.fetchFeeds(updateLoadingInfo = false) + val isValidRss = feedManagerRepository.checkIfValidRss(feedUrl) + if (isValidRss) { + feedManagerRepository.addFeed( + url = feedUrl, + name = feedName, + ) + isAddDoneMutableState.update { true } + feedRetrieverRepository.fetchFeeds(updateLoadingInfo = false) + } else { + isInvalidRssFeedMutableState.update { true } + } } } } diff --git a/shared/src/desktopMain/kotlin/com/prof18/feedflow/domain/opml/OpmlFeedHandler.kt b/shared/src/desktopMain/kotlin/com/prof18/feedflow/domain/opml/OpmlFeedHandler.kt index 9df43b6a..2345287e 100644 --- a/shared/src/desktopMain/kotlin/com/prof18/feedflow/domain/opml/OpmlFeedHandler.kt +++ b/shared/src/desktopMain/kotlin/com/prof18/feedflow/domain/opml/OpmlFeedHandler.kt @@ -16,14 +16,15 @@ import javax.xml.stream.XMLOutputFactory internal actual class OpmlFeedHandler( private val dispatcherProvider: DispatcherProvider, ) { - actual suspend fun importFeed(opmlInput: OpmlInput): List = withContext(dispatcherProvider.io) { - val feed = opmlInput.file.readText() - val parser = SAXParserFactory.newInstance().newSAXParser() - val handler = SaxFeedHandler() - parser.parse(InputSource(StringReader(feed)), handler) - - return@withContext handler.getFeedSource() - } + actual suspend fun generateFeedSources(opmlInput: OpmlInput): List = + withContext(dispatcherProvider.io) { + val feed = opmlInput.file.readText() + val parser = SAXParserFactory.newInstance().newSAXParser() + val handler = SaxFeedHandler() + parser.parse(InputSource(StringReader(feed)), handler) + + return@withContext handler.getFeedSource() + } actual suspend fun exportFeed( opmlOutput: OpmlOutput, diff --git a/shared/src/desktopTest/kotlin/com/prof18/feedflow/domain/opml/OPMLFeedParserTest.kt b/shared/src/desktopTest/kotlin/com/prof18/feedflow/domain/opml/OPMLFeedParserTest.kt index 7143c8fe..41b1318a 100644 --- a/shared/src/desktopTest/kotlin/com/prof18/feedflow/domain/opml/OPMLFeedParserTest.kt +++ b/shared/src/desktopTest/kotlin/com/prof18/feedflow/domain/opml/OPMLFeedParserTest.kt @@ -28,13 +28,13 @@ class OPMLFeedParserTest { @Test fun `The number of feeds are correct`() = runTest { - val feedSources = parser.importFeed(opmlInput) + val feedSources = parser.generateFeedSources(opmlInput) assertTrue(feedSources.size == 6) } @Test fun `The number of feed in category are correct`() = runTest { - val feedSources = parser.importFeed(opmlInput) + val feedSources = parser.generateFeedSources(opmlInput) val techFeeds = feedSources.filter { it.category == "Tech" } val basketFeeds = feedSources.filter { it.category == "Basket" } @@ -47,7 +47,7 @@ class OPMLFeedParserTest { @Test fun `The feeds are parsed correctly`() = runTest { - val feedSources = parser.importFeed(opmlInput) + val feedSources = parser.generateFeedSources(opmlInput) assertEquals("Hacker News", feedSources[0].title) assertEquals("https://news.ycombinator.com/rss", feedSources[0].url) @@ -85,7 +85,7 @@ class OPMLFeedParserTest { file = file, ) - val feedSources = parser.importFeed(opmlInput) + val feedSources = parser.generateFeedSources(opmlInput) assertTrue(feedSources.isNotEmpty()) } } diff --git a/shared/src/iosMain/kotlin/com/prof18/feedflow/domain/opml/OpmlFeedHandler.kt b/shared/src/iosMain/kotlin/com/prof18/feedflow/domain/opml/OpmlFeedHandler.kt index 5873c6f1..e79e8035 100644 --- a/shared/src/iosMain/kotlin/com/prof18/feedflow/domain/opml/OpmlFeedHandler.kt +++ b/shared/src/iosMain/kotlin/com/prof18/feedflow/domain/opml/OpmlFeedHandler.kt @@ -21,7 +21,7 @@ import kotlin.coroutines.suspendCoroutine internal actual class OpmlFeedHandler( private val dispatcherProvider: DispatcherProvider, ) { - actual suspend fun importFeed(opmlInput: OpmlInput): List = + actual suspend fun generateFeedSources(opmlInput: OpmlInput): List = withContext(dispatcherProvider.default) { suspendCoroutine { continuation -> NSXMLParser(opmlInput.opmlData).apply { diff --git a/shared/src/iosTest/kotlin/com/prof18/feedflow/domain/opml/OPMLFeedParserTest.kt b/shared/src/iosTest/kotlin/com/prof18/feedflow/domain/opml/OPMLFeedParserTest.kt index 40e6f277..231af8cc 100644 --- a/shared/src/iosTest/kotlin/com/prof18/feedflow/domain/opml/OPMLFeedParserTest.kt +++ b/shared/src/iosTest/kotlin/com/prof18/feedflow/domain/opml/OPMLFeedParserTest.kt @@ -24,13 +24,13 @@ class OPMLFeedParserTest { @Test fun `The number of feeds are correct`() = runTest { - val feedSources = parser.importFeed(opmlInput) + val feedSources = parser.generateFeedSources(opmlInput) assertTrue(feedSources.size == 6) } @Test fun `The number of feed in category are correct`() = runTest { - val feedSources = parser.importFeed(opmlInput) + val feedSources = parser.generateFeedSources(opmlInput) val techFeeds = feedSources.filter { it.category == "Tech" } val basketFeeds = feedSources.filter { it.category == "Basket" } @@ -43,7 +43,7 @@ class OPMLFeedParserTest { @Test fun `The feeds are parsed correctly`() = runTest { - val feedSources = parser.importFeed(opmlInput) + val feedSources = parser.generateFeedSources(opmlInput) assertEquals("Hacker News", feedSources[0].title) assertEquals("https://news.ycombinator.com/rss", feedSources[0].url) @@ -76,7 +76,7 @@ class OPMLFeedParserTest { opmlData = (opmlWithText as NSString).dataUsingEncoding(NSUTF8StringEncoding) ?: NSData(), ) - val feedSources = parser.importFeed(opmlInput) + val feedSources = parser.generateFeedSources(opmlInput) assertTrue(feedSources.isNotEmpty()) } } diff --git a/sharedUI/src/commonMain/kotlin/com/prof18/feedflow/ui/addfeed/AddFeedComponents.kt b/sharedUI/src/commonMain/kotlin/com/prof18/feedflow/ui/addfeed/AddFeedComponents.kt index 344adfb8..13943d52 100644 --- a/sharedUI/src/commonMain/kotlin/com/prof18/feedflow/ui/addfeed/AddFeedComponents.kt +++ b/sharedUI/src/commonMain/kotlin/com/prof18/feedflow/ui/addfeed/AddFeedComponents.kt @@ -26,6 +26,7 @@ fun AddFeedsContent( paddingValues: PaddingValues, feedName: String, feedUrl: String, + isInvalidUrl: Boolean, onFeedNameUpdated: (String) -> Unit, onFeedUrlUpdated: (String) -> Unit, addFeed: () -> Unit, @@ -47,6 +48,7 @@ fun AddFeedsContent( .padding(top = Spacing.regular) .fillMaxWidth(), feedUrl = feedUrl, + showError = isInvalidUrl, onFeedUrlUpdated = onFeedUrlUpdated, ) @@ -95,6 +97,7 @@ private fun FeedNameTextField( private fun FeedUrlTextField( modifier: Modifier = Modifier, feedUrl: String, + showError: Boolean, onFeedUrlUpdated: (String) -> Unit, ) { TextField( @@ -102,6 +105,14 @@ private fun FeedUrlTextField( label = { Text(text = stringResource(resource = MR.strings.feed_url)) }, + isError = showError, + supportingText = if (showError) { + { + Text(stringResource(MR.strings.invalid_rss_url)) + } + } else { + null + }, keyboardOptions = KeyboardOptions( imeAction = ImeAction.Done, keyboardType = KeyboardType.Uri,