Commit 327d4d93 authored by Niklas Fix's avatar Niklas Fix 🎓

Merge branch 'master' of ssh://gitlab.akamu.de:26/akamu/game-client-android into latex

parents e13102c4 374a6c4e
...@@ -17,7 +17,7 @@ android { ...@@ -17,7 +17,7 @@ android {
applicationId "de.akamu.tudarmstadt" applicationId "de.akamu.tudarmstadt"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 29 targetSdkVersion 29
versionCode 5 versionCode 8
versionName "1.0.0" versionName "1.0.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
......
package de.akamu.tudarmstadt; package de.akamu.tudarmstadt;
import android.app.Application; import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.util.Log;
import androidx.appcompat.app.AppCompatDelegate; import androidx.appcompat.app.AppCompatDelegate;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import com.yariksoffice.lingver.Lingver; import com.yariksoffice.lingver.Lingver;
import org.jetbrains.annotations.NotNull;
import org.json.JSONObject;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import de.akamu.tudarmstadt.data.settings.SettingsDataSource;
import de.akamu.tudarmstadt.data.settings.SettingsDataSourceImpl;
import de.akamu.tudarmstadt.interactors.SettingsInteractor;
import de.akamu.tudarmstadt.model.MyObjectBox; import de.akamu.tudarmstadt.model.MyObjectBox;
import de.akamu.tudarmstadt.util.Constants; import de.akamu.tudarmstadt.util.Constants;
import de.akamu.tudarmstadt.util.SettingsPresenter;
import io.objectbox.BoxStore; import io.objectbox.BoxStore;
/** /**
...@@ -29,12 +20,11 @@ import io.objectbox.BoxStore; ...@@ -29,12 +20,11 @@ import io.objectbox.BoxStore;
* application state. It provides access to the ObjectBox database * application state. It provides access to the ObjectBox database
* and the global settings. * and the global settings.
*/ */
public class App extends Application implements SettingsDataSource.FetchSettingsCallback { public class App extends Application {
private final String TAG = "App"; private final String TAG = "App";
private BoxStore boxStore; private BoxStore boxStore;
SettingsPresenter settingsPresenter;
@Override @Override
public void onCreate() { public void onCreate() {
...@@ -51,8 +41,6 @@ public class App extends Application implements SettingsDataSource.FetchSettings ...@@ -51,8 +41,6 @@ public class App extends Application implements SettingsDataSource.FetchSettings
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
} }
boxStore = MyObjectBox.builder().androidContext(App.this).build(); boxStore = MyObjectBox.builder().androidContext(App.this).build();
settingsPresenter = new SettingsPresenter(new SettingsInteractor(new SettingsDataSourceImpl()));
settingsPresenter.fetchSettings(this);
} }
/** /**
...@@ -61,14 +49,4 @@ public class App extends Application implements SettingsDataSource.FetchSettings ...@@ -61,14 +49,4 @@ public class App extends Application implements SettingsDataSource.FetchSettings
public BoxStore getBoxStore() { public BoxStore getBoxStore() {
return boxStore; return boxStore;
} }
@Override
public void onFetchSettingsSuccess(@NotNull JSONObject settings) {
System.out.println("Settings fetched successfully.");
}
@Override
public void onFetchSettingsFailed(@NotNull String reason) {
System.out.println("Fetching settings failed: " + reason);
}
} }
\ No newline at end of file
...@@ -117,15 +117,15 @@ class FriendsListFragment : Fragment(), ...@@ -117,15 +117,15 @@ class FriendsListFragment : Fragment(),
} }
override fun bindRecommendedUsersAdapter( override fun bindRecommendedUsersAdapter(
allUsers: ArrayList<User>, recommendedUsers: ArrayList<User>,
friends: ArrayList<User>, friends: ArrayList<User>,
duels: ArrayList<Duel> duels: ArrayList<Duel>
) { ) {
nestedScollView_friends.visibility = View.VISIBLE nestedScollView_friends.visibility = View.VISIBLE
textView_friends_recommended_users.visibility = View.VISIBLE
textView_your_friends.visibility = View.VISIBLE textView_your_friends.visibility = View.VISIBLE
textView_friends_recommended_users.visibility = View.VISIBLE
recyclerView_recommended_user_list.visibility = View.VISIBLE recyclerView_recommended_user_list.visibility = View.VISIBLE
recyclerView_recommended_user_list.adapter = RecommendedUsersAdapter(allUsers, duels, presenter, this, requireActivity()) recyclerView_recommended_user_list.adapter = RecommendedUsersAdapter(recommendedUsers, duels, presenter, this, requireActivity())
recyclerView_recommended_user_list.layoutManager = LinearLayoutManager(requireActivity(), RecyclerView.HORIZONTAL, false) recyclerView_recommended_user_list.layoutManager = LinearLayoutManager(requireActivity(), RecyclerView.HORIZONTAL, false)
} }
...@@ -295,6 +295,8 @@ class FriendsListFragment : Fragment(), ...@@ -295,6 +295,8 @@ class FriendsListFragment : Fragment(),
Toast.makeText(requireActivity(), resources.getString(R.string.user_added_to_friends, user.username), Toast.LENGTH_SHORT).show() Toast.makeText(requireActivity(), resources.getString(R.string.user_added_to_friends, user.username), Toast.LENGTH_SHORT).show()
searchListAdapter?.friends?.add(user) searchListAdapter?.friends?.add(user)
searchListAdapter?.notifyDataSetChanged() searchListAdapter?.notifyDataSetChanged()
friendsListAdapter?.mFriends?.add(0, user)
friendsListAdapter?.notifyItemInserted(0)
} }
override fun onAddRecommendedUserClicked(user: User) { override fun onAddRecommendedUserClicked(user: User) {
......
...@@ -35,7 +35,7 @@ class RecommendedUsersAdapter( ...@@ -35,7 +35,7 @@ class RecommendedUsersAdapter(
} }
override fun getItemCount(): Int { override fun getItemCount(): Int {
return Constants.MAX_RECOMMENDATIONS return recommendedUsers.size
} }
override fun onBindViewHolder(holder: RecommendedUsersAdapter.RecommendedUserViewHolder, position: Int) { override fun onBindViewHolder(holder: RecommendedUsersAdapter.RecommendedUserViewHolder, position: Int) {
......
...@@ -14,7 +14,7 @@ interface FriendsListContract { ...@@ -14,7 +14,7 @@ interface FriendsListContract {
*/ */
fun bindFriendListAdapter(friends: ArrayList<User>, duels: ArrayList<Duel>) fun bindFriendListAdapter(friends: ArrayList<User>, duels: ArrayList<Duel>)
/** Bind RecommendedUsersAdapter **/ /** Bind RecommendedUsersAdapter **/
fun bindRecommendedUsersAdapter(allUsers: ArrayList<User>, friends: ArrayList<User>, duels: ArrayList<Duel>) fun bindRecommendedUsersAdapter(recommendedUsers: ArrayList<User>, friends: ArrayList<User>, duels: ArrayList<Duel>)
/** Let the user know that fetching his/her friends has failed due to [reason] **/ /** Let the user know that fetching his/her friends has failed due to [reason] **/
fun showFetchFriendsFailed(reason: String) fun showFetchFriendsFailed(reason: String)
/** Let the user know that sending a challenge request has failed due to [reason] **/ /** Let the user know that sending a challenge request has failed due to [reason] **/
......
...@@ -20,6 +20,7 @@ class FriendsListPresenter( ...@@ -20,6 +20,7 @@ class FriendsListPresenter(
lateinit var allUsers: ArrayList<User> lateinit var allUsers: ArrayList<User>
lateinit var mFriends: ArrayList<User> lateinit var mFriends: ArrayList<User>
lateinit var mDuels: ArrayList<Duel> lateinit var mDuels: ArrayList<Duel>
lateinit var mRecommendedUsers: ArrayList<User>
private var userFetched: Boolean = false private var userFetched: Boolean = false
private var friendsFetched: Boolean = false private var friendsFetched: Boolean = false
...@@ -33,17 +34,17 @@ class FriendsListPresenter( ...@@ -33,17 +34,17 @@ class FriendsListPresenter(
override fun fetchAllUsers() { override fun fetchAllUsers() {
friendsInteractor.getAllUsers(object : FriendsDataSource.GetAllUsersCallback { friendsInteractor.getAllUsers(object : FriendsDataSource.GetAllUsersCallback {
override fun onGetAllUsersSuccess(all: ArrayList<User>) { override fun onGetAllUsersSuccess(all: ArrayList<User>) {
allUsers = all allUsers = all
userFetched = true userFetched = true
handleFriendsAndDuelCallback() handleFriendsAndDuelCallback()
} }
override fun onGetAllUsersFailed(reason: String) { override fun onGetAllUsersFailed(reason: String) {
// silent? // silent?
} }
}) })
} }
override fun fetchFriends() { override fun fetchFriends() {
...@@ -75,8 +76,9 @@ class FriendsListPresenter( ...@@ -75,8 +76,9 @@ class FriendsListPresenter(
} }
private fun handleFriendsAndDuelCallback() { private fun handleFriendsAndDuelCallback() {
if(friendsFetched && duelsFetched && userFetched) { if (friendsFetched && duelsFetched && userFetched) {
view?.bindRecommendedUsersAdapter(getRecommendedUsers(allUsers), mFriends, mDuels) mRecommendedUsers = getRecommendedUsers(allUsers)
view?.bindRecommendedUsersAdapter(mRecommendedUsers, mFriends, mDuels)
view?.bindFriendListAdapter(mFriends, mDuels) view?.bindFriendListAdapter(mFriends, mDuels)
} }
} }
...@@ -84,66 +86,74 @@ class FriendsListPresenter( ...@@ -84,66 +86,74 @@ class FriendsListPresenter(
private fun getRecommendedUsers(allUsers: ArrayList<User>): ArrayList<User> { private fun getRecommendedUsers(allUsers: ArrayList<User>): ArrayList<User> {
val generatedIndices: ArrayList<Int> = arrayListOf() val generatedIndices: ArrayList<Int> = arrayListOf()
val recommendedUsers: ArrayList<User> = arrayListOf() val recommendedUsers: ArrayList<User> = arrayListOf()
val allUsersCopy: ArrayList<User> = arrayListOf() var allUsersCopy: ArrayList<User> = arrayListOf()
allUsersCopy.addAll(allUsers) allUsersCopy.addAll(allUsers)
val allUsersIterator = allUsersCopy.listIterator() allUsersCopy = ArrayList(allUsersCopy.distinct())
// Make sure we don't recommend someone who is already a friend // Make sure we don't recommend someone who is already a friend or ourselves
while (allUsersIterator.hasNext()) { val possibleUsers = ArrayList(allUsersCopy.filter{user ->
val user = allUsersIterator.next() user.id != ID_ME && !isAlreadyFriend(user)
// Remove yourself fromm the list })
if (user.id == ID_ME) {
allUsersIterator.remove() if (possibleUsers.isEmpty()) return possibleUsers
} val maxShownRecommendations =
for (friend in mFriends) { if (possibleUsers.size < Constants.MAX_RECOMMENDATIONS) possibleUsers.size
if (user.username == friend.username) { else Constants.MAX_RECOMMENDATIONS
allUsersIterator.remove()
}
}
}
for (i in 0 until Constants.MAX_RECOMMENDATIONS) { for (i in 0 until maxShownRecommendations) {
var index = (0 until allUsersCopy.size).random() var index = (0 until possibleUsers.size).random()
// Make sure we do not recommend the same user twice or more // Make sure we do not recommend the same user twice or more
while (generatedIndices.contains(index)) { while (generatedIndices.contains(index)) {
index = (0 until allUsersCopy.size).random() index = (0 until possibleUsers.size).random()
} }
generatedIndices.add(index) generatedIndices.add(index)
recommendedUsers.add(allUsersCopy[index]) recommendedUsers.add(possibleUsers[index])
} }
return recommendedUsers return recommendedUsers
} }
override fun sendChallengeRequest(friend: User, callback: ChallengeSentCallback) { private fun isAlreadyFriend(user: User) : Boolean {
friendsInteractor.sendChallengeRequest(friend, object : FriendsDataSource.ChallengeRequestCallback { for (friend in mFriends) {
override fun onChallengeRequestSuccess(friendName: String) { if (user.username == friend.username) {
view?.showSendChallengeRequestSuccess(friendName) return true
callback.indicateChallengeSentSuccessfully()
} }
}
return false
}
override fun onChallengeRequestFailedNoPoolsSelected() { override fun sendChallengeRequest(friend: User, callback: ChallengeSentCallback) {
view?.showSendChallengeRequestFailedNoPoolsSelected() friendsInteractor.sendChallengeRequest(
callback.indicateChallengeSentFailed() friend,
} object : FriendsDataSource.ChallengeRequestCallback {
override fun onChallengeRequestSuccess(friendName: String) {
view?.showSendChallengeRequestSuccess(friendName)
callback.indicateChallengeSentSuccessfully()
}
override fun onChallengeRequestFailed(reason: String) { override fun onChallengeRequestFailedNoPoolsSelected() {
view?.showSendChallengeRequestFailed(reason) view?.showSendChallengeRequestFailedNoPoolsSelected()
callback.indicateChallengeSentFailed() callback.indicateChallengeSentFailed()
} }
override fun onChallengeRequestFailedNoPoolsInCommon() { override fun onChallengeRequestFailed(reason: String) {
view?.showSendChallengeRequestFailedNoPoolsInCommon() view?.showSendChallengeRequestFailed(reason)
callback.indicateChallengeSentFailed() callback.indicateChallengeSentFailed()
} }
}) override fun onChallengeRequestFailedNoPoolsInCommon() {
view?.showSendChallengeRequestFailedNoPoolsInCommon()
callback.indicateChallengeSentFailed()
}
})
} }
override fun findUsersThatStartWith(startsWith: String) { override fun findUsersThatStartWith(startsWith: String) {
val usersThatStartsWith = ArrayList<User>() val usersThatStartsWith = ArrayList<User>()
allUsers.forEach { allUsers.forEach {
if (it.username.startsWith(startsWith, true) if (it.username.startsWith(startsWith, true)
&& it.username != me.username) { && it.username != me.username
) {
usersThatStartsWith.add(it) usersThatStartsWith.add(it)
} }
} }
......
...@@ -17,6 +17,7 @@ import de.akamu.tudarmstadt.util.AppUserUtil ...@@ -17,6 +17,7 @@ import de.akamu.tudarmstadt.util.AppUserUtil
import de.akamu.tudarmstadt.util.Constants import de.akamu.tudarmstadt.util.Constants
import de.akamu.tudarmstadt.util.Extensions.Companion.toast import de.akamu.tudarmstadt.util.Extensions.Companion.toast
import kotlinx.android.synthetic.main.activity_login.* import kotlinx.android.synthetic.main.activity_login.*
import org.json.JSONObject
class LoginActivity : BaseActivity(), LoginContract.View { class LoginActivity : BaseActivity(), LoginContract.View {
...@@ -124,6 +125,14 @@ class LoginActivity : BaseActivity(), LoginContract.View { ...@@ -124,6 +125,14 @@ class LoginActivity : BaseActivity(), LoginContract.View {
} }
} }
override fun onFetchSettingsSuccess(settings: JSONObject) {
println("Settings fetched successfully.")
}
override fun onFetchSettingsFailed(reason: String) {
println("Fetching settings failed: $reason")
}
override fun handleUnauthorizedEvent() { override fun handleUnauthorizedEvent() {
//Don't handle unauthorized event //Don't handle unauthorized event
} }
......
...@@ -2,9 +2,11 @@ package de.akamu.tudarmstadt.features.login ...@@ -2,9 +2,11 @@ package de.akamu.tudarmstadt.features.login
import de.akamu.tudarmstadt.BasePresenter import de.akamu.tudarmstadt.BasePresenter
import de.akamu.tudarmstadt.BaseView import de.akamu.tudarmstadt.BaseView
import de.akamu.tudarmstadt.data.settings.SettingsDataSource
import de.akamu.tudarmstadt.features.login.LoginContract.Presenter import de.akamu.tudarmstadt.features.login.LoginContract.Presenter
import de.akamu.tudarmstadt.features.login.LoginContract.View import de.akamu.tudarmstadt.features.login.LoginContract.View
import de.akamu.tudarmstadt.model.User import de.akamu.tudarmstadt.model.User
import org.json.JSONObject
/** /**
* Specifies the contract between the [View] and the [Presenter] * Specifies the contract between the [View] and the [Presenter]
...@@ -37,6 +39,10 @@ interface LoginContract { ...@@ -37,6 +39,10 @@ interface LoginContract {
fun getLoginToken() : String fun getLoginToken() : String
fun getAppUser() : User? fun getAppUser() : User?
fun onFetchSettingsSuccess(settings: JSONObject)
fun onFetchSettingsFailed(reason: String)
} }
interface Presenter : BasePresenter { interface Presenter : BasePresenter {
...@@ -48,6 +54,9 @@ interface LoginContract { ...@@ -48,6 +54,9 @@ interface LoginContract {
fun attemptLogin(username: String, password: String) fun attemptLogin(username: String, password: String)
/** Start the [SignupActivity] **/ /** Start the [SignupActivity] **/
fun startSignup(); fun startSignup()
/** Fetches the settings for the app from server **/
fun fetchSettings()
} }
} }
\ No newline at end of file
...@@ -4,17 +4,22 @@ import androidx.preference.PreferenceManager ...@@ -4,17 +4,22 @@ import androidx.preference.PreferenceManager
import de.akamu.tudarmstadt.api.AkamuFirebase import de.akamu.tudarmstadt.api.AkamuFirebase
import de.akamu.tudarmstadt.api.V2API import de.akamu.tudarmstadt.api.V2API
import de.akamu.tudarmstadt.data.login.LoginDataSource import de.akamu.tudarmstadt.data.login.LoginDataSource
import de.akamu.tudarmstadt.data.settings.SettingsDataSource
import de.akamu.tudarmstadt.data.settings.SettingsDataSourceImpl
import de.akamu.tudarmstadt.interactors.SettingsInteractor
import de.akamu.tudarmstadt.model.User import de.akamu.tudarmstadt.model.User
import de.akamu.tudarmstadt.util.Constants import de.akamu.tudarmstadt.util.Constants
import org.json.JSONObject
class LoginPresenter(private val interactor: LoginInteractor, var view: LoginContract.View?) : LoginContract.Presenter { class LoginPresenter(private val interactor: LoginInteractor, var view: LoginContract.View?) :
LoginContract.Presenter {
init { init {
view?.presenter = this view?.presenter = this
} }
override fun autoLogin() { override fun autoLogin() {
if(view?.getLoginToken() != "" && view?.getAppUser() != null) { if (view?.getLoginToken() != "" && view?.getAppUser() != null) {
V2API.setToken(view?.getLoginToken()) V2API.setToken(view?.getLoginToken())
view?.isLoggedIn(true, view?.getAppUser()!!) view?.isLoggedIn(true, view?.getAppUser()!!)
} }
...@@ -24,23 +29,27 @@ class LoginPresenter(private val interactor: LoginInteractor, var view: LoginCon ...@@ -24,23 +29,27 @@ class LoginPresenter(private val interactor: LoginInteractor, var view: LoginCon
view?.showProgress(true) view?.showProgress(true)
AkamuFirebase.loadFirebaseToken(object : AkamuFirebase.GetFirebaseTokenCallback { AkamuFirebase.loadFirebaseToken(object : AkamuFirebase.GetFirebaseTokenCallback {
override fun onGetFirebaseTokenSuccess(firebaseToken: String) { override fun onGetFirebaseTokenSuccess(firebaseToken: String) {
interactor.login(username, password, firebaseToken, object: LoginDataSource.LoginCallback { interactor.login(
override fun onLoginSuccess(appUser: User, loginToken: String) { username,
view?.saveLoginToken(loginToken) password,
view?.isLoggedIn(true, appUser) firebaseToken,
view?.showProgress(false) object : LoginDataSource.LoginCallback {
} override fun onLoginSuccess(appUser: User, loginToken: String) {
view?.saveLoginToken(loginToken)
override fun onLoginFailed(message: String) { view?.isLoggedIn(true, appUser)
view?.showLoginFailed() view?.showProgress(false)
view?.showProgress(false) }
}
override fun onLoginFailed(message: String) {
override fun onLoginCancelled() { view?.showLoginFailed()
view?.showLoginCancelled() view?.showProgress(false)
view?.showProgress(false) }
}
}) override fun onLoginCancelled() {
view?.showLoginCancelled()
view?.showProgress(false)
}
})
} }
override fun onGetFirebaseTokenFail(error: String) { override fun onGetFirebaseTokenFail(error: String) {
...@@ -50,6 +59,20 @@ class LoginPresenter(private val interactor: LoginInteractor, var view: LoginCon ...@@ -50,6 +59,20 @@ class LoginPresenter(private val interactor: LoginInteractor, var view: LoginCon
}) })
} }
override fun fetchSettings() {
val settingsInteractor = SettingsInteractor(SettingsDataSourceImpl())
settingsInteractor.fetchSettings(object : SettingsDataSource.FetchSettingsCallback {
override fun onFetchSettingsSuccess(settings: JSONObject) {
view?.onFetchSettingsSuccess(settings)
}
override fun onFetchSettingsFailed(reason: String) {
view?.onFetchSettingsFailed(reason)
}
})
}
override fun startSignup() { override fun startSignup() {
view?.navigateToSignup() view?.navigateToSignup()
} }
......
...@@ -5,6 +5,7 @@ import android.graphics.Paint ...@@ -5,6 +5,7 @@ import android.graphics.Paint
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView import android.widget.TextView