Follow users, view followers/following lists, and search for users in the community.
CentralHubFollowingManager manages follow/unfollow operations and emits FollowEvent updates through the followEvents flow.
When you follow or unfollow a user, the event flow notifies all observers so UI can update accordingly.
class FollowingViewModel : ViewModel(), KoinComponent {
private val followingManager:
CentralHubFollowingManager = get()
val followEvents = followingManager.followEvents
suspend fun followUser(userId: String) {
followingManager.followUser(
userId = userId,
onSuccess = { /* User followed */ },
onFailure = { error -> /* Handle error */ }
)
}
suspend fun unfollowUser(userId: String) {
followingManager.unfollowUser(
userId = userId,
onSuccess = { /* User unfollowed */ },
onFailure = { error -> /* Handle error */ }
)
}
}CentralHubFollowersProvider displays followers and following lists for any user with sorting and pagination support.
Sort Options:
The provider maintains separate lists and pagination for followers and following.
class FollowersViewModel : ViewModel(), KoinComponent {
private val followersProvider:
CentralHubFollowersProvider = get()
val followersState = followersProvider.state
suspend fun loadFollowers(userId: String) {
followersProvider.loadData(
userId = userId,
sortType = FollowersSortType.Latest,
pageLimit = 20
)
}
fun loadMore(
followingType: FollowingType,
sortType: FollowersSortType
) {
followersProvider.loadNextPage(
followingType = followingType,
sortType = sortType,
pageLimit = 20
)
}
}CentralHubSearchProvider enables searching for users within the Central Hub community. You can search across all users or filter by followers/following lists.
SearchType.ALL
Search across all users in the Central Hub.
SearchType.FOLLOWING
Search only within users you're following.
SearchType.FOLLOWERS
Search only within your followers list.
The search provider supports pagination for large result sets. Load additional results with loadNextSearchPage().
class SearchViewModel : ViewModel(), KoinComponent {
private val searchProvider:
CentralHubSearchProvider = get()
val searchState = searchProvider.state
suspend fun search(
query: String,
searchType: SearchType = SearchType.ALL
) {
if (query.isNotBlank()) {
searchProvider.search(
query = query,
searchType = searchType,
pageLimit = 20
)
}
}
suspend fun loadMoreResults(
query: String,
searchType: SearchType
) {
searchProvider.loadNextSearchPage(
query = query,
searchType = searchType,
pageLimit = 20
)
}
fun clearSearch() {
searchProvider.clear()
}
override fun onCleared() {
super.onCleared()
searchProvider.destroy()
}
}Compose UI:
@Composable
fun UserSearchScreen(
viewModel: SearchViewModel = viewModel()
) {
var searchQuery by remember { mutableStateOf("") }
val searchState by viewModel.searchState
.collectAsStateWithLifecycle()
Column {
// Search input
TextField(
value = searchQuery,
onValueChange = { query ->
searchQuery = query
viewModel.viewModelScope.launch {
viewModel.search(query)
}
},
placeholder = { Text("Search users...") }
)
// Search results
LazyColumn {
items(searchState.searchResults) { user ->
UserListItem(user)
}
// Load more button
if (!searchState.allDataLoaded &&
searchState.searchResults.isNotEmpty()) {
item {
LoadMoreButton {
viewModel.viewModelScope.launch {
viewModel.loadMoreResults(
searchQuery,
SearchType.ALL
)
}
}
}
}
}
// Loading indicator
if (searchState.loadingStatus == LoadingStatus.LOADING) {
CircularProgressIndicator()
}
}
}State for followers and following lists:
data class FollowersState(
val followers: List<Follower>,
val following: List<Follower>,
val followersCount: Int,
val followingCount: Int,
val followersNextPage: String?,
val followingNextPage: String?,
val loadingStatus: LoadingStatus,
val nextPageLoadingStatus: LoadingStatus,
val followActionLoadingStatus: LoadingStatus,
)State for user search:
data class CentralHubSearchState(
val searchResults: List<Follower>,
val loadingStatus: LoadingStatus,
val nextPageLoadingStatus: LoadingStatus,
val followActionLoadingStatus: LoadingStatus,
val allDataLoaded: Boolean,
)