From 1978ac770e6a33f5fbaf319a857c94d0e67de1d2 Mon Sep 17 00:00:00 2001 From: Josiah Campbell <9521010+jocmp@users.noreply.github.com> Date: Mon, 27 Nov 2023 17:00:41 -0600 Subject: [PATCH] Add feed modal --- .../main/java/com/jocmp/basilreader/ui/App.kt | 5 +- .../ui/articles/AccountViewModel.kt | 23 +-- .../basilreader/ui/articles/AddFeedDialog.kt | 32 ----- .../basilreader/ui/articles/AddFeedScreen.kt | 21 +++ .../basilreader/ui/articles/AddFeedView.kt | 135 +++++++++++------- .../ui/articles/ArticleNavigation.kt | 36 +++-- .../basilreader/ui/articles/ArticleScreen.kt | 13 +- .../jocmp/basilreader/ui/articles/FeedList.kt | 16 +-- .../basilreader/ui/components/TextField.kt | 18 ++- .../ui/fixtures/FeedPreviewFixture.kt | 4 +- .../ui/fixtures/FolderPreviewFixture.kt | 8 +- app/src/main/res/values/strings.xml | 8 +- .../src/main/java/com/jocmp/basil/Account.kt | 31 +++- basil/src/main/java/com/jocmp/basil/Feed.kt | 20 ++- .../java/com/jocmp/basil/FeedFormEntry.kt | 7 + basil/src/main/java/com/jocmp/basil/Folder.kt | 15 +- .../jocmp/basil/extensions/FeedOutlineExt.kt | 1 + .../basil/extensions/FolderOutlineExt.kt | 6 +- .../test/java/com/jocmp/basil/AccountTest.kt | 95 +++++++++++- .../test/java/com/jocmp/basil/OPMLFileTest.kt | 24 ++-- 20 files changed, 355 insertions(+), 163 deletions(-) delete mode 100644 app/src/main/java/com/jocmp/basilreader/ui/articles/AddFeedDialog.kt create mode 100644 app/src/main/java/com/jocmp/basilreader/ui/articles/AddFeedScreen.kt create mode 100644 basil/src/main/java/com/jocmp/basil/FeedFormEntry.kt diff --git a/app/src/main/java/com/jocmp/basilreader/ui/App.kt b/app/src/main/java/com/jocmp/basilreader/ui/App.kt index cf643872..84e05d71 100644 --- a/app/src/main/java/com/jocmp/basilreader/ui/App.kt +++ b/app/src/main/java/com/jocmp/basilreader/ui/App.kt @@ -34,7 +34,10 @@ fun App() { navController.navigateToArticles(account.id) } ) - articleGraph(defaultAccountID = defaultAccountID) + articleGraph( + navController = navController, + defaultAccountID = defaultAccountID + ) } } } diff --git a/app/src/main/java/com/jocmp/basilreader/ui/articles/AccountViewModel.kt b/app/src/main/java/com/jocmp/basilreader/ui/articles/AccountViewModel.kt index 45f645c8..5729f4c8 100644 --- a/app/src/main/java/com/jocmp/basilreader/ui/articles/AccountViewModel.kt +++ b/app/src/main/java/com/jocmp/basilreader/ui/articles/AccountViewModel.kt @@ -1,17 +1,14 @@ package com.jocmp.basilreader.ui.articles import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import androidx.compose.runtime.toMutableStateList +import androidx.compose.runtime.neverEqualPolicy import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.jocmp.basil.Account import com.jocmp.basil.AccountManager -import com.jocmp.basil.Feed -import com.jocmp.basil.Folder +import com.jocmp.basil.FeedFormEntry import kotlinx.coroutines.launch -import java.util.UUID class AccountViewModel( savedStateHandle: SavedStateHandle, @@ -19,18 +16,22 @@ class AccountViewModel( ) : ViewModel() { private val args = ArticleArgs(savedStateHandle) - private val accountState = mutableStateOf(accountManager.findByID(args.accountId)) - private val account: Account + private val accountState = mutableStateOf( + accountManager.findByID(args.accountID), + policy = neverEqualPolicy() + ) + + val account: Account get() = accountState.value val feeds = account.feeds.toList() val folders = account.folders.toList() - fun addFeed(url: String) { + fun addFeed(entry: FeedFormEntry, onSuccess: () -> Unit) { viewModelScope.launch { - val nextAccount = accountState.value.copy() - nextAccount.addFeed(url = url) - accountState.value = nextAccount + account.addFeed(entry) + accountState.value = account + onSuccess() } } } diff --git a/app/src/main/java/com/jocmp/basilreader/ui/articles/AddFeedDialog.kt b/app/src/main/java/com/jocmp/basilreader/ui/articles/AddFeedDialog.kt deleted file mode 100644 index e5355a5c..00000000 --- a/app/src/main/java/com/jocmp/basilreader/ui/articles/AddFeedDialog.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.jocmp.basilreader.ui.articles - -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Card -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog -import com.jocmp.basil.Folder - -@Composable -fun AddFeedDialog( - folders: List, - onDismissRequest: () -> Unit, -) { - Dialog(onDismissRequest = onDismissRequest) { - Card( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - shape = RoundedCornerShape(16.dp), - ) { - AddFeedView( - folders = folders, - onSubmit = {} - ) - } - } -} diff --git a/app/src/main/java/com/jocmp/basilreader/ui/articles/AddFeedScreen.kt b/app/src/main/java/com/jocmp/basilreader/ui/articles/AddFeedScreen.kt new file mode 100644 index 00000000..5d5b9afe --- /dev/null +++ b/app/src/main/java/com/jocmp/basilreader/ui/articles/AddFeedScreen.kt @@ -0,0 +1,21 @@ +package com.jocmp.basilreader.ui.articles + +import androidx.compose.runtime.Composable +import org.koin.androidx.compose.koinViewModel + +@Composable +fun AddFeedScreen( + viewModel: AccountViewModel = koinViewModel(), + onSubmit: (accountID: String) -> Unit, + onCancel: () -> Unit +) { + AddFeedView( + folders = viewModel.folders, + onSubmit = { entry -> + viewModel.addFeed(entry) { + onSubmit(viewModel.account.id) + } + }, + onCancel = onCancel + ) +} diff --git a/app/src/main/java/com/jocmp/basilreader/ui/articles/AddFeedView.kt b/app/src/main/java/com/jocmp/basilreader/ui/articles/AddFeedView.kt index 3d398f7e..fdb35aa2 100644 --- a/app/src/main/java/com/jocmp/basilreader/ui/articles/AddFeedView.kt +++ b/app/src/main/java/com/jocmp/basilreader/ui/articles/AddFeedView.kt @@ -1,92 +1,127 @@ package com.jocmp.basilreader.ui.articles +import androidx.compose.ui.Alignment +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.Switch import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.toMutableStateMap import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview -import com.jocmp.basil.Feed +import androidx.compose.ui.unit.dp +import com.jocmp.basil.FeedFormEntry import com.jocmp.basil.Folder +import com.jocmp.basilreader.R import com.jocmp.basilreader.ui.components.TextField -import kotlin.math.exp @Composable fun AddFeedView( folders: List, - onSubmit: (feed: Feed) -> Unit, + onSubmit: (feed: FeedFormEntry) -> Unit, + onCancel: () -> Unit ) { - val (selectedFolder, selectFolder) = remember { - mutableStateOf(null) - } - val (url, setURL) = remember { mutableStateOf("") } val (name, setName) = remember { mutableStateOf("") } + val (addedFolder, setAddedFolder) = remember { mutableStateOf("") } + val switchFolders = remember { + folders.map { it.title to false }.toMutableStateMap() + } + + fun submitFeed() { + val existingFolderNames = switchFolders.filter { it.value }.keys + val folderNames = collectFolders(existingFolderNames, addedFolder) + + onSubmit( + FeedFormEntry( + url = url, + name = name, + folderTitles = folderNames + ) + ) + } Column { TextField( value = url, onValueChange = setURL, + label = { + Text(stringResource(id = R.string.add_feed_url_title)) + }, + supportingText = { + Text(stringResource(R.string.required_placeholder)) + } ) TextField( value = name, onValueChange = setName, + label = { + Text(stringResource(id = R.string.add_feed_name_title)) + } ) -// if (folders.isNotEmpty()) { -// FolderMenuBox( -// selected = selectedFolder, -// folders = folders, -// onFolderSelect = { selectFolder(it) } -// ) -// } - } -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun FolderMenuBox( - selected: Folder?, - folders: List, - onFolderSelect: (folder: Folder) -> Unit, -) { - val (expanded, setExpanded) = mutableStateOf(false) - - ExposedDropdownMenuBox( - expanded = expanded, - onExpandedChange = { setExpanded(!expanded) } - ) { TextField( - readOnly = true, - value = selected?.title ?: "", - onValueChange = {}, - modifier = Modifier.menuAnchor() + value = addedFolder, + onValueChange = setAddedFolder, + placeholder = { + Text(stringResource(id = R.string.add_feed_new_folder_title)) + } ) - ExposedDropdownMenu( - expanded = true, - onDismissRequest = {} + Column(modifier = Modifier.padding(horizontal = 16.dp)) { + switchFolders.forEach { (folderTitle, checked) -> + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + Text(folderTitle) + Switch( + checked = checked, + onCheckedChange = { value -> switchFolders[folderTitle] = value } + ) + } + } + } + Row( + horizontalArrangement = Arrangement.End, + modifier = Modifier.fillMaxWidth() ) { - folders.forEach { folder -> - DropdownMenuItem( - text = { Text(folder.title) }, - onClick = { - onFolderSelect(folder) - } - ) + TextButton(onClick = onCancel) { + Text(stringResource(R.string.add_feed_cancel)) + } + Button(onClick = { submitFeed() }) { + Text(stringResource(R.string.add_feed_submit)) } } } } +fun collectFolders( + existingFolders: Set, + addedFolder: String +): List { + val folderNames = existingFolders.toMutableList() + + if (addedFolder.isNotBlank()) { + folderNames.add(addedFolder) + } + + return folderNames +} + @Preview @Composable fun AddFeedViewPreview() { AddFeedView( - folders = emptyList(), - onSubmit = {} + folders = listOf(Folder(title = "Tech")), + onSubmit = {}, + onCancel = {} ) } diff --git a/app/src/main/java/com/jocmp/basilreader/ui/articles/ArticleNavigation.kt b/app/src/main/java/com/jocmp/basilreader/ui/articles/ArticleNavigation.kt index 73be9ac0..1c468bfc 100644 --- a/app/src/main/java/com/jocmp/basilreader/ui/articles/ArticleNavigation.kt +++ b/app/src/main/java/com/jocmp/basilreader/ui/articles/ArticleNavigation.kt @@ -3,28 +3,46 @@ package com.jocmp.basilreader.ui.articles import androidx.lifecycle.SavedStateHandle import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavType import androidx.navigation.compose.composable -import androidx.navigation.compose.dialog import androidx.navigation.navArgument -import com.jocmp.basil.AccountManager -import org.koin.compose.koinInject private const val ACCOUNT_ID_KEY = "account_id" -internal class ArticleArgs(val accountId: String) { +internal class ArticleArgs(val accountID: String) { constructor(savedStateHandle: SavedStateHandle) : this(checkNotNull(savedStateHandle[ACCOUNT_ID_KEY]) as String) } -fun NavController.navigateToArticles(accountId: String) = - navigate("articles?account_id=${accountId}") +fun NavController.navigateToArticles(accountID: String) = + navigate("articles?account_id=${accountID}") -fun NavGraphBuilder.articleGraph(defaultAccountID: String) { +fun NavController.navigateToAddFeed(accountID: String) = + navigate("feeds/new?account_id=${accountID}") + +fun NavGraphBuilder.articleGraph( + navController: NavController, + defaultAccountID: String, +) { composable( route = "articles?account_id={${ACCOUNT_ID_KEY}}", arguments = listOf(navArgument(ACCOUNT_ID_KEY) { defaultValue = defaultAccountID }) ) { - ArticleScreen() + ArticleScreen( + onNewFeedNavigate = { accountID -> + navController.navigateToAddFeed(accountID) + } + ) + } + composable( + route = "feeds/new?account_id={${ACCOUNT_ID_KEY}}", + ) { + AddFeedScreen( + onCancel = { + navController.popBackStack() + }, + onSubmit = { accountID -> + navController.navigateToArticles(accountID) + } + ) } } diff --git a/app/src/main/java/com/jocmp/basilreader/ui/articles/ArticleScreen.kt b/app/src/main/java/com/jocmp/basilreader/ui/articles/ArticleScreen.kt index 9b83072f..aa40c5c1 100644 --- a/app/src/main/java/com/jocmp/basilreader/ui/articles/ArticleScreen.kt +++ b/app/src/main/java/com/jocmp/basilreader/ui/articles/ArticleScreen.kt @@ -1,14 +1,19 @@ package com.jocmp.basilreader.ui.articles +import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable import org.koin.androidx.compose.koinViewModel @Composable fun ArticleScreen( viewModel: AccountViewModel = koinViewModel(), + onNewFeedNavigate: (accountID: String) -> Unit, ) { - FeedList( - folders = viewModel.folders, - feeds = viewModel.feeds - ) + Column { + FeedList( + folders = viewModel.folders, + feeds = viewModel.feeds, + onNewFeedNavigate = { onNewFeedNavigate(viewModel.account.id) } + ) + } } diff --git a/app/src/main/java/com/jocmp/basilreader/ui/articles/FeedList.kt b/app/src/main/java/com/jocmp/basilreader/ui/articles/FeedList.kt index 21fc3808..6579fd29 100644 --- a/app/src/main/java/com/jocmp/basilreader/ui/articles/FeedList.kt +++ b/app/src/main/java/com/jocmp/basilreader/ui/articles/FeedList.kt @@ -3,11 +3,8 @@ package com.jocmp.basilreader.ui.articles import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.material3.Button -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.ui.tooling.preview.Preview import com.jocmp.basil.Feed import com.jocmp.basil.Folder @@ -18,12 +15,11 @@ import com.jocmp.basilreader.ui.fixtures.FolderPreviewFixture fun FeedList( folders: List = emptyList(), feeds: List = emptyList(), + onNewFeedNavigate: () -> Unit, ) { - val (isOpen, setOpen) = remember { mutableStateOf(false) } - Column { Row { - Button(onClick = { setOpen(true) }) { + Button(onClick = onNewFeedNavigate) { Text("+ Feed") } } @@ -37,13 +33,6 @@ fun FeedList( Text(feed.name) } } - - if (isOpen) { - AddFeedDialog( - folders = folders, - onDismissRequest = { setOpen(false) } - ) - } } @Preview @@ -55,5 +44,6 @@ fun FeedListPreview() { FeedList( folders = folders, feeds = feeds, + onNewFeedNavigate = {} ) } diff --git a/app/src/main/java/com/jocmp/basilreader/ui/components/TextField.kt b/app/src/main/java/com/jocmp/basilreader/ui/components/TextField.kt index 183ee80d..faaef069 100644 --- a/app/src/main/java/com/jocmp/basilreader/ui/components/TextField.kt +++ b/app/src/main/java/com/jocmp/basilreader/ui/components/TextField.kt @@ -1,21 +1,33 @@ package com.jocmp.basilreader.ui.components +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import androidx.compose.material3.TextField as MaterialTextField @Composable fun TextField( value: String, modifier: Modifier = Modifier, onValueChange: (String) -> Unit, - readOnly: Boolean = false + readOnly: Boolean = false, + label: @Composable (() -> Unit)? = null, + placeholder: @Composable (() -> Unit)? = null, + supportingText: @Composable (() -> Unit)? = null ) { MaterialTextField( value = value, onValueChange = onValueChange, - modifier = modifier, - readOnly = readOnly + modifier = modifier.then( + Modifier.fillMaxWidth() + .padding(bottom = 8.dp) + ), + readOnly = readOnly, + label = label, + placeholder = placeholder, + supportingText = supportingText ) } diff --git a/app/src/main/java/com/jocmp/basilreader/ui/fixtures/FeedPreviewFixture.kt b/app/src/main/java/com/jocmp/basilreader/ui/fixtures/FeedPreviewFixture.kt index c82a19dc..dfca5124 100644 --- a/app/src/main/java/com/jocmp/basilreader/ui/fixtures/FeedPreviewFixture.kt +++ b/app/src/main/java/com/jocmp/basilreader/ui/fixtures/FeedPreviewFixture.kt @@ -9,8 +9,8 @@ class FeedPreviewFixture : PreviewParameterProvider { private fun feeds(): Sequence { return sequenceOf( - Feed(id = UUID.randomUUID().toString(), name = "GamersNexus"), - Feed(id = UUID.randomUUID().toString(), name = "9to5Google") + Feed(id = UUID.randomUUID().toString(), name = "GamersNexus", feedURL = ""), + Feed(id = UUID.randomUUID().toString(), name = "9to5Google", feedURL = "") ) } } diff --git a/app/src/main/java/com/jocmp/basilreader/ui/fixtures/FolderPreviewFixture.kt b/app/src/main/java/com/jocmp/basilreader/ui/fixtures/FolderPreviewFixture.kt index 3bd5a6d0..c548c755 100644 --- a/app/src/main/java/com/jocmp/basilreader/ui/fixtures/FolderPreviewFixture.kt +++ b/app/src/main/java/com/jocmp/basilreader/ui/fixtures/FolderPreviewFixture.kt @@ -14,15 +14,15 @@ class FolderPreviewFixture : PreviewParameterProvider { Folder( title = "Tech", feeds = mutableListOf( - Feed(id = UUID.randomUUID().toString(), name = "The Verge"), - Feed(id = UUID.randomUUID().toString(), name = "Ars Technica") + Feed(id = UUID.randomUUID().toString(), name = "The Verge", feedURL = ""), + Feed(id = UUID.randomUUID().toString(), name = "Ars Technica", feedURL = "") ) ), Folder( title = "Programming", feeds = mutableListOf( - Feed(id = UUID.randomUUID().toString(), name = "Android Weekly"), - Feed(id = UUID.randomUUID().toString(), name = "Ruby Weekly"), + Feed(id = UUID.randomUUID().toString(), name = "Android Weekly", feedURL = ""), + Feed(id = UUID.randomUUID().toString(), name = "Ruby Weekly", feedURL = ""), ) ) ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0b5d9e8c..c10d3b1c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,9 @@ Basil Reader - \ No newline at end of file + URL + Name + New Folder + Add + Cancel + Required + diff --git a/basil/src/main/java/com/jocmp/basil/Account.kt b/basil/src/main/java/com/jocmp/basil/Account.kt index 640f1334..59508d99 100644 --- a/basil/src/main/java/com/jocmp/basil/Account.kt +++ b/basil/src/main/java/com/jocmp/basil/Account.kt @@ -38,11 +38,28 @@ data class Account( return folder } - suspend fun addFeed(url: String = ""): Feed { - val randomID = UUID.randomUUID().toString() - val feed = Feed(id = randomID, name = randomID) - - feeds.add(feed) + suspend fun addFeed(entry: FeedFormEntry): Feed { + val feed = Feed( + id = UUID.randomUUID().toString(), + name = entry.name, + feedURL = entry.url + ) + + if (entry.folderTitles.isEmpty()) { + feeds.add(feed) + } else { + entry.folderTitles.forEach { folderTitle -> + val folder = folders.find { folder -> folder.title == folderTitle } ?: Folder(title = folderTitle) + + folder.feeds.add(feed) + + if (folders.contains(folder)) { + folders.remove(folder) + } + + folders.add(folder) + } + } saveOPMLFile() @@ -66,11 +83,11 @@ data class Account( fun Account.asOPML(): String { var opml = "" - feeds.sorted().forEach { feed -> + feeds.sortedBy { it.name }.forEach { feed -> opml += feed.asOPML(indentLevel = 2) } - folders.sorted().forEach { folder -> + folders.sortedBy { it.title } .forEach { folder -> opml += folder.asOPML(indentLevel = 2) } diff --git a/basil/src/main/java/com/jocmp/basil/Feed.kt b/basil/src/main/java/com/jocmp/basil/Feed.kt index 7c6cf8c7..5357b8d5 100644 --- a/basil/src/main/java/com/jocmp/basil/Feed.kt +++ b/basil/src/main/java/com/jocmp/basil/Feed.kt @@ -4,17 +4,23 @@ import com.jocmp.basil.extensions.prepending data class Feed( val id: String, - val name: String -): Comparable { - override fun compareTo(other: Feed): Int { - return when { - this.name != other.name -> this.name.compareTo(other.name) - else -> 0 + val name: String, + val feedURL: String, + val homepageURL: String = "" +) { + override fun equals(other: Any?): Boolean { + if (other is Feed) { + return id == other.id } + return super.equals(other) + } + + override fun hashCode(): Int { + return id.hashCode() } } fun Feed.asOPML(indentLevel: Int): String { - val opml = "\n" + val opml = "\n" return opml.prepending(tabCount = indentLevel) } diff --git a/basil/src/main/java/com/jocmp/basil/FeedFormEntry.kt b/basil/src/main/java/com/jocmp/basil/FeedFormEntry.kt new file mode 100644 index 00000000..c622de06 --- /dev/null +++ b/basil/src/main/java/com/jocmp/basil/FeedFormEntry.kt @@ -0,0 +1,7 @@ +package com.jocmp.basil + +data class FeedFormEntry( + val url: String, + val name: String, + val folderTitles: List +) diff --git a/basil/src/main/java/com/jocmp/basil/Folder.kt b/basil/src/main/java/com/jocmp/basil/Folder.kt index c0e6f8a5..38185c4d 100644 --- a/basil/src/main/java/com/jocmp/basil/Folder.kt +++ b/basil/src/main/java/com/jocmp/basil/Folder.kt @@ -6,12 +6,17 @@ import com.jocmp.basil.extensions.repeatTab data class Folder( val title: String, val feeds: MutableList = mutableListOf() -) : Comparable { - override fun compareTo(other: Folder): Int { - return when { - this.title != other.title -> this.title.compareTo(other.title) - else -> 0 +) { + override fun equals(other: Any?): Boolean { + if (other is Folder) { + return title == other.title } + return super.equals(other) + } + + override fun hashCode(): Int { + val result = title.hashCode() + return 31 * result } } diff --git a/basil/src/main/java/com/jocmp/basil/extensions/FeedOutlineExt.kt b/basil/src/main/java/com/jocmp/basil/extensions/FeedOutlineExt.kt index 906d40d0..f10294fd 100644 --- a/basil/src/main/java/com/jocmp/basil/extensions/FeedOutlineExt.kt +++ b/basil/src/main/java/com/jocmp/basil/extensions/FeedOutlineExt.kt @@ -8,5 +8,6 @@ internal val Outline.FeedOutline.asFeed: Feed return Feed( id = "", name = feed.title ?: "", + feedURL = feed.xmlUrl ?: "" ) } diff --git a/basil/src/main/java/com/jocmp/basil/extensions/FolderOutlineExt.kt b/basil/src/main/java/com/jocmp/basil/extensions/FolderOutlineExt.kt index 0a6ca6a8..e3676c6e 100644 --- a/basil/src/main/java/com/jocmp/basil/extensions/FolderOutlineExt.kt +++ b/basil/src/main/java/com/jocmp/basil/extensions/FolderOutlineExt.kt @@ -9,7 +9,11 @@ internal val Outline.FolderOutline.asFolder: Folder return Folder( title = folder.title ?: "", feeds = folder.feeds.map { feed -> - Feed(id = "", name = feed.title ?: "") + Feed( + id = "", + name = feed.title ?: "", + feedURL = feed.xmlUrl ?: "" + ) }.toMutableList() ) } diff --git a/basil/src/test/java/com/jocmp/basil/AccountTest.kt b/basil/src/test/java/com/jocmp/basil/AccountTest.kt index 418642f9..24e32ea2 100644 --- a/basil/src/test/java/com/jocmp/basil/AccountTest.kt +++ b/basil/src/test/java/com/jocmp/basil/AccountTest.kt @@ -4,6 +4,7 @@ import kotlinx.coroutines.runBlocking import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder +import java.util.UUID import kotlin.test.assertContains import kotlin.test.assertEquals @@ -28,7 +29,13 @@ class AccountTest { runBlocking { Account(id = accountID, path = accountPath).addFolder(title = "Test Title") - Account(id = accountID, path = accountPath).addFeed() + Account(id = accountID, path = accountPath).addFeed( + FeedFormEntry( + url = "https://theverge.com/rss.xml", + name = "The Verge", + folderTitles = listOf(), + ) + ) } val account = Account(id = accountID, path = accountPath) @@ -37,4 +44,90 @@ class AccountTest { assertEquals(expected = "Test Title", actual = accountTitle) assertEquals(expected = account.feeds.size, actual = 1) } + + @Test + fun addFeed_singleTopLevelFeed() { + val accountPath = folder.newFile().toURI() + val account = Account(id = "777", path = accountPath) + val entry = FeedFormEntry( + url = "https://www.theverge.com/rss/index.xml", + name = "The Verge", + folderTitles = listOf(), + ) + + runBlocking { account.addFeed(entry) } + + assertEquals(expected = account.feeds.size, actual = 1) + assertEquals(expected = account.folders.size, actual = 0) + + val feed = account.feeds.first() + assertEquals(expected = entry.name, actual = entry.name) + assertEquals(expected = entry.url, actual = feed.feedURL) + } + + @Test + fun addFeed_newFolder() { + val accountPath = folder.newFile().toURI() + val account = Account(id = "777", path = accountPath) + val entry = FeedFormEntry( + url = "https://www.theverge.com/rss/index.xml", + name = "The Verge", + folderTitles = listOf("Tech"), + ) + + runBlocking { account.addFeed(entry) } + + assertEquals(expected = account.feeds.size, actual = 0) + assertEquals(expected = account.folders.size, actual = 1) + + val feed = account.folders.first().feeds.first() + assertEquals(expected = entry.name, actual = entry.name) + assertEquals(expected = entry.url, actual = feed.feedURL) + } + + @Test + fun addFeed_existingFolders() { + val accountPath = folder.newFile().toURI() + val account = Account(id = "777", path = accountPath) + runBlocking { account.addFolder("Tech") } + + val entry = FeedFormEntry( + url = "https://www.theverge.com/rss/index.xml", + name = "The Verge", + folderTitles = listOf("Tech"), + ) + + runBlocking { account.addFeed(entry) } + + assertEquals(expected = account.feeds.size, actual = 0) + assertEquals(expected = account.folders.size, actual = 1) + + val feed = account.folders.first().feeds.first() + assertEquals(expected = entry.name, actual = feed.name) + assertEquals(expected = entry.url, actual = feed.feedURL) + } + + @Test + fun addFeed_multipleFolders() { + val accountPath = folder.newFile().toURI() + val account = Account(id = "777", path = accountPath) + runBlocking { account.addFolder("Tech") } + + val entry = FeedFormEntry( + url = "https://www.theverge.com/rss/index.xml", + name = "The Verge", + folderTitles = listOf("Tech", "Culture"), + ) + + runBlocking { account.addFeed(entry) } + + assertEquals(expected = account.feeds.size, actual = 0) + assertEquals(expected = account.folders.size, actual = 2) + + val techFeed = account.folders.first().feeds.first() + val cultureFeed = account.folders.first().feeds.first() + assertEquals(expected = entry.name, actual = techFeed.name) + assertEquals(expected = entry.url, actual = techFeed.feedURL) + assertEquals(techFeed, cultureFeed) + } } diff --git a/basil/src/test/java/com/jocmp/basil/OPMLFileTest.kt b/basil/src/test/java/com/jocmp/basil/OPMLFileTest.kt index d3e882dd..55ed4ec8 100644 --- a/basil/src/test/java/com/jocmp/basil/OPMLFileTest.kt +++ b/basil/src/test/java/com/jocmp/basil/OPMLFileTest.kt @@ -25,15 +25,15 @@ class OPMLFileTest { Folder( title = "Tech", feeds = mutableListOf( - Feed(id = UUID.randomUUID().toString(), name = "The Verge"), - Feed(id = UUID.randomUUID().toString(), name = "Ars Technica") + Feed(id = UUID.randomUUID().toString(), name = "The Verge", feedURL = "https://www.theverge.com/rss/index.xml"), + Feed(id = UUID.randomUUID().toString(), name = "Ars Technica", feedURL = "https://feeds.arstechnica.com/arstechnica/index") ) ), Folder( title = "Programming", feeds = mutableListOf( - Feed(id = UUID.randomUUID().toString(), name = "Android Weekly"), - Feed(id = UUID.randomUUID().toString(), name = "Ruby Weekly"), + Feed(id = UUID.randomUUID().toString(), name = "De Programmatica Ipsum", feedURL = "https://deprogrammaticaipsum.com/feed"), + Feed(id = UUID.randomUUID().toString(), name = "Ruby Weekly", feedURL = "https://cprss.s3.amazonaws.com/rubyweekly.com.xml"), ) ) ) @@ -41,8 +41,8 @@ class OPMLFileTest { account.feeds.addAll( listOf( - Feed(id = UUID.randomUUID().toString(), name = "GamersNexus"), - Feed(id = UUID.randomUUID().toString(), name = "9to5Google") + Feed(id = UUID.randomUUID().toString(), name = "GamersNexus", feedURL = "https://gamersnexus.net/rss.xml"), + Feed(id = UUID.randomUUID().toString(), name = "9to5Google", feedURL = "https://9to5google.com/feed") ) ) @@ -59,16 +59,16 @@ class OPMLFileTest { | Test Display Name | | - | - | + | + | | | - | - | + | + | | | - | - | + | + | | | |