diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 00000000..324450d2
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 512528d6..506cca0b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -118,6 +118,7 @@ dependencies {
androidTestImplementation('androidx.test.espresso:espresso-contrib:3.4.0') {
exclude module: "protobuf-lite"
}
+ implementation('androidx.test.espresso:espresso-idling-resource:3.4.0')
// Import the BoM for the Firebase platform
implementation platform('com.google.firebase:firebase-bom:30.0.2')
diff --git a/app/src/androidTest/java/ch/epfl/sweng/rps/CacheTests.kt b/app/src/androidTest/java/ch/epfl/sweng/rps/CacheTests.kt
index eb21bf97..b6b8d093 100644
--- a/app/src/androidTest/java/ch/epfl/sweng/rps/CacheTests.kt
+++ b/app/src/androidTest/java/ch/epfl/sweng/rps/CacheTests.kt
@@ -8,13 +8,13 @@ import androidx.annotation.ColorInt
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import ch.epfl.sweng.rps.TestUtils.initializeForTest
+import ch.epfl.sweng.rps.remote.Env
import ch.epfl.sweng.rps.models.remote.LeaderBoardInfo
import ch.epfl.sweng.rps.models.remote.User
import ch.epfl.sweng.rps.models.ui.UserStat
import ch.epfl.sweng.rps.persistence.Cache
import ch.epfl.sweng.rps.persistence.PrivateStorage
import ch.epfl.sweng.rps.persistence.Storage
-import ch.epfl.sweng.rps.remote.Env
import ch.epfl.sweng.rps.services.ServiceLocator
import com.google.firebase.ktx.Firebase
import io.mockk.mockk
diff --git a/app/src/androidTest/java/ch/epfl/sweng/rps/ExampleInstrumentedTest.kt b/app/src/androidTest/java/ch/epfl/sweng/rps/ExampleInstrumentedTest.kt
index b01c62b3..993fff47 100644
--- a/app/src/androidTest/java/ch/epfl/sweng/rps/ExampleInstrumentedTest.kt
+++ b/app/src/androidTest/java/ch/epfl/sweng/rps/ExampleInstrumentedTest.kt
@@ -1,11 +1,13 @@
package ch.epfl.sweng.rps
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
-import org.junit.Assert.assertEquals
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
import org.junit.Test
import org.junit.runner.RunWith
+import org.junit.Assert.*
+
/**
* Instrumented test, which will execute on an Android device.
*
diff --git a/app/src/androidTest/java/ch/epfl/sweng/rps/LeaderBoardFragmentTest.kt b/app/src/androidTest/java/ch/epfl/sweng/rps/LeaderBoardFragmentTest.kt
index 4129ed2b..4c833914 100644
--- a/app/src/androidTest/java/ch/epfl/sweng/rps/LeaderBoardFragmentTest.kt
+++ b/app/src/androidTest/java/ch/epfl/sweng/rps/LeaderBoardFragmentTest.kt
@@ -121,4 +121,3 @@ class LeaderBoardFragmentTest {
}
}
-
diff --git a/app/src/androidTest/java/ch/epfl/sweng/rps/SettingsPageTest.kt b/app/src/androidTest/java/ch/epfl/sweng/rps/SettingsPageTest.kt
index 001fe7c2..c4268c43 100644
--- a/app/src/androidTest/java/ch/epfl/sweng/rps/SettingsPageTest.kt
+++ b/app/src/androidTest/java/ch/epfl/sweng/rps/SettingsPageTest.kt
@@ -92,6 +92,7 @@ class SettingsPageTest {
}
+
@Test
fun testSettingsPage() {
onView(withId(R.id.settings)).check(matches(isDisplayed()))
diff --git a/app/src/androidTest/java/ch/epfl/sweng/rps/StatisticsFragmentTest.kt b/app/src/androidTest/java/ch/epfl/sweng/rps/StatisticsFragmentTest.kt
index a80efd64..b6bb5081 100644
--- a/app/src/androidTest/java/ch/epfl/sweng/rps/StatisticsFragmentTest.kt
+++ b/app/src/androidTest/java/ch/epfl/sweng/rps/StatisticsFragmentTest.kt
@@ -179,5 +179,4 @@ class StatisticsFragmentTest {
}
}
-}
-
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/ch/epfl/sweng/rps/services/FirebaseGameServiceTest.kt b/app/src/androidTest/java/ch/epfl/sweng/rps/services/FirebaseGameServiceTest.kt
index 60934243..92337c9d 100644
--- a/app/src/androidTest/java/ch/epfl/sweng/rps/services/FirebaseGameServiceTest.kt
+++ b/app/src/androidTest/java/ch/epfl/sweng/rps/services/FirebaseGameServiceTest.kt
@@ -1,11 +1,11 @@
package ch.epfl.sweng.rps.services
import ch.epfl.sweng.rps.TestUtils.initializeForTest
-import ch.epfl.sweng.rps.models.remote.Game
+import ch.epfl.sweng.rps.remote.FirebaseReferences
import ch.epfl.sweng.rps.models.remote.GameMode
+import ch.epfl.sweng.rps.models.remote.Game
import ch.epfl.sweng.rps.models.remote.Hand
import ch.epfl.sweng.rps.models.remote.Round
-import ch.epfl.sweng.rps.remote.FirebaseReferences
import ch.epfl.sweng.rps.remote.FirebaseRepository
import ch.epfl.sweng.rps.utils.consume
import com.google.firebase.Timestamp
diff --git a/app/src/androidTest/java/ch/epfl/sweng/rps/ui/friends/FriendsFragmentTest.kt b/app/src/androidTest/java/ch/epfl/sweng/rps/ui/friends/FriendsFragmentTest.kt
index f93879cf..dfae56d9 100644
--- a/app/src/androidTest/java/ch/epfl/sweng/rps/ui/friends/FriendsFragmentTest.kt
+++ b/app/src/androidTest/java/ch/epfl/sweng/rps/ui/friends/FriendsFragmentTest.kt
@@ -1,71 +1,118 @@
package ch.epfl.sweng.rps.ui.friends
+import android.content.Intent
+import android.service.autofill.Validators.not
import android.view.View
import android.widget.ImageButton
import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
-import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.Espresso.pressBack
+import androidx.test.espresso.action.ViewActions.*
+import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition
+import androidx.test.espresso.matcher.RootMatchers.isDialog
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.platform.app.InstrumentationRegistry
+import ch.epfl.sweng.rps.*
+import ch.epfl.sweng.rps.TestUtils.initializeForTest
+import ch.epfl.sweng.rps.remote.Env
+import ch.epfl.sweng.rps.models.remote.FriendRequest
+import ch.epfl.sweng.rps.models.remote.User
+import com.google.firebase.Timestamp
import ch.epfl.sweng.rps.ActivityScenarioRuleWithSetup
-import ch.epfl.sweng.rps.ActivityScenarioRuleWithSetup.Companion.defaultTestFlow
import ch.epfl.sweng.rps.MainActivity
import ch.epfl.sweng.rps.R
-import ch.epfl.sweng.rps.TestFlow
-import ch.epfl.sweng.rps.TestUtils.initializeForTest
-import ch.epfl.sweng.rps.models.remote.User
-import ch.epfl.sweng.rps.models.ui.FakeFriendsData
-import ch.epfl.sweng.rps.persistence.Cache
-import ch.epfl.sweng.rps.persistence.PrivateStorage
-import ch.epfl.sweng.rps.persistence.Storage
-import ch.epfl.sweng.rps.remote.Env
import ch.epfl.sweng.rps.remote.LocalRepository
import ch.epfl.sweng.rps.services.ServiceLocator
import com.google.firebase.ktx.Firebase
-import kotlinx.coroutines.runBlocking
import org.hamcrest.Matcher
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
+import kotlin.test.assertIsNot
class FriendsFragmentTest {
- val LIST_ITEM = FakeFriendsData.myFriendsData.size - 1
- val thisFriend = FakeFriendsData.myFriendsData[LIST_ITEM]
- lateinit var cache: Cache
- lateinit var storage: Storage
-
+ private fun createIntent(): Intent {
+ Firebase.initializeForTest()
+ val i: Intent = Intent(
+ InstrumentationRegistry.getInstrumentation().targetContext,
+ MainActivity::class.java
+ )
+ return i
+ }
@get:Rule
- val activityRule = ActivityScenarioRuleWithSetup(MainActivity::class.java,
- defaultTestFlow then TestFlow.onlySetup { ServiceLocator.localRepository.setCurrentUid("test") }
- )
+ val testRule = ActivityScenarioRuleWithSetup.default(createIntent())
@Before
fun setUp() {
ServiceLocator.setCurrentEnv(Env.Test)
- ServiceLocator.localRepository.setCurrentUid("test")
- Firebase.initializeForTest()
- cache = Cache.initialize(InstrumentationRegistry.getInstrumentation().targetContext)
- storage = PrivateStorage(InstrumentationRegistry.getInstrumentation().targetContext)
- runBlocking {
- cache.setUserDetails(User(uid = "user_test_uid", username = "test name"))
- }
+ IdlingRegistry.getInstance().register(EspressoIdlingResource.countingIdlingResource)
+
+ val repo = ServiceLocator.getInstance().repository as LocalRepository
+
+ repo.setCurrentUid("player1")
+ repo.users.clear()
+ repo.friendRequests.clear()
+ repo.gamesMap.clear()
+
+
+ repo.users["player1"] = User(
+ "player1",
+ "player1",
+ "public",
+ true,
+ "p1@example.com"
+ )
+ repo.users["player2"] = User(
+ "player2",
+ "player2",
+ "public",
+ true,
+ "p2@example.com"
+ )
+ repo.users["player3"] = User(
+ "player3",
+ "player3",
+ "public",
+ true,
+ "p3@example.com"
+ )
+ repo.users["player4"] = User(
+ "player4",
+ "player4",
+ "public",
+ true,
+ "p4@example.com"
+ )
+
+ val friends: List = listOf(
+ FriendRequest(listOf("player2","player1"), Timestamp.now() , FriendRequest.Status.ACCEPTED, "player2"),
+ FriendRequest(listOf("player3","player1"), Timestamp.now(), FriendRequest.Status.PENDING, "player3"),
+ FriendRequest(listOf("player1","player4"), Timestamp.now(), FriendRequest.Status.ACCEPTED,"player4")
+ )
+
+ repo.friendRequests.addAll(friends)
}
@After
fun tearDown() {
- val repo = ServiceLocator.getInstance().repository as LocalRepository
- repo.setCurrentUid(null)
ServiceLocator.setCurrentEnv(Env.Prod)
+ IdlingRegistry.getInstance().unregister(EspressoIdlingResource.countingIdlingResource)
+ }
+
+ @Test
+ fun testEnv() {
+ assert(ServiceLocator.getCurrentEnv() == Env.Test)
}
@Test
- fun checkFriendsFragment() {
+ fun checkFriendsFragment(){
onView(withId(R.id.nav_friends)).perform(click())
onView(withId(R.id.fragment_friends)).check(matches(isDisplayed()))
}
@@ -77,171 +124,134 @@ class FriendsFragmentTest {
}
@Test
- fun test_FriendInfoFragmentShown_onInfoButtonClick() {
+ fun test_CheckFriendInfo_onInfoButtonClick() {
+ val gamesPlayed = 0
+ val gamesWon = 0
+ val winRate ="0.0"
+
onView(withId(R.id.nav_friends)).perform(click())
onView(withId(R.id.friendListRecyclerView))
- .perform(
- actionOnItemAtPosition(
- LIST_ITEM,
- ClickButtonAction.clickInfoButton(R.id.infoButton)
- )
- )
-
- onView(withId(R.id.fragment_info_page)).check(matches(isDisplayed()))
- }
+ .perform(actionOnItemAtPosition(0,ClickButtonAction.clickInfoButton(R.id.infoButton)))
- @Test
- fun test_GameFragmentShown_onPlayButtonClick() {
- onView(withId(R.id.nav_friends)).perform(click())
+ onView(withId(R.id.userName_infoPage)).check(matches(withText("player2")))
+ onView(withId(R.id.gamesPlayedText_infoPage)).check(matches(withText("Games Played: $gamesPlayed")))
+ onView(withId(R.id.gamesWonText_infoPage)).check(matches(withText("Games Won: $gamesWon")))
+ onView(withId(R.id.winRateText_infoPage)).check(matches(withText("Win Rate: $winRate%")))
- onView(withId(R.id.friendListRecyclerView))
- .perform(
- actionOnItemAtPosition(
- LIST_ITEM,
- ClickButtonAction.clickPlayButton(R.id.playButton)
- )
- )
-
- onView(withId(R.id.fragment_game)).check(matches(isDisplayed()))
}
@Test
- fun test_CorrectGamesPlayedShows_onInfoButtonClick() {
- val gamesPlayed = thisFriend.gamesPlayed
+ fun test_goesToGameFragment_onChoosing1GameMode(){
onView(withId(R.id.nav_friends)).perform(click())
onView(withId(R.id.friendListRecyclerView))
- .perform(
- actionOnItemAtPosition(
- LIST_ITEM,
- ClickButtonAction.clickInfoButton(R.id.infoButton)
- )
- )
+ .perform(actionOnItemAtPosition(0,ClickButtonAction.clickPlayButton(R.id.playButton)))
- onView(withId(R.id.gamesPlayedText_infoPage)).check(matches(withText("Games Played: $gamesPlayed")))
+ onView(withId(R.id.oneGameRadioBtn)).inRoot(isDialog()).perform(click())
+ onView(withId(R.id.confirmButton)).inRoot(isDialog()).perform(click())
+
+ onView(withId(R.id.matchmaking_fragment)).check(matches(isDisplayed()))
}
@Test
- fun test_CorrectGamesWonShows_onInfoButtonClick() {
- val gamesWon = thisFriend.gamesWon
+ fun test_goesToGameFragment_onChoosing5GameMode(){
onView(withId(R.id.nav_friends)).perform(click())
onView(withId(R.id.friendListRecyclerView))
- .perform(
- actionOnItemAtPosition(
- LIST_ITEM,
- ClickButtonAction.clickInfoButton(R.id.infoButton)
- )
- )
+ .perform(actionOnItemAtPosition(0,ClickButtonAction.clickPlayButton(R.id.playButton)))
- onView(withId(R.id.gamesWonText_infoPage)).check(matches(withText("Games Won: $gamesWon")))
+ onView(withId(R.id.fiveGameRadioBtn)).inRoot(isDialog()).perform(click())
+ onView(withId(R.id.confirmButton)).inRoot(isDialog()).perform(click())
+
+ onView(withId(R.id.matchmaking_fragment)).check(matches(isDisplayed()))
}
@Test
- fun test_CorrectUserNameShows_onInfoButtonClick() {
-
+ fun test_dialogFragmentCloses_onClickingCancel(){
onView(withId(R.id.nav_friends)).perform(click())
onView(withId(R.id.friendListRecyclerView))
- .perform(
- actionOnItemAtPosition(
- LIST_ITEM,
- ClickButtonAction.clickInfoButton(R.id.infoButton)
- )
- )
-
- onView(withId(R.id.userName_infoPage)).check(matches(withText(thisFriend.username)))
+ .perform(actionOnItemAtPosition(0,ClickButtonAction.clickPlayButton(R.id.playButton)))
+
+ onView(withId(R.id.cancelButton)).inRoot(isDialog()).perform(click())
+
+ onView(withId(R.id.fragment_game_mode_dialog)).check(doesNotExist())
}
@Test
- fun test_CorrectWinRateShows_onInfoButtonClick() {
- val winRate = thisFriend.winRate
+ fun test_nothingHappens_onClickingSubmitWithoutSelection(){
onView(withId(R.id.nav_friends)).perform(click())
onView(withId(R.id.friendListRecyclerView))
- .perform(
- actionOnItemAtPosition(
- LIST_ITEM,
- ClickButtonAction.clickInfoButton(R.id.infoButton)
- )
- )
+ .perform(actionOnItemAtPosition(0,ClickButtonAction.clickPlayButton(R.id.playButton)))
+
+ onView(withId(R.id.confirmButton)).inRoot(isDialog()).perform(click())
+
+ onView(withId(R.id.fragment_game_mode_dialog)).check(matches(isDisplayed()))
- onView(withId(R.id.winRateText_infoPage)).check(matches(withText("Win Rate: $winRate%")))
}
+
@Test
- fun test_offlineStatusShows_onInfoButtonClick() {
+ fun test_returnsToFriendFragment_onBackButtonClick(){
onView(withId(R.id.nav_friends)).perform(click())
onView(withId(R.id.friendListRecyclerView))
- .perform(
- actionOnItemAtPosition(
- LIST_ITEM,
- ClickButtonAction.clickInfoButton(R.id.infoButton)
- )
- )
-
- onView(withId(R.id.offlineImage_infoPage)).check(matches(isDisplayed()))
+ .perform(actionOnItemAtPosition(0,ClickButtonAction.clickInfoButton(R.id.infoButton)))
+
+ onView(withId(R.id.infoPage_backButton)).perform(click())
+
+ onView(withId(R.id.fragment_friends)).check(matches(isDisplayed()))
}
@Test
- fun test_onlineStatusShows_onInfoButtonClick() {
+ fun test_showsMyFriendReqs_onMyRequestButtonClicked(){
onView(withId(R.id.nav_friends)).perform(click())
- onView(withId(R.id.friendListRecyclerView))
- .perform(
- actionOnItemAtPosition(
- 0,
- ClickButtonAction.clickInfoButton(R.id.infoButton)
- )
- )
-
- onView(withId(R.id.onlineImage_infoPage)).check(matches(isDisplayed()))
+ onView(withId(R.id.requestButton)).perform(click())
+ onView(withId(R.id.myFriendReqButton)).perform(click())
+
+ onView(withId(R.id.myReqsRecyclerView)).check(matches(isDisplayed()))
}
@Test
- fun test_returnsToFriendFragment_onBackButtonClick() {
+ fun test_showsAddFriendFragment_onAddFriendButtonClicked(){
onView(withId(R.id.nav_friends)).perform(click())
- onView(withId(R.id.friendListRecyclerView))
- .perform(
- actionOnItemAtPosition(
- LIST_ITEM,
- ClickButtonAction.clickInfoButton(R.id.infoButton)
- )
- )
-
- onView(withId(R.id.infoPage_backButton)).perform(click())
+ onView(withId(R.id.requestButton)).perform(click())
+ onView(withId(R.id.addFriendsButton)).perform(click())
- onView(withId(R.id.fragment_friends)).check(matches(isDisplayed()))
+ onView(withId(R.id.addFriendFragment)).check(matches(isDisplayed()))
}
@Test
- fun test_goesToGameFragment_onPlayButtonClick() {
+ fun test_FriendAdded_onAcceptedRequest(){
onView(withId(R.id.nav_friends)).perform(click())
+ onView(withId(R.id.requestButton)).perform(click())
+ onView(withId(R.id.myFriendReqButton)).perform(click())
- onView(withId(R.id.friendListRecyclerView))
- .perform(
- actionOnItemAtPosition(
- LIST_ITEM,
- ClickButtonAction.clickInfoButton(R.id.infoButton)
- )
- )
+ onView(withId(R.id.myReqsRecyclerView))
+ .perform(actionOnItemAtPosition(0,ClickButtonAction.clickInfoButton(R.id.acceptButton)))
- onView(withId(R.id.infoPage_playButton)).perform(click())
+ pressBack()
- onView(withId(R.id.fragment_game)).check(matches(isDisplayed()))
+ onView(withId(R.id.friendListRecyclerView)).check(matches(isDisplayed()))
+ onView(withId(R.id.friendListRecyclerView))
+ .perform(actionOnItemAtPosition(1,ClickButtonAction.clickInfoButton(R.id.infoButton)))
+
+ onView(withId(R.id.userName_infoPage)).check(matches(withText("player3")))
}
-}
+
+}
class ClickButtonAction {
companion object {
fun clickInfoButton(childId: Int): ViewAction {
return object : ViewAction {
override fun getConstraints(): Matcher {
- return isAssignableFrom(ImageButton::class.java)
+ return isAssignableFrom(ImageButton::class.java)
}
override fun getDescription(): String {
diff --git a/app/src/credentials.json b/app/src/credentials.json
index 5ec6e134..5b7d8ef9 100644
--- a/app/src/credentials.json
+++ b/app/src/credentials.json
@@ -1,10 +1 @@
-{
- "web": {
- "client_id": "896605409351-sfmsu9jivh9dg2rn9ciu9jevv3van4l1.apps.googleusercontent.com",
- "project_id": "rps-sdp",
- "auth_uri": "https://accounts.google.com/o/oauth2/auth",
- "token_uri": "https://oauth2.googleapis.com/token",
- "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
- "client_secret": "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"
- }
-}
\ No newline at end of file
+{"web":{"client_id":"896605409351-sfmsu9jivh9dg2rn9ciu9jevv3van4l1.apps.googleusercontent.com","project_id":"rps-sdp","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"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"}}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5128b9d6..c2268418 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -44,7 +44,9 @@
+ android:theme="@style/Theme.RockPaperScissors.NoActionBar"
+ android:windowSoftInputMode="adjustNothing"
+ />
? = null
private var leaderBoardData: List? = null
+ private lateinit var myFriends: List
+ private lateinit var myFriendReqs: List
private val repo get() = repoOverride ?: ServiceLocator.getInstance().repository
private var repoOverride: Repository? = null
@@ -95,6 +100,7 @@ class Cache private constructor(ctx: Context, val preferFresh: Boolean = false)
storage.writeBackUser(user)
}
+
/**
* Updates the stats data in both firebase and local storage.
* @param statsData The stats data to be updated
@@ -122,8 +128,9 @@ class Cache private constructor(ctx: Context, val preferFresh: Boolean = false)
if (!isInternetAvailable()) {
return getUserPicture()
}
- if (user == null)
+ if(user == null)
return null
+
userPicture = repo.getUserProfilePictureImage(user!!.uid)
Log.d("UserPic", userPicture.toString())
userPicture?.let { storage.writeBackUserPicture(it) }
@@ -140,6 +147,7 @@ class Cache private constructor(ctx: Context, val preferFresh: Boolean = false)
storage.writeBackUserPicture(bitmap)
}
+
/**
* Retrieves the stats data from cache or local storage.
* @return the stats data for the user.
@@ -175,16 +183,19 @@ class Cache private constructor(ctx: Context, val preferFresh: Boolean = false)
}
}
+
/**
* MAINLY FOR TESTING
* Updates the leaderboard data in cache and localStorage
* @param lBData the new leaderboard data
*/
fun updateLeaderBoardData(lBData: List) {
+
leaderBoardData = lBData
storage.writeBackLeaderBoardData(lBData)
}
+
/**
* Gets leaderboard data from cache or local storage
* @return A list of LeaderBoardInfo fetched from local storage or cache.
@@ -195,6 +206,7 @@ class Cache private constructor(ctx: Context, val preferFresh: Boolean = false)
return leaderBoardData!!
}
+
/**
* Gets leaderboard data firebase.
* @param position the position of the leaderboard to load.
@@ -220,6 +232,50 @@ class Cache private constructor(ctx: Context, val preferFresh: Boolean = false)
}
}
+ fun getFriends():List {
+ if(::myFriends.isInitialized) return myFriends
+ myFriends = storage.getFriends() ?: listOf()
+ return myFriends
+ }
+
+ suspend fun getFriendsAsync():List{
+ if(!isInternetAvailable()) {
+ Log.d("CACHE", "INTERNET NOT AVAILABLE")
+ return getFriends()
+ }
+ myFriends = FirebaseHelper.getFriends()
+ Log.d("Cache", myFriends.size.toString())
+ storage.writeBackFriends(myFriends)
+ return myFriends
+ }
+
+ fun getFriendReqs():List {
+ if(::myFriendReqs.isInitialized) return myFriendReqs
+ myFriendReqs = storage.getFriendReqs() ?: listOf()
+ return myFriendReqs
+ }
+
+ suspend fun getFriendReqsAsync():List{
+ if(!isInternetAvailable()) {
+ Log.d("CACHE", "INTERNET NOT AVAILABLE")
+ return getFriendReqs()
+ }
+ myFriendReqs = FirebaseHelper.getFriendReqs()
+ Log.d("Cache", myFriends.size.toString())
+ storage.writeBackFriendReqs(myFriendReqs)
+ return myFriendReqs
+ }
+
+ fun isInternetAvailable(): Boolean {
+ return try {
+ val ipAddr: InetAddress = InetAddress.getByName("www.google.com")
+ //You can replace it with your name
+ !ipAddr.equals("")
+ } catch (e: Exception) {
+ Log.d("Cache", e.toString())
+ false
+ }
+ }
/**
* Clears the cache and local storage.
*/
@@ -259,5 +315,4 @@ class Cache private constructor(ctx: Context, val preferFresh: Boolean = false)
}
}
-
}
\ No newline at end of file
diff --git a/app/src/main/java/ch/epfl/sweng/rps/persistence/PrivateStorage.kt b/app/src/main/java/ch/epfl/sweng/rps/persistence/PrivateStorage.kt
index 756b0797..3a184984 100644
--- a/app/src/main/java/ch/epfl/sweng/rps/persistence/PrivateStorage.kt
+++ b/app/src/main/java/ch/epfl/sweng/rps/persistence/PrivateStorage.kt
@@ -3,9 +3,13 @@ package ch.epfl.sweng.rps.persistence
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
+import ch.epfl.sweng.rps.models.*
+import com.google.gson.Gson
import android.net.Uri
import ch.epfl.sweng.rps.models.remote.LeaderBoardInfo
import ch.epfl.sweng.rps.models.remote.User
+import ch.epfl.sweng.rps.models.ui.FriendRequestInfo
+import ch.epfl.sweng.rps.models.ui.FriendsInfo
import ch.epfl.sweng.rps.models.ui.UserStat
import com.google.gson.*
import java.io.ByteArrayOutputStream
@@ -50,6 +54,47 @@ class PrivateStorage constructor(val context: Context) : Storage {
return arr.toList()
}
+ override fun removeFile(file: Storage.FILES): Boolean {
+ return false
+ }
+
+ override fun getUserDetails(): User? {
+ return null
+ }
+
+ override fun getFriends(): List? {
+ val statsFile = getFile(Storage.FILES.FRIENDS)
+ if (!statsFile.exists())
+ return null
+ val json = statsFile.readText()
+ val arr = Gson().fromJson(json, Array::class.java)
+ return arr.toList()
+ }
+
+ override fun getFriendReqs(): List? {
+ val statsFile = getFile(Storage.FILES.REQUESTS)
+ if (!statsFile.exists())
+ return null
+ val json = statsFile.readText()
+ val arr = Gson().fromJson(json, Array::class.java)
+ return arr.toList()
+ }
+
+ override fun writeBackFriends(data : List){
+ val gson = Gson()
+ val json = gson.toJson(data.toTypedArray(), Array::class.java)
+ val f = getFile(Storage.FILES.FRIENDS)
+ f.writeText(json)
+ }
+
+ override fun writeBackFriendReqs(data : List){
+ val gson = Gson()
+ val json = gson.toJson(data.toTypedArray(), Array::class.java)
+ val f = getFile(Storage.FILES.REQUESTS)
+ f.writeText(json)
+ }
+
+
override fun getLeaderBoardData(): List? {
val leaderBoardFile = getFile(Storage.FILES.LEADERBOARDDATA)
if (!leaderBoardFile.exists())
diff --git a/app/src/main/java/ch/epfl/sweng/rps/persistence/Storage.kt b/app/src/main/java/ch/epfl/sweng/rps/persistence/Storage.kt
index 70912a10..f546bbe7 100644
--- a/app/src/main/java/ch/epfl/sweng/rps/persistence/Storage.kt
+++ b/app/src/main/java/ch/epfl/sweng/rps/persistence/Storage.kt
@@ -1,27 +1,37 @@
package ch.epfl.sweng.rps.persistence
-
import android.graphics.Bitmap
import ch.epfl.sweng.rps.models.remote.LeaderBoardInfo
import ch.epfl.sweng.rps.models.remote.User
+import ch.epfl.sweng.rps.models.ui.FriendRequestInfo
+import ch.epfl.sweng.rps.models.ui.FriendsInfo
import ch.epfl.sweng.rps.models.ui.UserStat
import java.io.File
interface Storage {
+ fun removeFile(file : FILES) : Boolean
+ fun getUserDetails() : User?
+ fun getFriends(): List?
+ fun getFriendReqs(): List?
fun getFile(file: FILES): File
fun deleteFile(file: FILES): Boolean
fun getUser(): User?
fun getStatsData(): List?
fun getLeaderBoardData(): List?
fun writeBackUser(user: User)
- fun writeBackStatsData(data: List)
- fun writeBackLeaderBoardData(data: List)
+ fun writeBackStatsData(data : List)
+ fun writeBackLeaderBoardData(data : List)
fun writeBackUserPicture(bitmap: Bitmap)
fun getUserPicture(): Bitmap?
- enum class FILES(val file: String) {
+ fun writeBackFriends(friends: List)
+ fun writeBackFriendReqs(reqs: List)
+
+ enum class FILES(val file:String){
USERINFO("USER"),
LEADERBOARDDATA("LBDATA"),
USERPICTURE("USERPICTURE"),
- STATSDATA("STATSDATA")
+ STATSDATA("STATSDATA"),
+ FRIENDS("FRIENDS"),
+ REQUESTS("REQUESTS")
}
enum class Origin {
diff --git a/app/src/main/java/ch/epfl/sweng/rps/remote/FirebaseHelper.kt b/app/src/main/java/ch/epfl/sweng/rps/remote/FirebaseHelper.kt
index f53dc3fc..ad2efdd0 100644
--- a/app/src/main/java/ch/epfl/sweng/rps/remote/FirebaseHelper.kt
+++ b/app/src/main/java/ch/epfl/sweng/rps/remote/FirebaseHelper.kt
@@ -1,12 +1,11 @@
package ch.epfl.sweng.rps.remote
+
import android.net.Uri
import ch.epfl.sweng.rps.R
-import ch.epfl.sweng.rps.models.remote.Hand
-import ch.epfl.sweng.rps.models.remote.LeaderBoardInfo
-import ch.epfl.sweng.rps.models.remote.User
-import ch.epfl.sweng.rps.models.ui.RoundStat
-import ch.epfl.sweng.rps.models.ui.UserStat
+import ch.epfl.sweng.rps.models.*
+import ch.epfl.sweng.rps.models.remote.*
+import ch.epfl.sweng.rps.models.ui.*
import ch.epfl.sweng.rps.services.ServiceLocator
import ch.epfl.sweng.rps.utils.Option
import java.text.SimpleDateFormat
@@ -68,6 +67,7 @@ object FirebaseHelper {
gameRounds.map { it.value.computeScores() }.filter { it.isNotEmpty() }
val userScore = allRoundScores.sumOf { scores ->
val max = scores.maxOf { it.points }
+
if (scores.any { it.points == max && it.uid == userid && !scores.all { score -> score.points == max } })
1L
else
@@ -177,6 +177,7 @@ object FirebaseHelper {
repo.getUserProfilePictureUrl(score.uid).asData?.value.let { Uri.parse(it.toString()) }
if (leaderBoardInfo.userProfilePictureUrl == null) {
leaderBoardInfo.userProfilePictureUrl =
+
Uri.parse("android.resource://ch.epfl.sweng.rps/" + R.drawable.profile_img)
}
@@ -185,6 +186,78 @@ object FirebaseHelper {
}
return allPlayers
}
+ /**
+ * This function returns the your friends.
+ */
+ suspend fun getFriends(): List {
+ val fbRepo = ServiceLocator.getInstance().repository
+ val userId = fbRepo.rawCurrentUid()
+ val friends = fbRepo.friends.getFriends()
+ val friendList = mutableListOf()
+
+ for (friend in friends) {
+ val user = fbRepo.getUser(friend)
+ val userStats = fbRepo.games.statsOfUser(friend)
+ val friendsInfo = userId?.let {
+ FriendsInfo(
+ username = user.asData?.value?.username?: "UsernameEmpty",
+ uid = it,
+ gamesPlayed = userStats.total_games,
+ gamesWon = userStats.wins,
+ winRate = userStats.winRate,
+ isOnline = true)
+ }
+
+ if (friendsInfo != null) {
+ friendList.add(friendsInfo)
+ }
+ }
+ return friendList
+ }
+ /**
+ * This function returns your friend requests.
+ */
+ suspend fun getFriendReqs(): List {
+ val fbRepo = ServiceLocator.getInstance().repository
+ val friendRequest = fbRepo.friends.listFriendRequests()
+ val reqList = mutableListOf()
+ val uid = fbRepo.rawCurrentUid()
+
+ for (req in friendRequest) {
+ if (req.from != uid && req.status == FriendRequest.Status.PENDING) {
+ val user = fbRepo.getUser(req.from)
+ val friendsReq = FriendRequestInfo(
+ username = user.asData?.value?.username?: "UsernameEmpty",
+ uid = req.from
+ )
+ reqList.add(friendsReq)
+ }
+ }
+ return reqList
+ }
+
+ /* suspend fun getInvites(): List {
+ val fbRepo = ServiceLocator.getInstance().repository
+ val uid = fbRepo.rawCurrentUid()
+ val friendInvite = uid?.let { fbRepo.friends.listInvitations(it) }
+ val myInvites = mutableListOf()
+
+
+ if (friendInvite != null) {
+ for (invite in friendInvite) {
+ if (invite.from != uid && invite.id == uid){
+ val user = fbRepo.getUser(invite.from)
+ val invitation = InvitationInfo(
+ username = user.asData?.value?.username?: "UsernameEmpty",
+ uid = invite.from
+ )
+ myInvites.add(invitation)
+ }
+ }
+ }
+ return myInvites
+ } */
+
}
diff --git a/app/src/main/java/ch/epfl/sweng/rps/remote/FirebaseReferences.kt b/app/src/main/java/ch/epfl/sweng/rps/remote/FirebaseReferences.kt
index 03b65335..fa6981d4 100644
--- a/app/src/main/java/ch/epfl/sweng/rps/remote/FirebaseReferences.kt
+++ b/app/src/main/java/ch/epfl/sweng/rps/remote/FirebaseReferences.kt
@@ -7,8 +7,8 @@ import com.google.firebase.storage.FirebaseStorage
* Instantiating this class necessitates the Firebase SDK to be initialized.
*/
class FirebaseReferences {
- private val root = FirebaseFirestore.getInstance()
- private val storageRoot = FirebaseStorage.getInstance().reference
+ val root = FirebaseFirestore.getInstance()
+ val storageRoot = FirebaseStorage.getInstance().reference
/**
* Thefriend request collection.
diff --git a/app/src/main/java/ch/epfl/sweng/rps/remote/LocalRepository.kt b/app/src/main/java/ch/epfl/sweng/rps/remote/LocalRepository.kt
index 5bb5ef04..e2e8a028 100644
--- a/app/src/main/java/ch/epfl/sweng/rps/remote/LocalRepository.kt
+++ b/app/src/main/java/ch/epfl/sweng/rps/remote/LocalRepository.kt
@@ -21,7 +21,7 @@ class LocalRepository(private var uid: String? = null) : Repository, GamesReposi
@VisibleForTesting
internal val users = mutableMapOf()
- private val friendRequests = mutableMapOf>()
+ val friendRequests = mutableListOf()
override val friends: FriendsRepository
get() = this
override val games: GamesRepository
@@ -79,32 +79,28 @@ class LocalRepository(private var uid: String? = null) : Repository, GamesReposi
}
override suspend fun sendFriendRequestTo(uid: String) {
- val map = (friendRequests[uid] ?: mutableMapOf())
- map[getCurrentUid()] = FriendRequest.build(getCurrentUid(), uid, Timestamp.now())
- friendRequests[uid] = map
+ val fr = FriendRequest.build(getCurrentUid(), uid, Timestamp.now())
+ friendRequests.add(fr)
}
override suspend fun listFriendRequests(): List {
- return friendRequests[getCurrentUid()]?.entries?.map { it.value } ?: emptyList()
+ return friendRequests
}
override suspend fun getFriends(): List {
- return friendRequests[getCurrentUid()]
- ?.filter { it.value.status == FriendRequest.Status.ACCEPTED }
- ?.map { entry -> entry.value.users.first { it != getCurrentUid() } }
- ?.toList() ?: emptyList()
+ return friendRequests.filter { it.status == FriendRequest.Status.ACCEPTED }
+ .map { it.users.first { it != getCurrentUid() } }
}
override suspend fun changeFriendRequestToStatus(
userUid: String,
status: FriendRequest.Status
) {
- friendRequests[getCurrentUid()]?.set(
- userUid,
- friendRequests[getCurrentUid()]!![userUid]!!.copy(status = status)
- )
+ val i = friendRequests.indexOfFirst { it.users.contains(userUid) }
+ friendRequests[i] = friendRequests[i].copy(status = status)
}
+
override suspend fun getGame(gameId: String): Game? {
return gamesMap[gameId]
}
diff --git a/app/src/main/java/ch/epfl/sweng/rps/remote/games/FirebaseGamesRepository.kt b/app/src/main/java/ch/epfl/sweng/rps/remote/games/FirebaseGamesRepository.kt
index 87950d79..703d9d1a 100644
--- a/app/src/main/java/ch/epfl/sweng/rps/remote/games/FirebaseGamesRepository.kt
+++ b/app/src/main/java/ch/epfl/sweng/rps/remote/games/FirebaseGamesRepository.kt
@@ -46,7 +46,7 @@ class FirebaseGamesRepository(internal val repository: FirebaseRepository) : Gam
override suspend fun statsOfUser(uid: String): UserStats {
return firebase.usersCollection.document(uid).collection("stats").document("games").get()
- .await().toObject()!!
+ .await().toObject()?: UserStats(uid,0,0)
}
override suspend fun listInvitations(): List {
diff --git a/app/src/main/java/ch/epfl/sweng/rps/services/OfflineGameService.kt b/app/src/main/java/ch/epfl/sweng/rps/services/OfflineGameService.kt
index f5915811..1ed8d9bb 100644
--- a/app/src/main/java/ch/epfl/sweng/rps/services/OfflineGameService.kt
+++ b/app/src/main/java/ch/epfl/sweng/rps/services/OfflineGameService.kt
@@ -1,11 +1,12 @@
package ch.epfl.sweng.rps.services
+import ch.epfl.sweng.rps.remote.Repository
+import ch.epfl.sweng.rps.models.*
import ch.epfl.sweng.rps.models.remote.Game
import ch.epfl.sweng.rps.models.remote.GameMode
import ch.epfl.sweng.rps.models.remote.Hand
import ch.epfl.sweng.rps.models.remote.Round
import ch.epfl.sweng.rps.models.xbstract.ComputerPlayer
-import ch.epfl.sweng.rps.remote.Repository
import com.google.firebase.Timestamp
import kotlinx.coroutines.delay
@@ -18,6 +19,9 @@ class OfflineGameService(
val artificialMovesDelay: Long = DEFAULT_DELAY
) : GameService() {
+ companion object {
+ private const val DEFAULT_DELAY = 100L
+ }
private var _disposed = false
@@ -31,26 +35,8 @@ class OfflineGameService(
get() {
return game?.done ?: return false
}
- private val currentHands get() = currentRound.hands as MutableMap
- override val currentGame: Game
- get() {
- checkNotDisposed()
- if (game == null) {
- throw error
- ?: GameServiceException("Game service has not received a game yet")
- } else {
- return game!!
- }
- }
- override val currentRound: Round
- get() = game!!.rounds[game!!.current_round.toString()]!!
- override val isGameFull: Boolean
- get() = true
- override val isDisposed: Boolean
- get() = _disposed
- override val started: Boolean
- get() = game != null
- override val imTheOwner get() = true
+
+
override suspend fun addRound(): Round {
checkNotDisposed()
val round = Round.Rps(
@@ -62,10 +48,13 @@ class OfflineGameService(
return round
}
+
private fun setRound(round: Round) {
(currentGame.rounds as MutableMap)[game!!.current_round.toString()] = round
}
+ private val currentHands get() = currentRound.hands as MutableMap
+
override suspend fun playHand(hand: Hand) {
checkNotDisposed()
val me: String = repository.getCurrentUid()
@@ -92,6 +81,24 @@ class OfflineGameService(
}
}
+ override val currentGame: Game
+ get() {
+ checkNotDisposed()
+ if (game == null) {
+ throw error
+ ?: GameServiceException("Game service has not received a game yet")
+ } else {
+ return game!!
+ }
+ }
+
+ override val currentRound: Round
+ get() = game!!.rounds[game!!.current_round.toString()]!!
+
+ override val isGameFull: Boolean
+ get() = true
+
+
override fun dispose() {
checkNotDisposed()
_disposed = true
@@ -101,7 +108,14 @@ class OfflineGameService(
override fun stopListening() {
}
+ override val isDisposed: Boolean
+ get() = _disposed
+
+ override val started: Boolean
+ get() = game != null
+
override suspend fun refreshGame(): Game = game!!
+
override fun startListening(): GameService {
checkNotDisposed()
val round = Round.Rps(
@@ -127,6 +141,8 @@ class OfflineGameService(
}
}
+ override val imTheOwner get() = true
+
override suspend fun awaitForAllHands() {
awaitFor { currentRound.hands.size == 2 }// 2 is the number of players, for now hardcoded
}
@@ -134,8 +150,4 @@ class OfflineGameService(
override suspend fun awaitForRoundAdded() {
awaitFor { true }
}
-
- companion object {
- private const val DEFAULT_DELAY = 100L
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/ch/epfl/sweng/rps/services/OfflineTicTacToe.kt b/app/src/main/java/ch/epfl/sweng/rps/services/OfflineTicTacToe.kt
index b40e5f02..35525166 100644
--- a/app/src/main/java/ch/epfl/sweng/rps/services/OfflineTicTacToe.kt
+++ b/app/src/main/java/ch/epfl/sweng/rps/services/OfflineTicTacToe.kt
@@ -23,9 +23,9 @@ class OfflineTicTacToe(val view: TicTacToeFragment, player: MOVES) : TicTacToeGa
if (calculate()) return
val opponent = if (player == MOVES.CROSS) MOVES.CIRCLE else MOVES.CROSS
while (true) {
- val oppChoice = player2.makeMove()
- val cell = oppChoice.second
- val row = oppChoice.first
+ var oppChoice = player2.makeMove()
+ var cell = oppChoice.second
+ var row = oppChoice.first
if (matrix[row][cell] == MOVES.EMPTY) {
matrix[row][cell] = opponent
view.updateUI((row * 3) + cell, opponent)
diff --git a/app/src/main/java/ch/epfl/sweng/rps/services/ProdServiceLocator.kt b/app/src/main/java/ch/epfl/sweng/rps/services/ProdServiceLocator.kt
index d93b6833..7c1f59a7 100644
--- a/app/src/main/java/ch/epfl/sweng/rps/services/ProdServiceLocator.kt
+++ b/app/src/main/java/ch/epfl/sweng/rps/services/ProdServiceLocator.kt
@@ -3,7 +3,6 @@ package ch.epfl.sweng.rps.services
import ch.epfl.sweng.rps.remote.Env
import ch.epfl.sweng.rps.remote.FirebaseReferences
import ch.epfl.sweng.rps.remote.FirebaseRepository
-import ch.epfl.sweng.rps.remote.LocalRepository
/**
* ServiceLocator is a singleton class that provides access to the different services
@@ -14,18 +13,18 @@ import ch.epfl.sweng.rps.remote.LocalRepository
* */
class ProdServiceLocator : ServiceLocator {
-
val firebaseReferences by lazy { FirebaseReferences() }
+
override val repository by lazy { FirebaseRepository.createInstance(firebaseReferences) }
- override val env: Env = Env.Prod
- private val gameServices = mutableMapOf()
- override val cachedGameServices: List
- get() = gameServices.keys.toList()
- override val matchmakingService: MatchmakingService by lazy { MatchmakingService() }
+
override fun dispose() {
disposeAllGameServices()
}
+ override val env: Env = Env.Prod
+
+ private val gameServices = mutableMapOf()
+
override fun getGameServiceForGame(gameId: String, start: Boolean): FirebaseGameService {
cleanUpServices()
val service = gameServices.getOrPut(gameId) {
@@ -58,4 +57,10 @@ class ProdServiceLocator : ServiceLocator {
}
+ override val cachedGameServices: List
+ get() = gameServices.keys.toList()
+
+ override val matchmakingService: MatchmakingService by lazy { MatchmakingService() }
+
+
}
\ No newline at end of file
diff --git a/app/src/main/java/ch/epfl/sweng/rps/services/TicTacToeOpponent.kt b/app/src/main/java/ch/epfl/sweng/rps/services/TicTacToeOpponent.kt
index 95d56038..707d2ac7 100644
--- a/app/src/main/java/ch/epfl/sweng/rps/services/TicTacToeOpponent.kt
+++ b/app/src/main/java/ch/epfl/sweng/rps/services/TicTacToeOpponent.kt
@@ -1,5 +1,5 @@
package ch.epfl.sweng.rps.services
interface TicTacToeOpponent {
- fun makeMove(): Pair
+ fun makeMove():Pair
}
\ No newline at end of file
diff --git a/app/src/main/java/ch/epfl/sweng/rps/ui/friends/AddFriendFragment.kt b/app/src/main/java/ch/epfl/sweng/rps/ui/friends/AddFriendFragment.kt
new file mode 100644
index 00000000..ccadc186
--- /dev/null
+++ b/app/src/main/java/ch/epfl/sweng/rps/ui/friends/AddFriendFragment.kt
@@ -0,0 +1,59 @@
+package ch.epfl.sweng.rps.ui.friends
+
+import android.os.Bundle
+import android.util.Log
+import androidx.fragment.app.Fragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import android.widget.EditText
+import android.widget.Toast
+import androidx.lifecycle.lifecycleScope
+import ch.epfl.sweng.rps.R
+import ch.epfl.sweng.rps.services.ServiceLocator
+import kotlinx.coroutines.launch
+
+
+class AddFriendFragment : Fragment() {
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ // Inflate the layout for this fragment
+ return inflater.inflate(R.layout.fragment_add_friend, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ val sendReqBtn = view.findViewById