Refactor UI to adapt to correct way of managing follow sets.

This commit is contained in:
KotlinGeekDev
2025-09-30 15:38:16 +01:00
parent dfb683e00d
commit f6b3fdcab4
5 changed files with 258 additions and 190 deletions

View File

@@ -30,9 +30,11 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.PlaylistAdd
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Tab
@@ -48,8 +50,6 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@@ -119,12 +119,11 @@ fun ListsAndSetsScreen(
refresh = {
followSetsViewModel.invalidateData()
},
addItem = { title: String, description: String?, listType: SetVisibility ->
val isSetPrivate = listType == SetVisibility.Private
addItem = { title: String, description: String? ->
followSetsViewModel.addFollowSet(
setName = title,
setDescription = description,
isListPrivate = isSetPrivate,
account = accountViewModel.account,
)
},
@@ -153,14 +152,14 @@ fun ListsAndSetsScreen(
fun CustomListsScreen(
followSetFeedState: FollowSetFeedState,
refresh: () -> Unit,
addItem: (title: String, description: String?, listType: SetVisibility) -> Unit,
addItem: (title: String, description: String?) -> Unit,
openItem: (identifier: String) -> Unit,
renameItem: (followSet: FollowSet, newName: String) -> Unit,
deleteItem: (followSet: FollowSet) -> Unit,
accountViewModel: AccountViewModel,
nav: INav,
) {
val pagerState = rememberPagerState { 3 }
val pagerState = rememberPagerState { 2 }
val coroutineScope = rememberCoroutineScope()
DisappearingScaffold(
@@ -185,22 +184,14 @@ fun CustomListsScreen(
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } },
text = { Text(text = stringRes(R.string.labeled_bookmarks), overflow = TextOverflow.Visible) },
)
Tab(
selected = pagerState.currentPage == 2,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(2) } },
text = { Text(text = stringRes(R.string.general_bookmarks), overflow = TextOverflow.Visible) },
)
}
}
},
floatingButton = {
// TODO: Show components based on current tab
FollowSetFabsAndMenu(
onAddPrivateSet = { name: String, description: String? ->
addItem(name, description, SetVisibility.Private)
},
onAddPublicSet = { name: String, description: String? ->
addItem(name, description, SetVisibility.Public)
onAddSet = { name: String, description: String? ->
addItem(name, description)
},
)
},
@@ -226,7 +217,7 @@ fun CustomListsScreen(
)
1 -> LabeledBookmarksFeedView()
2 -> GeneralBookmarksFeedView()
// 2 -> GeneralBookmarksFeedView()
}
}
}
@@ -236,57 +227,34 @@ fun CustomListsScreen(
@Composable
private fun FollowSetFabsAndMenu(
modifier: Modifier = Modifier,
onAddPrivateSet: (name: String, description: String?) -> Unit,
onAddPublicSet: (name: String, description: String?) -> Unit,
onAddSet: (name: String, description: String?) -> Unit,
) {
val isSetAdditionDialogOpen = remember { mutableStateOf(false) }
val isPrivateOptionTapped = remember { mutableStateOf(false) }
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp),
) {
FloatingActionButton(
onClick = {
isPrivateOptionTapped.value = true
isSetAdditionDialogOpen.value = true
},
shape = CircleShape,
containerColor = MaterialTheme.colorScheme.primary,
) {
ExtendedFloatingActionButton(
text = {
Text(text = "New")
},
icon = {
Icon(
painter = painterResource(R.drawable.lock_plus),
imageVector = Icons.AutoMirrored.Filled.PlaylistAdd,
contentDescription = null,
tint = Color.White,
)
}
FloatingActionButton(
onClick = {
isSetAdditionDialogOpen.value = true
},
shape = CircleShape,
containerColor = MaterialTheme.colorScheme.primary,
) {
Icon(
painter = painterResource(R.drawable.earth_plus),
contentDescription = null,
tint = Color.White,
)
}
}
},
onClick = {
isSetAdditionDialogOpen.value = true
},
shape = CircleShape,
containerColor = MaterialTheme.colorScheme.primary,
)
if (isSetAdditionDialogOpen.value) {
NewSetCreationDialog(
onDismiss = {
isSetAdditionDialogOpen.value = false
isPrivateOptionTapped.value = false
},
shouldBePrivate = isPrivateOptionTapped.value,
onCreateList = { name, description ->
if (isPrivateOptionTapped.value) {
onAddPrivateSet(name, description)
} else {
onAddPublicSet(name, description)
}
onAddSet(name, description)
},
)
}
@@ -296,27 +264,10 @@ private fun FollowSetFabsAndMenu(
fun NewSetCreationDialog(
modifier: Modifier = Modifier,
onDismiss: () -> Unit,
shouldBePrivate: Boolean,
onCreateList: (name: String, description: String?) -> Unit,
) {
val newListName = remember { mutableStateOf("") }
val newListDescription = remember { mutableStateOf<String?>(null) }
val context = LocalContext.current
val listTypeText =
stringRes(
context,
when (shouldBePrivate) {
true -> R.string.follow_set_type_private
false -> R.string.follow_set_type_public
},
)
val listTypeIcon =
when (shouldBePrivate) {
true -> R.drawable.lock
false -> R.drawable.ic_public
}
AlertDialog(
onDismissRequest = onDismiss,
@@ -325,12 +276,8 @@ fun NewSetCreationDialog(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Icon(
painter = painterResource(listTypeIcon),
contentDescription = null,
)
Text(
text = stringRes(R.string.follow_set_creation_dialog_title, listTypeText),
text = stringRes(R.string.follow_set_creation_dialog_title),
)
}
},
@@ -347,7 +294,7 @@ fun NewSetCreationDialog(
},
)
Spacer(modifier = DoubleVertSpacer)
// For the list description
// For the set description
TextField(
value =
(

View File

@@ -37,13 +37,10 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.SpanStyle
@@ -56,7 +53,6 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.nip51Lists.followSets.FollowSet
import com.vitorpamplona.amethyst.model.nip51Lists.followSets.SetVisibility
import com.vitorpamplona.amethyst.ui.components.ClickableBox
import com.vitorpamplona.amethyst.ui.note.VerticalDotsIcon
import com.vitorpamplona.amethyst.ui.stringRes
@@ -64,7 +60,6 @@ import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.Size5dp
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
import kotlin.let
@Composable
fun CustomSetItem(
@@ -96,20 +91,75 @@ fun CustomSetItem(
) {
Text(followSet.title, fontWeight = FontWeight.Bold)
Spacer(modifier = StdHorzSpacer)
FilterChip(
selected = true,
onClick = {},
label = {
Text(text = "${followSet.profiles.size}")
},
leadingIcon = {
Icon(
imageVector = Icons.Default.People,
contentDescription = null,
if (followSet.publicProfiles.isEmpty() && followSet.privateProfiles.isEmpty()) {
FilterChip(
selected = true,
onClick = {},
label = {
Text(text = "No members")
},
leadingIcon = {
Icon(
imageVector = Icons.Default.People,
contentDescription = null,
)
},
shape = ButtonBorder,
)
}
if (followSet.publicProfiles.isNotEmpty()) {
val publicMemberSize = followSet.publicProfiles.size
val membersLabel =
stringRes(
context,
if (publicMemberSize == 1) {
R.string.follow_set_single_member_label
} else {
R.string.follow_set_multiple_member_label
},
)
},
shape = ButtonBorder,
)
FilterChip(
selected = true,
onClick = {},
label = {
Text(text = "$publicMemberSize $membersLabel")
},
leadingIcon = {
Icon(
painterResource(R.drawable.ic_public),
contentDescription = null,
)
},
shape = ButtonBorder,
)
Spacer(modifier = StdHorzSpacer)
}
if (followSet.privateProfiles.isNotEmpty()) {
val privateMemberSize = followSet.privateProfiles.size
val membersLabel =
stringRes(
context,
if (privateMemberSize == 1) {
R.string.follow_set_single_member_label
} else {
R.string.follow_set_multiple_member_label
},
)
FilterChip(
selected = true,
onClick = {},
label = {
Text(text = "$privateMemberSize $membersLabel")
},
leadingIcon = {
Icon(
painterResource(R.drawable.lock),
contentDescription = null,
)
},
shape = ButtonBorder,
)
}
}
Spacer(modifier = StdVertSpacer)
Text(
@@ -119,34 +169,6 @@ fun CustomSetItem(
maxLines = 2,
)
}
followSet.visibility.let {
val text by derivedStateOf {
when (it) {
SetVisibility.Public -> stringRes(context, R.string.follow_set_type_public)
SetVisibility.Private -> stringRes(context, R.string.follow_set_type_private)
SetVisibility.Mixed -> stringRes(context, R.string.follow_set_type_mixed)
}
}
Column(
modifier = Modifier.padding(top = 15.dp),
verticalArrangement = Arrangement.Bottom,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Icon(
painter =
painterResource(
when (it) {
SetVisibility.Public -> R.drawable.ic_public
SetVisibility.Private -> R.drawable.lock
SetVisibility.Mixed -> R.drawable.format_list_bulleted_type
},
),
contentDescription = stringRes(R.string.follow_set_type_description, text),
)
Text(text, color = Color.Gray, fontWeight = FontWeight.Light)
}
}
}
Column(

View File

@@ -28,6 +28,8 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
@@ -68,7 +70,6 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.model.nip51Lists.followSets.FollowSet
import com.vitorpamplona.amethyst.model.nip51Lists.followSets.SetVisibility
import com.vitorpamplona.amethyst.ui.components.ClickableBox
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.note.UserCompose
@@ -81,6 +82,7 @@ import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import com.vitorpamplona.amethyst.ui.theme.HalfPadding
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.StdPadding
import com.vitorpamplona.amethyst.ui.theme.VertPadding
import com.vitorpamplona.quartz.nip51Lists.peopleList.PeopleListEvent
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -144,7 +146,8 @@ fun FollowSetScreen(
when {
selectedSetState.value != null -> {
val selectedSet = selectedSetState.value
val users = selectedSet!!.profiles.mapToUsers(accountViewModel).filterNotNull()
val publicMembers = selectedSet!!.publicProfiles.mapToUsers(accountViewModel).filterNotNull()
val privateMembers = selectedSet.privateProfiles.mapToUsers(accountViewModel).filterNotNull()
Scaffold(
topBar = {
TopAppBar(
@@ -171,6 +174,7 @@ fun FollowSetScreen(
selectedSet,
accountViewModel.account,
)
navigator.popBack()
},
)
},
@@ -190,7 +194,8 @@ fun FollowSetScreen(
bottom = padding.calculateBottomPadding(),
).consumeWindowInsets(padding)
.imePadding(),
followSetList = users,
publicMemberList = publicMembers,
privateMemberList = privateMembers,
onDeleteUser = {
followSetViewModel.removeUserFromSet(
it,
@@ -233,14 +238,7 @@ fun TitleAndDescription(
)
Spacer(modifier = StdHorzSpacer)
Icon(
painter =
painterResource(
when (followSet.setVisibility) {
SetVisibility.Public -> R.drawable.ic_public
SetVisibility.Private -> R.drawable.lock
SetVisibility.Mixed -> R.drawable.format_list_bulleted_type
},
),
painter = painterResource(R.drawable.format_list_bulleted_type),
contentDescription = null,
)
}
@@ -260,7 +258,8 @@ fun TitleAndDescription(
@Composable
private fun FollowSetListView(
modifier: Modifier = Modifier,
followSetList: List<User>,
publicMemberList: List<User>,
privateMemberList: List<User>,
onDeleteUser: (String) -> Unit,
accountViewModel: AccountViewModel,
nav: INav,
@@ -272,14 +271,61 @@ private fun FollowSetListView(
contentPadding = FeedPadding,
state = listState,
) {
itemsIndexed(followSetList, key = { _, item -> item.pubkeyHex }) { _, item ->
FollowSetListItem(
modifier = Modifier.animateItem(),
user = item,
accountViewModel = accountViewModel,
nav = nav,
onDeleteUser = onDeleteUser,
)
if (publicMemberList.isNotEmpty()) {
stickyHeader {
Column(
modifier = VertPadding,
) {
Text(
text = "Public Profiles",
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
)
HorizontalDivider(
modifier = Modifier.fillMaxWidth(),
thickness = 2.dp,
color = MaterialTheme.colorScheme.onBackground,
)
}
}
itemsIndexed(publicMemberList, key = { _, item -> item.pubkeyHex }) { _, item ->
FollowSetListItem(
modifier = Modifier.animateItem(),
user = item,
accountViewModel = accountViewModel,
nav = nav,
onDeleteUser = onDeleteUser,
)
}
item {
Spacer(modifier = Modifier.height(30.dp))
}
}
if (privateMemberList.isNotEmpty()) {
stickyHeader {
Text(
text = "Private Profiles",
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
)
HorizontalDivider(
modifier = Modifier.fillMaxWidth(),
thickness = 2.dp,
color = MaterialTheme.colorScheme.onBackground,
)
}
itemsIndexed(privateMemberList, key = { _, item -> item.pubkeyHex }) { _, item ->
FollowSetListItem(
modifier = Modifier.animateItem(),
user = item,
accountViewModel = accountViewModel,
nav = nav,
onDeleteUser = onDeleteUser,
)
}
item {
Spacer(modifier = Modifier.height(30.dp))
}
}
}
}

View File

@@ -45,6 +45,8 @@ import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilterChip
import androidx.compose.material3.HorizontalDivider
@@ -69,6 +71,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
@@ -165,16 +168,18 @@ fun FollowSetsManagementDialog(
) {
when (followSetsState) {
is FollowSetFeedState.Loaded -> {
val lists = (followSetsState as FollowSetFeedState.Loaded).feed
val sets = (followSetsState as FollowSetFeedState.Loaded).feed
sets.forEachIndexed { index, set ->
lists.forEachIndexed { index, list ->
Spacer(StdVertSpacer)
FollowSetItem(
modifier = Modifier.fillMaxWidth(),
listHeader = list.title,
setVisibility = list.visibility,
listHeader = set.title,
setVisibility = set.visibility,
userName = userInfo.toBestDisplayName(),
isUserInList = list.profiles.contains(userHex),
userIsPrivateMember = set.privateProfiles.contains(userHex),
userIsPublicMember = set.publicProfiles.contains(userHex),
onRemoveUser = {
Log.d(
"Amethyst",
@@ -182,23 +187,30 @@ fun FollowSetsManagementDialog(
)
followSetsViewModel.removeUserFromSet(
userHex,
list,
set,
account,
)
Log.d(
"Amethyst",
"Updated List. New size: ${list.profiles.size}",
"Updated List. Private profiles size: ${set.privateProfiles.size}," +
"Public profiles size: ${set.publicProfiles.size}",
)
},
onAddUser = {
onAddUserToList = { userShouldBePrivate ->
Log.d(
"Amethyst",
"ProfileActions: Adding item to list ...",
)
followSetsViewModel.addUserToSet(userHex, list, account)
followSetsViewModel.addUserToSet(
userHex,
set,
userShouldBePrivate,
account,
)
Log.d(
"Amethyst",
"Updated List. New size: ${list.profiles.size}",
"Updated List. Private profiles size: ${set.privateProfiles.size}," +
"Public profiles size: ${set.publicProfiles.size}",
)
},
)
@@ -222,11 +234,11 @@ fun FollowSetsManagementDialog(
if (followSetsState != FollowSetFeedState.Loading) {
FollowSetsCreationMenu(
userName = userInfo.toBestDisplayName(),
onSetCreate = { setName, setIsPrivate, description ->
onSetCreate = { setName, memberShouldBePrivate, description ->
followSetsViewModel.addFollowSet(
setName = setName,
setDescription = description,
isListPrivate = setIsPrivate,
firstMemberShouldBePrivate = memberShouldBePrivate,
optionalFirstMemberHex = userHex,
account = account,
)
@@ -306,11 +318,13 @@ fun FollowSetItem(
listHeader: String,
setVisibility: SetVisibility,
userName: String,
isUserInList: Boolean,
onAddUser: () -> Unit,
userIsPrivateMember: Boolean,
userIsPublicMember: Boolean,
onAddUserToList: (shouldBePrivateMember: Boolean) -> Unit,
onRemoveUser: () -> Unit,
) {
val context = LocalContext.current
val isUserInList = userIsPrivateMember || userIsPublicMember
Row(
modifier =
modifier
@@ -330,26 +344,10 @@ fun FollowSetItem(
) {
Text(listHeader, fontWeight = FontWeight.Bold)
Spacer(modifier = StdHorzSpacer)
setVisibility.let {
val text by derivedStateOf {
when (it) {
SetVisibility.Public -> stringRes(context, R.string.follow_set_type_public)
SetVisibility.Private -> stringRes(context, R.string.follow_set_type_private)
SetVisibility.Mixed -> stringRes(context, R.string.follow_set_type_mixed)
}
}
Icon(
painter =
painterResource(
when (setVisibility) {
SetVisibility.Public -> R.drawable.ic_public
SetVisibility.Private -> R.drawable.lock
SetVisibility.Mixed -> R.drawable.format_list_bulleted_type
},
),
contentDescription = stringRes(R.string.follow_set_type_description, text),
)
}
Icon(
painter = painterResource(R.drawable.format_list_bulleted_type),
contentDescription = stringRes(R.string.follow_set_type_description),
)
}
Spacer(modifier = StdVertSpacer)
@@ -362,7 +360,11 @@ fun FollowSetItem(
Text(
text =
if (isUserInList) {
stringRes(R.string.follow_set_presence_indicator, userName)
if (userIsPublicMember) {
stringRes(R.string.follow_set_public_presence_indicator, userName)
} else {
stringRes(R.string.follow_set_private_presence_indicator, userName)
}
} else {
stringRes(R.string.follow_set_absence_indicator, userName)
},
@@ -389,9 +391,14 @@ fun FollowSetItem(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
val isUserAddTapped = remember { mutableStateOf(false) }
IconButton(
onClick = {
if (isUserInList) onRemoveUser() else onAddUser()
if (isUserInList) {
onRemoveUser()
} else {
isUserAddTapped.value = true
}
},
modifier =
Modifier
@@ -423,15 +430,55 @@ fun FollowSetItem(
text = stringRes(if (isUserInList) R.string.remove else R.string.add),
color = MaterialTheme.colorScheme.onBackground,
)
UserAdditionOptionsMenu(
isExpanded = isUserAddTapped.value,
onUserAdd = { shouldBePrivateMember ->
onAddUserToList(shouldBePrivateMember)
},
onDismiss = { isUserAddTapped.value = false },
)
}
}
}
@Composable
private fun UserAdditionOptionsMenu(
modifier: Modifier = Modifier,
isExpanded: Boolean,
onUserAdd: (asPrivateMember: Boolean) -> Unit,
onDismiss: () -> Unit,
) {
DropdownMenu(
expanded = isExpanded,
onDismissRequest = onDismiss,
) {
DropdownMenuItem(
text = {
Text(text = "Add as public member")
},
onClick = {
onUserAdd(false)
onDismiss()
},
)
DropdownMenuItem(
text = {
Text(text = "Add as private member")
},
onClick = {
onUserAdd(true)
onDismiss()
},
)
}
}
@Composable
fun FollowSetsCreationMenu(
modifier: Modifier = Modifier,
userName: String,
onSetCreate: (setName: String, setIsPrivate: Boolean, description: String?) -> Unit,
onSetCreate: (setName: String, memberShouldBePrivate: Boolean, description: String?) -> Unit,
) {
val isListAdditionDialogOpen = remember { mutableStateOf(false) }
val isPrivateOptionTapped = remember { mutableStateOf(false) }
@@ -474,7 +521,6 @@ fun FollowSetsCreationMenu(
isListAdditionDialogOpen.value = false
isPrivateOptionTapped.value = false
},
shouldBePrivate = isPrivateOptionTapped.value,
onCreateList = { name, description ->
onSetCreate(name, isPrivateOptionTapped.value, description)
},
@@ -528,7 +574,11 @@ fun FollowSetCreationItem(
}
Spacer(modifier = StdVertSpacer)
Text(
stringRes(R.string.follow_set_creation_item_description, setTypeLabel, userName),
stringRes(
R.string.follow_set_creation_item_description,
userName,
setTypeLabel.lowercase(Locale.current.platformLocale),
),
fontWeight = FontWeight.Light,
overflow = TextOverflow.Ellipsis,
maxLines = 2,

View File

@@ -522,15 +522,18 @@
<string name="follow_set_add_author_from_note_action">Add author to follow set</string>
<string name="follow_set_profile_actions_menu_description">Add or remove user from lists, or create a new list with this user.</string>
<string name="follow_set_type_description">Icon for %1$s List</string>
<string name="follow_set_presence_indicator">"%1$s is present in this list"</string>
<string name="follow_set_absence_indicator">"%1$s is not in this list"</string>
<string name="follow_set_public_presence_indicator">%1$s is a public member</string>
<string name="follow_set_private_presence_indicator">%1$s is a private member</string>
<string name="follow_set_single_member_label">member</string>
<string name="follow_set_multiple_member_label">members</string>
<string name="follow_set_absence_indicator">%1$s is not in this list</string>
<string name="follow_set_man_dialog_title">Your Follow Sets</string>
<string name="follow_set_empty_dialog_msg">No follow sets were found, or you don\'t have any follow sets. Tap below to refresh, or use the menu to create one.</string>
<string name="follow_set_error_dialog_msg">There was a problem while fetching: %1$s</string>
<string name="follow_set_creation_menu_title">Make New List</string>
<string name="follow_set_creation_item_label">"Create new %1$s list with user</string>
<string name="follow_set_creation_item_description">Creates a %1$s follow set, and adds %2$s to it.</string>
<string name="follow_set_creation_dialog_title">New %1$s List</string>
<string name="follow_set_creation_item_label">New list with %1$s membership</string>
<string name="follow_set_creation_item_description">Creates a new follow set, and adds %1$s as a %2$s member.</string>
<string name="follow_set_creation_dialog_title">New Follow Set</string>
<string name="follow_set_creation_name_label">Set name</string>
<string name="follow_set_creation_desc_label">Set description(optional)</string>
<string name="follow_set_creation_action_btn_label">Create set</string>