Show something on the feed
This commit is contained in:
@@ -1,42 +1,101 @@
|
||||
package ac.aux.compose.ui.composable
|
||||
|
||||
import ac.aux.compose.ui.composable.widgets.LoadingDataIndicator
|
||||
import ac.aux.compose.ui.composable.widgets.feed.ListView
|
||||
import ac.aux.compose.ui.composable.widgets.feed.LoadedFeedView
|
||||
import ac.aux.compose.ui.theme.AuxTheme
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import ac.aux.compose.ui.view.model.FeedListViewModel
|
||||
import ac.aux.compose.ui.view.state.FeedListUIState
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Person
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.MediumTopAppBar
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import kotlin.time.Clock
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun FeedScreen(
|
||||
text: String
|
||||
initialFeedListUIState: FeedListUIState,
|
||||
onNavigateToEvent: (Event) -> Unit
|
||||
) {
|
||||
val feedListViewModel: FeedListViewModel = viewModel(
|
||||
factory = FeedListViewModel.factory(
|
||||
initialFeedListUIState = initialFeedListUIState,
|
||||
),
|
||||
)
|
||||
|
||||
Scaffold { innerPadding ->
|
||||
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState())
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier.nestedScroll(
|
||||
scrollBehavior.nestedScrollConnection
|
||||
),
|
||||
topBar = {
|
||||
MediumTopAppBar(
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
titleContentColor = MaterialTheme.colorScheme.primary
|
||||
),
|
||||
title = {
|
||||
Text(
|
||||
"Aux"
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Person,
|
||||
contentDescription = "Account"
|
||||
)
|
||||
}
|
||||
},
|
||||
scrollBehavior = scrollBehavior
|
||||
)
|
||||
|
||||
}
|
||||
) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier.padding(innerPadding).fillMaxSize()
|
||||
) {
|
||||
// Notes
|
||||
// Stories/Reels
|
||||
// Pictures
|
||||
// Private Messages (deferred to another app)
|
||||
// Notifications
|
||||
Column(
|
||||
modifier = Modifier.padding(20.dp).fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
"\"$text\" functionality coming soon",
|
||||
|
||||
)
|
||||
when(val feedListUIState = feedListViewModel.feedListUIState) {
|
||||
is FeedListUIState.Loading -> {
|
||||
LoadingDataIndicator()
|
||||
}
|
||||
FeedListUIState.Error -> {
|
||||
Text("Something went wrong")
|
||||
}
|
||||
is FeedListUIState.Loaded -> {
|
||||
LoadedFeedView(
|
||||
events = feedListUIState.events,
|
||||
onNavigateToEvent = onNavigateToEvent
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,7 +109,8 @@ private fun FeedScreenPreview() {
|
||||
modifier = Modifier.padding(20.dp)
|
||||
) {
|
||||
FeedScreen(
|
||||
"Feed"
|
||||
initialFeedListUIState = FeedListUIState.Loading,
|
||||
onNavigateToEvent = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,35 +3,110 @@ package ac.aux.compose.ui.composable
|
||||
import ac.aux.compose.ui.theme.AuxTheme
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.SkipNext
|
||||
import androidx.compose.material3.BottomAppBar
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ExtendedFloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun SocialPreconditionScreen(
|
||||
text: String
|
||||
onNavigateToSkipForNow: () -> Unit,
|
||||
onNavigateToInviteFriend: () -> Unit,
|
||||
onNavigateToViewInvites: () -> Unit,
|
||||
) {
|
||||
Scaffold { innerPadding ->
|
||||
Scaffold(
|
||||
bottomBar = {
|
||||
BottomAppBar(
|
||||
modifier = Modifier,
|
||||
actions = {
|
||||
|
||||
},
|
||||
floatingActionButton = {
|
||||
ExtendedFloatingActionButton(
|
||||
modifier = Modifier,
|
||||
icon = {
|
||||
Icon(
|
||||
Icons.Default.SkipNext,
|
||||
contentDescription = "Skip for now"
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Text("Skip for now")
|
||||
},
|
||||
onClick = {
|
||||
onNavigateToSkipForNow.invoke()
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier.padding(innerPadding).fillMaxSize()
|
||||
) {
|
||||
// Invite A Friend
|
||||
// Accept an invite
|
||||
Column(
|
||||
modifier = Modifier.padding(20.dp).fillMaxSize(),
|
||||
modifier = Modifier.fillMaxWidth().padding(20.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp)
|
||||
) {
|
||||
|
||||
Text(
|
||||
"\"$text\" functionality coming soon",
|
||||
"Who will you be passing the aux to?",
|
||||
textAlign = TextAlign.Center,
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
|
||||
Spacer(
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
|
||||
Text(
|
||||
"Tell friends to join you on Aux so your feed stays lively and fresh.",
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
onNavigateToInviteFriend.invoke()
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
"Invite a Friend"
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
|
||||
Text(
|
||||
"View and accept invites you may have received to stay connected with others.",
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
onNavigateToViewInvites.invoke()
|
||||
}
|
||||
) {
|
||||
Text("View invites")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,7 +120,9 @@ private fun SocialPreconditionScreenPreview() {
|
||||
modifier = Modifier.padding(20.dp)
|
||||
) {
|
||||
SocialPreconditionScreen(
|
||||
"Social Precondition"
|
||||
onNavigateToSkipForNow = {},
|
||||
onNavigateToInviteFriend = {},
|
||||
onNavigateToViewInvites = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package ac.aux.compose.ui.composable.navigation
|
||||
|
||||
import ac.aux.compose.ui.composable.CreateProfileScreen
|
||||
import ac.aux.compose.ui.composable.FeedScreen
|
||||
import ac.aux.compose.ui.composable.ImplementationPendingScreen
|
||||
import ac.aux.compose.ui.composable.LandingScreen
|
||||
import ac.aux.compose.ui.composable.SocialPreconditionScreen
|
||||
import ac.aux.compose.ui.composable.navigation.routes.CreateProfileRoute
|
||||
import ac.aux.compose.ui.composable.navigation.routes.FeedRoute
|
||||
import ac.aux.compose.ui.composable.navigation.routes.ImplementationPendingRoute
|
||||
import ac.aux.compose.ui.composable.navigation.routes.LandingRoute
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -12,6 +15,9 @@ import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.toRoute
|
||||
import ac.aux.compose.ui.composable.navigation.routes.Route
|
||||
import ac.aux.compose.ui.composable.navigation.routes.SocialPreconditionRoute
|
||||
import ac.aux.compose.ui.composable.widgets.feed.DUMMY_EVENTS
|
||||
import ac.aux.compose.ui.view.state.FeedListUIState
|
||||
|
||||
@Composable
|
||||
fun AuxNavHost(
|
||||
@@ -46,7 +52,40 @@ fun AuxNavHost(
|
||||
CreateProfileScreen(
|
||||
onNavigateToChatListRoute = {
|
||||
navController.navigate(
|
||||
route = ImplementationPendingRoute("Feed")
|
||||
route = SocialPreconditionRoute
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
composable<SocialPreconditionRoute> {
|
||||
SocialPreconditionScreen(
|
||||
onNavigateToSkipForNow = {
|
||||
navController.navigate(
|
||||
route = FeedRoute
|
||||
)
|
||||
},
|
||||
onNavigateToInviteFriend = {
|
||||
navController.navigate(
|
||||
route = ImplementationPendingRoute("Invite Friend")
|
||||
)
|
||||
},
|
||||
onNavigateToViewInvites = {
|
||||
navController.navigate(
|
||||
route = ImplementationPendingRoute("View invites")
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
composable<FeedRoute> {
|
||||
FeedScreen(
|
||||
initialFeedListUIState = FeedListUIState.Loaded(
|
||||
events = DUMMY_EVENTS,
|
||||
),
|
||||
onNavigateToEvent = { event ->
|
||||
navController.navigate(
|
||||
route = ImplementationPendingRoute(
|
||||
event.content
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package ac.aux.compose.ui.composable.navigation.routes
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
object FeedRoute: Route()
|
||||
@@ -0,0 +1,6 @@
|
||||
package ac.aux.compose.ui.composable.navigation.routes
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
object SocialPreconditionRoute: Route()
|
||||
@@ -0,0 +1,35 @@
|
||||
package ac.aux.compose.ui.composable.widgets.feed
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import kotlin.time.Clock
|
||||
|
||||
val DUMMY_EVENTS = listOf(
|
||||
Event(
|
||||
id = KeyPair().pubKey.toString(),
|
||||
pubKey = KeyPair().pubKey.toString(),
|
||||
createdAt = Clock.System.now().toEpochMilliseconds(),
|
||||
kind = 1,
|
||||
tags = emptyArray(),
|
||||
content = "This is just a note",
|
||||
sig = KeyPair().pubKey.toString(),
|
||||
),
|
||||
Event(
|
||||
id = KeyPair().pubKey.toString(),
|
||||
pubKey = KeyPair().pubKey.toString(),
|
||||
createdAt = Clock.System.now().toEpochMilliseconds(),
|
||||
kind = 1,
|
||||
tags = emptyArray(),
|
||||
content = "This is another note",
|
||||
sig = KeyPair().pubKey.toString(),
|
||||
),
|
||||
Event(
|
||||
id = KeyPair().pubKey.toString(),
|
||||
pubKey = KeyPair().pubKey.toString(),
|
||||
createdAt = Clock.System.now().toEpochMilliseconds(),
|
||||
kind = 1,
|
||||
tags = emptyArray(),
|
||||
content = "This is a better note",
|
||||
sig = KeyPair().pubKey.toString(),
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,85 @@
|
||||
package ac.aux.compose.ui.composable.widgets.feed
|
||||
|
||||
import ac.aux.compose.ui.theme.AuxTheme
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.AssistChip
|
||||
import androidx.compose.material3.SuggestionChip
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
|
||||
@Composable
|
||||
fun Event.ListView() {
|
||||
when (kind) {
|
||||
// TODO: Support other kinds...
|
||||
else -> {
|
||||
Column(
|
||||
modifier = Modifier.padding(10.dp).fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = content,
|
||||
)
|
||||
|
||||
LazyRow(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(
|
||||
10.dp,
|
||||
alignment = Alignment.Start
|
||||
)
|
||||
) {
|
||||
item {
|
||||
AssistChip(
|
||||
label = {
|
||||
Text(
|
||||
text = kind.toString() // TODO: Kind by name...
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
items(
|
||||
items = tags,
|
||||
) { tag ->
|
||||
SuggestionChip(
|
||||
label = {
|
||||
Text(
|
||||
text = tag.toString()
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun EventListViewPreview() {
|
||||
AuxTheme {
|
||||
Surface(
|
||||
modifier = Modifier.padding(20.dp)
|
||||
) {
|
||||
DUMMY_EVENTS.first().ListView()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package ac.aux.compose.ui.composable.widgets.feed
|
||||
|
||||
import androidx.compose.material3.ElevatedCard
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
|
||||
@Composable
|
||||
fun FeedListItem(
|
||||
event: Event,
|
||||
onNavigateToEvent: (Event) -> Unit
|
||||
) {
|
||||
ElevatedCard(
|
||||
onClick = {
|
||||
onNavigateToEvent.invoke(event)
|
||||
}
|
||||
) {
|
||||
event.ListView()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package ac.aux.compose.ui.composable.widgets.feed
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
|
||||
@Composable
|
||||
fun LoadedFeedView(
|
||||
events: List<Event>,
|
||||
onNavigateToEvent: (Event) -> Unit
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
items(
|
||||
items = events,
|
||||
key = { e -> e.id }
|
||||
) { event ->
|
||||
FeedListItem(
|
||||
event = event,
|
||||
onNavigateToEvent = {
|
||||
onNavigateToEvent.invoke(event)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package ac.aux.compose.ui.view.model
|
||||
|
||||
import ac.aux.compose.ui.view.state.FeedListUIState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class FeedListViewModel(
|
||||
initialFeedListUIState: FeedListUIState,
|
||||
): ViewModel() {
|
||||
var feedListUIState: FeedListUIState by mutableStateOf(initialFeedListUIState)
|
||||
private set
|
||||
|
||||
fun retryLoad() {
|
||||
feedListUIState = FeedListUIState.Loading
|
||||
loadRecipientListData()
|
||||
}
|
||||
|
||||
fun loadRecipientListData() {
|
||||
viewModelScope.launch {
|
||||
// TODO: Load Dummy data...
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
const val TAG = "RecipientListViewModel"
|
||||
|
||||
fun factory(
|
||||
initialFeedListUIState: FeedListUIState = FeedListUIState.Loading,
|
||||
): ViewModelProvider.Factory = viewModelFactory {
|
||||
initializer {
|
||||
FeedListViewModel(
|
||||
initialFeedListUIState = initialFeedListUIState,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package ac.aux.compose.ui.view.state
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import kotlin.time.Clock
|
||||
|
||||
sealed interface FeedListUIState {
|
||||
data class Loaded(
|
||||
val events: List<Event>
|
||||
): FeedListUIState
|
||||
|
||||
data object Error: FeedListUIState
|
||||
data object Loading: FeedListUIState
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user