diff --git a/app/src/main/java/me/ash/reader/ui/page/home/drawer/feed/FeedOptionDrawer.kt b/app/src/main/java/me/ash/reader/ui/page/home/drawer/feed/FeedOptionDrawer.kt index f3c605d..5d9fe7c 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/drawer/feed/FeedOptionDrawer.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/drawer/feed/FeedOptionDrawer.kt @@ -1,21 +1,21 @@ package me.ash.reader.ui.page.home.drawer.feed -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height +import android.widget.Toast +import androidx.compose.foundation.layout.* import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.CreateNewFolder +import androidx.compose.material.icons.outlined.Edit import androidx.compose.material.icons.rounded.RssFeed import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel @@ -33,41 +33,46 @@ fun FeedOptionDrawer( feedOptionViewModel: FeedOptionViewModel = hiltViewModel(), content: @Composable () -> Unit = {}, ) { + val context = LocalContext.current + val scope = rememberCoroutineScope() val viewState = feedOptionViewModel.viewState.collectAsStateValue() val feed = viewState.feed + val toastString = stringResource(R.string.rename_toast, viewState.newName) BottomDrawer( drawerState = viewState.drawerState, sheetContent = { Column { - Icon( - modifier = modifier - .roundClick { } - .fillMaxWidth() - .align(Alignment.CenterHorizontally), - imageVector = Icons.Rounded.RssFeed, - contentDescription = feed?.name - ?: stringResource(R.string.unknown), - tint = MaterialTheme.colorScheme.onSurface - ) - Spacer(modifier = modifier.height(16.dp)) - Text( - modifier = Modifier - .roundClick {} - .fillMaxWidth(), - text = feed?.name ?: stringResource(R.string.unknown), - style = MaterialTheme.typography.headlineSmall, - textAlign = TextAlign.Center, - color = MaterialTheme.colorScheme.onSurface, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - Spacer(modifier = modifier.height(16.dp)) + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Icon( + modifier = Modifier.roundClick { }, + imageVector = Icons.Rounded.RssFeed, + contentDescription = feed?.name ?: stringResource(R.string.unknown), + tint = MaterialTheme.colorScheme.secondary, + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + modifier = Modifier.roundClick { + feedOptionViewModel.dispatch(FeedOptionViewAction.ShowRenameDialog) + }, + text = feed?.name ?: stringResource(R.string.unknown), + style = MaterialTheme.typography.headlineSmall, + color = MaterialTheme.colorScheme.onSurface, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + Spacer(modifier = Modifier.height(16.dp)) ResultView( link = feed?.url ?: stringResource(R.string.unknown), groups = viewState.groups, selectedAllowNotificationPreset = viewState.feed?.isNotification ?: false, selectedParseFullContentPreset = viewState.feed?.isFullContent ?: false, + isMoveToGroup = true, showUnsubscribe = true, selectedGroupId = viewState.feed?.groupId ?: "", allowNotificationPresetOnClick = { @@ -110,4 +115,23 @@ fun FeedOptionDrawer( feedOptionViewModel.dispatch(FeedOptionViewAction.AddNewGroup) } ) + + TextFieldDialog( + visible = viewState.renameDialogVisible, + title = stringResource(R.string.rename), + icon = Icons.Outlined.Edit, + value = viewState.newName, + placeholder = stringResource(R.string.name), + onValueChange = { + feedOptionViewModel.dispatch(FeedOptionViewAction.InputNewName(it)) + }, + onDismissRequest = { + feedOptionViewModel.dispatch(FeedOptionViewAction.HideRenameDialog) + }, + onConfirm = { + feedOptionViewModel.dispatch(FeedOptionViewAction.Rename) + feedOptionViewModel.dispatch(FeedOptionViewAction.Hide(scope)) + Toast.makeText(context, toastString, Toast.LENGTH_SHORT).show() + } + ) } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/ui/page/home/drawer/feed/FeedOptionViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/drawer/feed/FeedOptionViewModel.kt index d8410dd..06c2096 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/drawer/feed/FeedOptionViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/drawer/feed/FeedOptionViewModel.kt @@ -57,6 +57,10 @@ class FeedOptionViewModel @Inject constructor( is FeedOptionViewAction.AddNewGroup -> addNewGroup() is FeedOptionViewAction.ShowNewGroupDialog -> changeNewGroupDialogVisible(true) is FeedOptionViewAction.HideNewGroupDialog -> changeNewGroupDialogVisible(false) + is FeedOptionViewAction.InputNewName -> inputNewName(action.content) + is FeedOptionViewAction.Rename -> rename() + is FeedOptionViewAction.ShowRenameDialog -> changeRenameDialogVisible(true) + is FeedOptionViewAction.HideRenameDialog -> changeRenameDialogVisible(false) } } @@ -174,6 +178,40 @@ class FeedOptionViewModel @Inject constructor( ) } } + + private fun rename() { + _viewState.value.feed?.let { + viewModelScope.launch { + rssRepository.get().updateFeed( + it.copy( + name = _viewState.value.newName + ) + ) + _viewState.update { + it.copy( + renameDialogVisible = false, + ) + } + } + } + } + + private fun changeRenameDialogVisible(visible: Boolean) { + _viewState.update { + it.copy( + renameDialogVisible = visible, + newName = if (visible) _viewState.value.feed?.name ?: "" else "", + ) + } + } + + private fun inputNewName(content: String) { + _viewState.update { + it.copy( + newName = content + ) + } + } } @OptIn(ExperimentalMaterialApi::class) @@ -185,6 +223,8 @@ data class FeedOptionViewState( val newGroupDialogVisible: Boolean = false, val groups: List = emptyList(), val deleteDialogVisible: Boolean = false, + val newName: String = "", + val renameDialogVisible: Boolean = false, ) sealed class FeedOptionViewAction { @@ -218,4 +258,11 @@ sealed class FeedOptionViewAction { object ShowNewGroupDialog : FeedOptionViewAction() object HideNewGroupDialog : FeedOptionViewAction() object AddNewGroup : FeedOptionViewAction() + + object ShowRenameDialog : FeedOptionViewAction() + object HideRenameDialog : FeedOptionViewAction() + object Rename : FeedOptionViewAction() + data class InputNewName( + val content: String + ) : FeedOptionViewAction() } diff --git a/app/src/main/java/me/ash/reader/ui/page/home/drawer/group/GroupOptionDrawer.kt b/app/src/main/java/me/ash/reader/ui/page/home/drawer/group/GroupOptionDrawer.kt index e644141..e4f87a3 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/drawer/group/GroupOptionDrawer.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/drawer/group/GroupOptionDrawer.kt @@ -1,5 +1,7 @@ package me.ash.reader.ui.page.home.drawer.group +import android.content.Context +import android.widget.Toast import androidx.compose.animation.animateContentSize import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyRow @@ -9,12 +11,14 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Article +import androidx.compose.material.icons.outlined.Edit import androidx.compose.material.icons.outlined.Folder import androidx.compose.material.icons.outlined.Notifications import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -26,9 +30,11 @@ import androidx.hilt.navigation.compose.hiltViewModel import com.google.accompanist.flowlayout.FlowRow import com.google.accompanist.flowlayout.MainAxisAlignment import me.ash.reader.R +import me.ash.reader.data.entity.Group import me.ash.reader.ui.component.BottomDrawer import me.ash.reader.ui.component.SelectionChip import me.ash.reader.ui.component.Subtitle +import me.ash.reader.ui.component.TextFieldDialog import me.ash.reader.ui.ext.collectAsStateValue import me.ash.reader.ui.ext.currentAccountId import me.ash.reader.ui.ext.getDefaultGroupId @@ -38,40 +44,44 @@ import me.ash.reader.ui.ext.roundClick @Composable fun GroupOptionDrawer( modifier: Modifier = Modifier, - GroupOptionViewModel: GroupOptionViewModel = hiltViewModel(), + groupOptionViewModel: GroupOptionViewModel = hiltViewModel(), content: @Composable () -> Unit = {}, ) { val context = LocalContext.current - val viewState = GroupOptionViewModel.viewState.collectAsStateValue() + val scope = rememberCoroutineScope() + val viewState = groupOptionViewModel.viewState.collectAsStateValue() val group = viewState.group + val toastString = stringResource(R.string.rename_toast, viewState.newName) BottomDrawer( drawerState = viewState.drawerState, sheetContent = { Column { - Icon( - modifier = modifier - .fillMaxWidth() - .align(Alignment.CenterHorizontally), - imageVector = Icons.Outlined.Folder, - contentDescription = group?.name ?: stringResource(R.string.unknown), - tint = MaterialTheme.colorScheme.onSurface - ) - Spacer(modifier = modifier.height(16.dp)) - Text( - modifier = Modifier - .roundClick {} - .fillMaxWidth(), - text = group?.name ?: stringResource(R.string.unknown), - style = MaterialTheme.typography.headlineSmall, - textAlign = TextAlign.Center, - color = MaterialTheme.colorScheme.onSurface, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - Spacer(modifier = modifier.height(16.dp)) Column( - modifier = modifier.verticalScroll(rememberScrollState()) + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Icon( + imageVector = Icons.Outlined.Folder, + contentDescription = group?.name ?: stringResource(R.string.unknown), + tint = MaterialTheme.colorScheme.secondary, + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + modifier = Modifier.roundClick { + groupOptionViewModel.dispatch(GroupOptionViewAction.ShowRenameDialog) + }, + text = group?.name ?: stringResource(R.string.unknown), + style = MaterialTheme.typography.headlineSmall, + color = MaterialTheme.colorScheme.onSurface, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + Spacer(modifier = Modifier.height(16.dp)) + Column( + modifier = Modifier.verticalScroll(rememberScrollState()) ) { Row( modifier = Modifier.fillMaxWidth(), @@ -84,98 +94,22 @@ fun GroupOptionDrawer( textAlign = TextAlign.Center, ) } - Spacer(modifier = Modifier.height(26.dp)) + Spacer(modifier = Modifier.height(26.dp)) Subtitle(text = stringResource(R.string.preset)) - Spacer(modifier = Modifier.height(10.dp)) - FlowRow( - mainAxisAlignment = MainAxisAlignment.Start, - crossAxisSpacing = 10.dp, - mainAxisSpacing = 10.dp, - ) { - SelectionChip( - modifier = Modifier.animateContentSize(), - content = stringResource(R.string.allow_notification), - selected = false, - selectedIcon = { - Icon( - imageVector = Icons.Outlined.Notifications, - contentDescription = stringResource(R.string.allow_notification), - modifier = Modifier - .padding(start = 8.dp) - .size(20.dp), - ) - }, - ) { - GroupOptionViewModel.dispatch(GroupOptionViewAction.ShowAllAllowNotificationDialog) - } - SelectionChip( - modifier = Modifier.animateContentSize(), - content = stringResource(R.string.parse_full_content), - selected = false, - selectedIcon = { - Icon( - imageVector = Icons.Outlined.Article, - contentDescription = stringResource(R.string.parse_full_content), - modifier = Modifier - .padding(start = 8.dp) - .size(20.dp), - ) - }, - ) { - GroupOptionViewModel.dispatch(GroupOptionViewAction.ShowAllParseFullContentDialog) - } - if (group?.id != context.currentAccountId.getDefaultGroupId()) { - SelectionChip( - modifier = Modifier.animateContentSize(), - content = stringResource(R.string.delete_group), - selected = false, - ) { - GroupOptionViewModel.dispatch(GroupOptionViewAction.ShowDeleteDialog) - } - } - } - Spacer(modifier = Modifier.height(26.dp)) - Subtitle(text = stringResource(R.string.move_to_group)) Spacer(modifier = Modifier.height(10.dp)) + Preset(groupOptionViewModel, group, context) - if (viewState.groups.size > 6) { - LazyRow { - items(viewState.groups) { - if (it.id != group?.id) { - SelectionChip( - modifier = Modifier.animateContentSize(), - content = it.name, - selected = false, - ) { - GroupOptionViewModel.dispatch( - GroupOptionViewAction.ShowAllMoveToGroupDialog(it) - ) - } - } - Spacer(modifier = Modifier.width(10.dp)) - } - } - } else { - FlowRow( - mainAxisAlignment = MainAxisAlignment.Start, - crossAxisSpacing = 10.dp, - mainAxisSpacing = 10.dp, - ) { - viewState.groups.forEach { - if (it.id != group?.id) { - SelectionChip( - modifier = Modifier.animateContentSize(), - content = it.name, - selected = false, - ) { - GroupOptionViewModel.dispatch( - GroupOptionViewAction.ShowAllMoveToGroupDialog(it) - ) - } - } - } + if (viewState.groups.size != 1) { + Spacer(modifier = Modifier.height(26.dp)) + Subtitle(text = stringResource(R.string.move_to_group)) + Spacer(modifier = Modifier.height(10.dp)) + + if (viewState.groups.size > 6) { + LazyRowGroups(viewState, group, groupOptionViewModel) + } else { + FlowRowGroups(viewState, group, groupOptionViewModel) } } @@ -191,4 +125,128 @@ fun GroupOptionDrawer( AllAllowNotificationDialog(groupName = group?.name ?: "") AllParseFullContentDialog(groupName = group?.name ?: "") AllMoveToGroupDialog(groupName = group?.name ?: "") + TextFieldDialog( + visible = viewState.renameDialogVisible, + title = stringResource(R.string.rename), + icon = Icons.Outlined.Edit, + value = viewState.newName, + placeholder = stringResource(R.string.name), + onValueChange = { + groupOptionViewModel.dispatch(GroupOptionViewAction.InputNewName(it)) + }, + onDismissRequest = { + groupOptionViewModel.dispatch(GroupOptionViewAction.HideRenameDialog) + }, + onConfirm = { + groupOptionViewModel.dispatch(GroupOptionViewAction.Rename) + groupOptionViewModel.dispatch(GroupOptionViewAction.Hide(scope)) + Toast.makeText(context, toastString, Toast.LENGTH_SHORT).show() + } + ) +} + +@Composable +private fun Preset( + groupOptionViewModel: GroupOptionViewModel, + group: Group?, + context: Context +) { + FlowRow( + mainAxisAlignment = MainAxisAlignment.Start, + crossAxisSpacing = 10.dp, + mainAxisSpacing = 10.dp, + ) { + SelectionChip( + modifier = Modifier.animateContentSize(), + content = stringResource(R.string.allow_notification), + selected = false, + selectedIcon = { + Icon( + imageVector = Icons.Outlined.Notifications, + contentDescription = stringResource(R.string.allow_notification), + modifier = Modifier + .padding(start = 8.dp) + .size(20.dp), + ) + }, + ) { + groupOptionViewModel.dispatch(GroupOptionViewAction.ShowAllAllowNotificationDialog) + } + SelectionChip( + modifier = Modifier.animateContentSize(), + content = stringResource(R.string.parse_full_content), + selected = false, + selectedIcon = { + Icon( + imageVector = Icons.Outlined.Article, + contentDescription = stringResource(R.string.parse_full_content), + modifier = Modifier + .padding(start = 8.dp) + .size(20.dp), + ) + }, + ) { + groupOptionViewModel.dispatch(GroupOptionViewAction.ShowAllParseFullContentDialog) + } + if (group?.id != context.currentAccountId.getDefaultGroupId()) { + SelectionChip( + modifier = Modifier.animateContentSize(), + content = stringResource(R.string.delete_group), + selected = false, + ) { + groupOptionViewModel.dispatch(GroupOptionViewAction.ShowDeleteDialog) + } + } + } +} + +@Composable +private fun FlowRowGroups( + viewState: GroupOptionViewState, + group: Group?, + groupOptionViewModel: GroupOptionViewModel +) { + FlowRow( + mainAxisAlignment = MainAxisAlignment.Start, + crossAxisSpacing = 10.dp, + mainAxisSpacing = 10.dp, + ) { + viewState.groups.forEach { + if (it.id != group?.id) { + SelectionChip( + modifier = Modifier.animateContentSize(), + content = it.name, + selected = false, + ) { + groupOptionViewModel.dispatch( + GroupOptionViewAction.ShowAllMoveToGroupDialog(it) + ) + } + } + } + } +} + +@Composable +private fun LazyRowGroups( + viewState: GroupOptionViewState, + group: Group?, + groupOptionViewModel: GroupOptionViewModel +) { + LazyRow { + items(viewState.groups) { + if (it.id != group?.id) { + SelectionChip( + modifier = Modifier.animateContentSize(), + content = it.name, + selected = false, + ) { + groupOptionViewModel.dispatch( + GroupOptionViewAction.ShowAllMoveToGroupDialog(it) + ) + } + } + Spacer(modifier = Modifier.width(10.dp)) + } + } } diff --git a/app/src/main/java/me/ash/reader/ui/page/home/drawer/group/GroupOptionViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/drawer/group/GroupOptionViewModel.kt index e625ee7..7ee989e 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/drawer/group/GroupOptionViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/drawer/group/GroupOptionViewModel.kt @@ -70,6 +70,11 @@ class GroupOptionViewModel @Inject constructor( changeAllMoveToGroupDialogVisible(visible = false) is GroupOptionViewAction.AllMoveToGroup -> allMoveToGroup(action.callback) + + is GroupOptionViewAction.InputNewName -> inputNewName(action.content) + is GroupOptionViewAction.Rename -> rename() + is GroupOptionViewAction.ShowRenameDialog -> changeRenameDialogVisible(true) + is GroupOptionViewAction.HideRenameDialog -> changeRenameDialogVisible(false) } } @@ -173,6 +178,40 @@ class GroupOptionViewModel @Inject constructor( ) } } + + private fun rename() { + _viewState.value.group?.let { + viewModelScope.launch { + rssRepository.get().updateGroup( + it.copy( + name = _viewState.value.newName + ) + ) + _viewState.update { + it.copy( + renameDialogVisible = false, + ) + } + } + } + } + + private fun changeRenameDialogVisible(visible: Boolean) { + _viewState.update { + it.copy( + renameDialogVisible = visible, + newName = if (visible) _viewState.value.group?.name ?: "" else "", + ) + } + } + + private fun inputNewName(content: String) { + _viewState.update { + it.copy( + newName = content + ) + } + } } @OptIn(ExperimentalMaterialApi::class) @@ -185,6 +224,8 @@ data class GroupOptionViewState( val allParseFullContentDialogVisible: Boolean = false, val allMoveToGroupDialogVisible: Boolean = false, val deleteDialogVisible: Boolean = false, + val newName: String = "", + val renameDialogVisible: Boolean = false, ) sealed class GroupOptionViewAction { @@ -229,4 +270,11 @@ sealed class GroupOptionViewAction { ) : GroupOptionViewAction() object HideAllMoveToGroupDialog : GroupOptionViewAction() + + object ShowRenameDialog : GroupOptionViewAction() + object HideRenameDialog : GroupOptionViewAction() + object Rename : GroupOptionViewAction() + data class InputNewName( + val content: String + ) : GroupOptionViewAction() } diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/ResultView.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/ResultView.kt index 8714c4b..d892900 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/ResultView.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/subscribe/ResultView.kt @@ -43,6 +43,7 @@ fun ResultView( groups: List = emptyList(), selectedAllowNotificationPreset: Boolean = false, selectedParseFullContentPreset: Boolean = false, + isMoveToGroup: Boolean = false, showUnsubscribe: Boolean = false, selectedGroupId: String = "", allowNotificationPresetOnClick: () -> Unit = {}, @@ -72,6 +73,7 @@ fun ResultView( Spacer(modifier = Modifier.height(26.dp)) AddToGroup( + isMoveToGroup = isMoveToGroup, groups = groups, selectedGroupId = selectedGroupId, onGroupClick = onGroupClick, @@ -169,12 +171,13 @@ private fun Preset( @Composable private fun AddToGroup( + isMoveToGroup: Boolean = false, groups: List, selectedGroupId: String, onGroupClick: (groupId: String) -> Unit = {}, onAddNewGroup: () -> Unit = {}, ) { - Subtitle(text = stringResource(R.string.add_to_group)) + Subtitle(text = stringResource(if (isMoveToGroup) R.string.move_to_group else R.string.add_to_group)) Spacer(modifier = Modifier.height(10.dp)) if (groups.size > 6) { diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/SettingsPage.kt b/app/src/main/java/me/ash/reader/ui/page/settings/SettingsPage.kt index 0316c6a..06209db 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/SettingsPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/SettingsPage.kt @@ -72,7 +72,6 @@ fun SettingsPage( } item { SelectableSettingGroupItem( - selected = true, title = stringResource(R.string.color_and_style), desc = stringResource(R.string.color_and_style_desc), icon = Icons.Outlined.Palette, diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 2fa7c3d..06921b3 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -42,6 +42,8 @@ 移动到组 将 \"%1$s\" 分组中的所有订阅源移动至 \"%2$s\" 分组。 已全部移动至 \"%1$s\" 分组 + 重命名 + 已重命名为 \"%1$s\" 新建分组 名称 打开 %1$s diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 22030e9..cf763aa 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -42,6 +42,8 @@ Move to Group Move all feeds in the \"%1$s\" group to the \"%2$s\" group. Moved all to \"%1$s\" group + Rename + Renamed to \"%1$s\" Create New Group Name Open %1$s