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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 533 additions and 2 deletions

View File

@ -552,8 +552,27 @@ Copy user ID =
Copy game ID =
UserID copied to clipboard =
Game ID copied to clipboard! =
Friend name =
Player ID =
Please input a name for your friend! =
Please input a player ID for your friend! =
Are you sure you want to delete this friend? =
Paste player ID from clipboard =
Player name already used! =
Player ID already used! =
Player ID is incorrect =
Friends list =
Add friend =
Edit friend =
Friend name is already in your friends list! =
Player ID is already in your friends list! =
You have to write a name for your friend! =
You have to write an ID for your friend! =
You cannot add your own player ID in your friend list! =
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. =
Set current user =
Player ID from clipboard =
Player ID from friends list =
To create a multiplayer game, check the 'multiplayer' toggle in the New Game screen, and for each human player insert that player's user ID. =
You can assign your own user ID there easily, and other players can copy their user IDs here and send them to you for you to include them in the game. =
Once you've created your game, the Game ID gets automatically copied to your clipboard so you can send it to the other players. =

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(