Friends list (#7020)

* created files for friends list screen

* redone #3

* fixed some crashes and more additions

* changelog in the comment below

* Update .gitignore

included the FriendsList.json file

* changelog in the comment below, again

* optimized imports to hopefully fix the error in github build check

* replaced imports with the ones from master since OnlineMultiplayer.kt no longer exists

* imported ViewFriendsList

* used the right package for all friendsList related files and a little bit of cleanup

* check if friends list is empty before adding the select friend button

* check if the ID is correct when adding a new friend

* don't set null as playerID if no friend is selected

* added messageTexts for the input TextFields

* don't show already selected friends

* fixed checkmark not updating after selecting a friend

* you can't be your own friend! and other checks for edit button

* fix error

* replaced error type with enum, replaced nested ifs with when loop, added additional checks when editing friends

* fixed error, can't really tell how I managed to do this

* reorganized checks for adding and created settings variable to hopefully fix the github build error

* actually fixed github error

* it now removes selected friends from the list of selectable friends

* fixed not being able to change only the id of a friend

* made it look half decent

* cleanup + improved help button

* updated template.properties

* renamed variable

* some tweaks

* moved friends list to GameSettings

* tested every feature and fixed what didn't work

* removed redundant variables and final cleanup

* little improvements

* string improvements

* removed rightSideButton from ViewFriendsListScreen

* removed unnecesary uniformX and decreased padding to allow seeing more friends in ViewFriendsListScreen
This commit is contained in:
alexban011
2022-06-01 22:47:43 +03:00
committed by GitHub
parent 5a4f1b0903
commit e4e2696160
10 changed files with 533 additions and 2 deletions

View File

@ -0,0 +1,85 @@
package com.unciv.logic.multiplayer
import com.unciv.UncivGame
class FriendList {
private val settings = UncivGame.Current.settings
var friendList = settings.multiplayer.friendList
enum class ErrorType {
NOERROR,
NAME,
ID,
NONAME,
NOID,
YOURSELF,
ALREADYINLIST;
}
data class Friend(val name: String, val playerID: String) {
constructor() : this("", "")
}
fun add(friendName: String, playerID: String): ErrorType {
for(index in friendList.indices){
if (friendList[index].name == friendName) {
return ErrorType.NAME
} else if (friendList[index].playerID == playerID) {
return ErrorType.ID
}
}
if (friendName == "") {
return ErrorType.NONAME
} else if (playerID == "") {
return ErrorType.NOID
} else if (playerID == UncivGame.Current.settings.multiplayer.userId) {
return ErrorType.YOURSELF
}
friendList.add(Friend(friendName, playerID))
settings.save()
return ErrorType.NOERROR
}
fun edit(friend: Friend, name: String, playerID: String) {
friendList.remove(friend)
val editedFriend = Friend(name,playerID)
friendList.add(editedFriend)
settings.save()
}
fun delete(friend: Friend) {
friendList.remove(friend)
settings.save()
}
fun getFriendsList(): MutableList<Friend> {
return friendList
}
fun isFriendNameInFriendList(name: String): ErrorType {
for (index in friendList.indices) {
if (name == friendList[index].name) {
return ErrorType.ALREADYINLIST
}
}
return ErrorType.NOERROR
}
fun isFriendIDInFriendList(id: String): ErrorType {
for (index in friendList.indices) {
if (id == friendList[index].playerID) {
return ErrorType.ALREADYINLIST
}
}
return ErrorType.NOERROR
}
fun getFriendWithId(id: String): Friend? {
for (index in friendList.indices) {
if (id == friendList[index].playerID) {
return friendList[index]
}
}
return null
}
}

View File

@ -4,6 +4,7 @@ import com.badlogic.gdx.Application
import com.badlogic.gdx.Gdx
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.multiplayer.FriendList
import com.unciv.ui.utils.Fonts
import java.text.Collator
import java.time.Duration
@ -159,6 +160,7 @@ enum class LocaleCode(var language: String, var country: String) {
class GameSettingsMultiplayer {
var userId = ""
var server = Constants.dropboxMultiplayerServer
var friendList: MutableList<FriendList.Friend> = mutableListOf()
var turnCheckerEnabled = true
var turnCheckerPersistentNotificationEnabled = true
var turnCheckerDelay = Duration.ofMinutes(5)

View File

@ -0,0 +1,71 @@
package com.unciv.ui.multiplayer
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.TextField
import com.unciv.logic.IdChecker
import com.unciv.logic.multiplayer.FriendList
import com.unciv.models.translations.tr
import com.unciv.ui.pickerscreens.PickerScreen
import com.unciv.ui.popup.ToastPopup
import com.unciv.ui.utils.*
import java.util.*
class AddFriendScreen(backScreen: ViewFriendsListScreen) : PickerScreen() {
init {
val friendNameTextField = TextField("", skin)
val pastePlayerIDButton = "Paste player ID from clipboard".toTextButton()
val playerIDTextField = TextField("", skin)
val friendlist = FriendList()
topTable.add("Friend name".toLabel()).row()
friendNameTextField.messageText = "Please input a name for your friend!".tr()
topTable.add(friendNameTextField).pad(10f).padBottom(30f).width(stage.width/2).row()
pastePlayerIDButton.onClick {
playerIDTextField.text = Gdx.app.clipboard.contents
}
topTable.add("Player ID".toLabel()).row()
val gameIDTable = Table()
playerIDTextField.messageText = "Please input a player ID for your friend!".tr()
gameIDTable.add(playerIDTextField).pad(10f).width(2*stage.width/3 - pastePlayerIDButton.width)
gameIDTable.add(pastePlayerIDButton)
topTable.add(gameIDTable).padBottom(30f).row()
//CloseButton Setup
closeButton.setText("Back".tr())
closeButton.onClick {
backScreen.game.setScreen(backScreen)
}
//RightSideButton Setup
rightSideButton.setText("Add friend".tr())
rightSideButton.enable()
rightSideButton.onClick {
try {
UUID.fromString(IdChecker.checkAndReturnPlayerUuid(playerIDTextField.text))
} catch (ex: Exception) {
ToastPopup("Player ID is incorrect", this)
return@onClick
}
when (friendlist.add(friendNameTextField.text, playerIDTextField.text)) {
FriendList.ErrorType.NAME -> ToastPopup("Friend name is already in your friends list!", this)
FriendList.ErrorType.ID -> ToastPopup("Player ID is already in your friends list!", this)
FriendList.ErrorType.NONAME -> ToastPopup("You have to write a name for your friend!", this)
FriendList.ErrorType.NOID -> ToastPopup("You have to write an ID for your friend!", this)
FriendList.ErrorType.YOURSELF -> ToastPopup("You cannot add your own player ID in your friend list!", this)
else -> {
backScreen.game.setScreen(backScreen)
backScreen.refreshFriendsList()
}
}
}
}
}

View File

@ -0,0 +1,85 @@
package com.unciv.ui.multiplayer
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.TextField
import com.unciv.logic.IdChecker
import com.unciv.logic.multiplayer.FriendList
import com.unciv.models.translations.tr
import com.unciv.ui.pickerscreens.PickerScreen
import com.unciv.ui.popup.ToastPopup
import com.unciv.ui.popup.YesNoPopup
import com.unciv.ui.utils.*
import java.util.*
class EditFriendScreen(selectedFriend: FriendList.Friend, backScreen: ViewFriendsListScreen) : PickerScreen() {
init {
val friendNameTextField = TextField(selectedFriend.name, skin)
val pastePlayerIDButton = "Player ID from clipboard".toTextButton()
val playerIDTextField = TextField(selectedFriend.playerID, skin)
val deleteFriendButton = "Delete".toTextButton()
val friendlist = FriendList()
topTable.add("Friend name".toLabel()).row()
friendNameTextField.messageText = "Please input a name for your friend!".tr()
topTable.add(friendNameTextField).pad(10f).padBottom(30f).width(stage.width/2).row()
pastePlayerIDButton.onClick {
playerIDTextField.text = Gdx.app.clipboard.contents
}
topTable.add("Player ID".toLabel()).row()
val gameIDTable = Table()
playerIDTextField.messageText = "Please input a player ID for your friend!".tr()
gameIDTable.add(playerIDTextField).pad(10f).width(2*stage.width/3 - pastePlayerIDButton.width)
gameIDTable.add(pastePlayerIDButton)
topTable.add(gameIDTable).padBottom(30f).row()
deleteFriendButton.onClick {
val askPopup = YesNoPopup("Are you sure you want to delete this friend?", {
friendlist.delete(selectedFriend)
backScreen.game.setScreen(backScreen)
backScreen.refreshFriendsList()
}, this)
askPopup.open()
}.apply { color = Color.RED }
topTable.add(deleteFriendButton)
//CloseButton Setup
closeButton.setText("Back".tr())
closeButton.onClick {
backScreen.game.setScreen(backScreen)
}
//RightSideButton Setup
rightSideButton.setText("Save".tr())
rightSideButton.enable()
rightSideButton.onClick {
// if no edits have been made, go back to friends list
if (selectedFriend.name == friendNameTextField.text && selectedFriend.playerID == playerIDTextField.text) {
backScreen.game.setScreen(backScreen)
backScreen.refreshFriendsList()
}
if (friendlist.isFriendNameInFriendList(friendNameTextField.text) == FriendList.ErrorType.ALREADYINLIST
&& friendlist.isFriendIDInFriendList(playerIDTextField.text) == FriendList.ErrorType.ALREADYINLIST) {
ToastPopup("Player name already used!", this)
return@onClick
}
if (friendlist.isFriendIDInFriendList(playerIDTextField.text) == FriendList.ErrorType.ALREADYINLIST
&& friendlist.isFriendNameInFriendList(friendNameTextField.text) == FriendList.ErrorType.ALREADYINLIST) {
ToastPopup("Player ID already used!", this)
return@onClick
}
try {
UUID.fromString(IdChecker.checkAndReturnPlayerUuid(playerIDTextField.text))
} catch (ex: Exception) {
ToastPopup("Player ID is incorrect", this)
return@onClick
}
friendlist.edit(selectedFriend, friendNameTextField.text, playerIDTextField.text)
backScreen.game.setScreen(backScreen)
backScreen.refreshFriendsList()
}
}
}

View File

@ -34,6 +34,9 @@ class MultiplayerScreen(previousScreen: BaseScreen) : PickerScreen() {
private val copyUserIdText = "Copy user ID"
private val copyUserIdButton = createCopyUserIdButton()
private val friendsListText = "Friends List"
private val friendsListButton = createFriendsListButton()
private val refreshText = "Refresh list"
private val refreshButton = createRefreshButton()
@ -74,6 +77,7 @@ class MultiplayerScreen(previousScreen: BaseScreen) : PickerScreen() {
table.add(copyGameIdButton).row()
table.add(editButton).row()
table.add(addGameButton).padBottom(30f).row()
table.add(friendsListButton).padBottom(30f).row()
table.add(refreshButton).row()
return table
}
@ -112,6 +116,14 @@ class MultiplayerScreen(previousScreen: BaseScreen) : PickerScreen() {
return btn
}
fun createFriendsListButton(): TextButton {
val btn = friendsListText.toTextButton()
btn.onClick {
game.setScreen(ViewFriendsListScreen(this))
}
return btn
}
private fun createCopyUserIdButton(): TextButton {
val btn = copyUserIdText.toTextButton()
btn.onClick {

View File

@ -0,0 +1,82 @@
package com.unciv.ui.multiplayer
import com.badlogic.gdx.scenes.scene2d.ui.*
import com.unciv.logic.multiplayer.FriendList
import com.unciv.ui.pickerscreens.PickerScreen
import com.unciv.ui.popup.Popup
import com.unciv.ui.utils.*
import com.unciv.ui.utils.AutoScrollPane as ScrollPane
class ViewFriendsListScreen(previousScreen: BaseScreen) : PickerScreen() {
private val rightSideTable = Table()
private val leftSideTable = Table()
private var friendsTable = Table()
private val addFriendButton = "Add friend".toTextButton()
private val editFriendButton = "Edit friend".toTextButton()
private val friendsList = FriendList().getFriendsList()
private var listOfFriendsButtons = arrayListOf<TextButton>()
private lateinit var selectedFriend: FriendList.Friend
init {
setDefaultCloseAction(previousScreen)
rightSideButton.remove()
//Help Button Setup
val tab = Table()
val helpButton = "Help".toTextButton()
helpButton.onClick {
val helpPopup = Popup(this)
helpPopup.addGoodSizedLabel("To add a friend, ask him to send you his player ID.\nClick the 'Add friend' button.\nInsert his player ID and a name for him.\nThen click the 'Add friend' button again.\n\nAfter that you will see him in your friends list.\n\nA new button will appear when creating a new\nmultiplayer game, which allows you to select your friend.").row()
helpPopup.addCloseButton()
helpPopup.open()
}
tab.add(helpButton)
tab.x = (stage.width - helpButton.width)
tab.y = (stage.height - helpButton.height)
stage.addActor(tab)
val mainTable = Table()
mainTable.add(ScrollPane(leftSideTable).apply { setScrollingDisabled(true, false) }).height(stage.height * 2 / 3)
mainTable.add(rightSideTable)
topTable.add(mainTable).row()
scrollPane.setScrollingDisabled(false, true)
rightSideTable.defaults().fillX()
rightSideTable.defaults().pad(20.0f)
addFriendButton.onClick {
game.setScreen(AddFriendScreen(this))
}
rightSideTable.add(addFriendButton).padBottom(10f).row()
editFriendButton.onClick {
game.setScreen(EditFriendScreen(selectedFriend,this))
editFriendButton.disable()
}
rightSideTable.add(editFriendButton).padBottom(30f).row()
editFriendButton.disable()
refreshFriendsList()
}
fun refreshFriendsList() {
listOfFriendsButtons.clear()
friendsTable.clear()
for (index in friendsList.indices) {
listOfFriendsButtons.add(friendsList[index].name.toTextButton())
listOfFriendsButtons[index].onClick {
selectedFriend = friendsList[index]
editFriendButton.enable()
}
friendsTable.add(listOfFriendsButtons[index]).padBottom(10f).row()
}
leftSideTable.clear()
leftSideTable.add(friendsTable)
}
}

View File

@ -0,0 +1,38 @@
package com.unciv.ui.newgamescreen
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.Touchable
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Align
import com.unciv.Constants
import com.unciv.logic.multiplayer.FriendList
import com.unciv.ui.utils.*
class FriendTable(val friend: FriendList.Friend, width: Float, minHeight: Float)
: Table() {
val innerTable = Table()
init {
val innerColor = Color.WHITE//because 0xFFFFFFFF doesn't work for some reason
val totalPadding = 30f
val internalWidth = width - totalPadding
val titleTable = Table()
val titleText = friend.name
val friendDisplayNameMaxWidth = internalWidth - 70f // for the friend indicator with padding
val friendDisplayLabel = WrappableLabel(titleText, friendDisplayNameMaxWidth, innerColor, Constants.headingFontSize)
if (friendDisplayLabel.prefWidth > friendDisplayNameMaxWidth - 2f) {
friendDisplayLabel.wrap = true
titleTable.add(friendDisplayLabel).width(friendDisplayNameMaxWidth)
} else {
titleTable.add(friendDisplayLabel).align(Align.center).pad(10f,0f)
}
innerTable.add(titleTable).growX().fillY().row()
add(innerTable).width(width).minHeight(minHeight - totalPadding)
touchable = Touchable.enabled
}
}

View File

@ -28,7 +28,6 @@ import java.net.URL
import java.util.*
import com.unciv.ui.utils.AutoScrollPane as ScrollPane
class NewGameScreen(
private val previousScreen: BaseScreen,
_gameSetupInfo: GameSetupInfo? = null

View File

@ -22,6 +22,7 @@ import com.unciv.ui.audio.MusicMood
import com.unciv.ui.audio.MusicTrackChooserFlags
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.mapeditor.GameParametersScreen
import com.unciv.logic.multiplayer.FriendList
import com.unciv.ui.pickerscreens.PickerScreen
import com.unciv.ui.popup.Popup
import com.unciv.ui.utils.*
@ -46,6 +47,7 @@ class PlayerPickerTable(
): Table() {
val playerListTable = Table()
val civBlocksWidth = if(blockWidth <= 10f) previousScreen.stage.width / 3 - 5f else blockWidth
val friendsBlocksWidth = if(blockWidth <= 10f) previousScreen.stage.width / 5 - 5f else blockWidth / 2
/** Locks player table for editing, currently unused, was previously used for scenarios and could be useful in the future.*/
var locked = false
@ -53,6 +55,8 @@ class PlayerPickerTable(
/** No random civilization is available, used during map editing.*/
var noRandom = false
private val friendList = FriendList()
init {
for (player in gameParameters.players)
player.playerId = "" // This is to stop people from getting other users' IDs and cheating with them in multiplayer games
@ -174,6 +178,7 @@ class PlayerPickerTable(
errorLabel.apply { setText("");setFontColor(Color.RED) }
}
}
onPlayerIdTextUpdated()
playerIdTextField.addListener { onPlayerIdTextUpdated(); true }
val currentUserId = UncivGame.Current.settings.multiplayer.userId
@ -189,7 +194,16 @@ class PlayerPickerTable(
playerIdTextField.text = Gdx.app.clipboard.contents
onPlayerIdTextUpdated()
}
playerTable.add(copyFromClipboardButton).colspan(3).fillX().pad(5f)
playerTable.add(copyFromClipboardButton).right().colspan(3).fillX().pad(5f).row()
//check if friends list is empty before adding the select friend button
if (friendList.friendList.isNotEmpty()) {
val selectPlayerFromFriendsList = "Player ID from friends list".toTextButton()
selectPlayerFromFriendsList.onClick {
popupFriendPicker(player)
}
playerTable.add(selectPlayerFromFriendsList).left().colspan(3).fillX().pad(5f)
}
}
return playerTable
@ -216,6 +230,17 @@ class PlayerPickerTable(
return nationTable
}
/**
* Opens Friend picking popup with all friends,
* currently available for [player] to choose, depending on current
* friends list and if another friend is selected.
* @param player current player
*/
private fun popupFriendPicker(player: Player) {
FriendPickerPopup(this, player).open()
update()
}
/**
* Opens Nation picking popup with all nations,
* currently available for [player] to choose, depending on current
@ -241,6 +266,119 @@ class PlayerPickerTable(
.filter { it.isMajorCiv() }
.filter { it.name == dontSkipNation || gameParameters.players.none { player -> player.chosenCiv == it.name } }
/**
* Returns a list of available friends.
* Skips friends already chosen.
*
* @return [Sequence] of available [FriendList.Friend]s
*/
internal fun getAvailableFriends(): Sequence<FriendList.Friend> {
val friendListWithRemovedFriends = friendList.friendList.toMutableList()
for (index in gameParameters.players.indices) {
val currentFriendId = previousScreen.gameSetupInfo.gameParameters.players[index].playerId
friendListWithRemovedFriends.remove(friendList.getFriendWithId(currentFriendId))
}
return friendListWithRemovedFriends.asSequence()
}
}
private class FriendPickerPopup(
private val playerPicker: PlayerPickerTable,
private val player: Player
) : Popup(playerPicker.previousScreen as BaseScreen) {
companion object {
// These are used for the Close/OK buttons in the lower left/right corners:
const val buttonsCircleSize = 70f
const val buttonsIconSize = 50f
const val buttonsOffsetFromEdge = 5f
val buttonsBackColor: Color = Color.BLACK.cpy().apply { a = 0.67f }
}
// This Popup's body has two halves of same size, either side by side or arranged vertically
// depending on screen proportions - determine height for one of those
private val partHeight = screen.stage.height * (if (screen.isNarrowerThan4to3()) 0.3f else 0.4f)
private val friendsBlocksWidth = playerPicker.friendsBlocksWidth
private val friendListTable = Table()
private val friendListScroll = ScrollPane(friendListTable)
private val friendDetailsTable = Table()
private val friendDetailsScroll = ScrollPane(friendDetailsTable)
var selectedFriend: FriendList.Friend? = null
init {
friendListScroll.setOverscroll(false, false)
add(friendListScroll).size( friendsBlocksWidth + 10f, partHeight )
// +10, because the friend table has a 5f pad, for a total of +10f
if (screen.isNarrowerThan4to3()) row()
friendDetailsScroll.setOverscroll(false, false)
add(friendDetailsScroll).size(friendsBlocksWidth + 10f, partHeight) // Same here, see above
val friends = ArrayList<FriendList.Friend>()
friends += playerPicker.getAvailableFriends()
var friendsListScrollY = 0f
var currentY = 0f
for (friend in friends) {
val friendTable = FriendTable(friend, friendsBlocksWidth, 0f) // no need for min height
val cell = friendListTable.add(friendTable)
cell.padTop(20f)
currentY += cell.padBottom + cell.prefHeight + cell.padTop
cell.row()
friendTable.onClick {
setFriendDetails(friend)
}
}
friendListScroll.layout()
pack()
if (friendsListScrollY > 0f) {
// center the selected friend vertically, getRowHeight safe because friendListScrollY > 0f ensures at least 1 row
friendsListScrollY -= (friendListScroll.height - friendListTable.getRowHeight(0)) / 2
friendListScroll.scrollY = friendsListScrollY.coerceIn(0f, friendListScroll.maxY)
}
val closeButton = "OtherIcons/Close".toImageButton(Color.FIREBRICK)
closeButton.onClick { close() }
closeButton.setPosition(buttonsOffsetFromEdge, buttonsOffsetFromEdge, Align.bottomLeft)
innerTable.addActor(closeButton)
keyPressDispatcher[KeyCharAndCode.BACK] = { close() }
val okButton = "OtherIcons/Checkmark".toImageButton(Color.LIME)
okButton.onClick { returnSelected() }
okButton.setPosition(innerTable.width - buttonsOffsetFromEdge, buttonsOffsetFromEdge, Align.bottomRight)
innerTable.addActor(okButton)
friendDetailsTable.touchable = Touchable.enabled
friendDetailsTable.onClick { returnSelected() }
}
private fun String.toImageButton(overColor: Color): Group {
val style = ImageButton.ImageButtonStyle()
val image = ImageGetter.getDrawable(this)
style.imageUp = image
style.imageOver = image.tint(overColor)
val button = ImageButton(style)
button.setSize(buttonsIconSize, buttonsIconSize)
return button.surroundWithCircle(buttonsCircleSize, false, buttonsBackColor)
}
private fun setFriendDetails(friend: FriendList.Friend) {
friendDetailsTable.clearChildren() // .clear() also clears listeners!
friendDetailsTable.add(FriendTable(friend, friendsBlocksWidth, partHeight))
selectedFriend = friend
}
fun returnSelected() {
if (selectedFriend == null) {
close()
return
}
player.playerId = selectedFriend?.playerID.toString()
close()
playerPicker.update()
}
}
private class NationPickerPopup(