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 {
applicationId "de.akamu.tudarmstadt"
minSdkVersion 21
targetSdkVersion 29
versionCode 5
versionCode 8
versionName "1.0.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
......
package de.akamu.tudarmstadt;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.preference.PreferenceManager;
import com.yariksoffice.lingver.Lingver;
import org.jetbrains.annotations.NotNull;
import org.json.JSONObject;
import java.util.Locale;
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.util.Constants;
import de.akamu.tudarmstadt.util.SettingsPresenter;
import io.objectbox.BoxStore;
/**
......@@ -29,12 +20,11 @@ import io.objectbox.BoxStore;
* application state. It provides access to the ObjectBox database
* and the global settings.
*/
public class App extends Application implements SettingsDataSource.FetchSettingsCallback {
public class App extends Application {
private final String TAG = "App";
private BoxStore boxStore;
SettingsPresenter settingsPresenter;
@Override
public void onCreate() {
......@@ -51,8 +41,6 @@ public class App extends Application implements SettingsDataSource.FetchSettings
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
}
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
public BoxStore getBoxStore() {
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(),
}
override fun bindRecommendedUsersAdapter(
allUsers: ArrayList<User>,
recommendedUsers: ArrayList<User>,
friends: ArrayList<User>,
duels: ArrayList<Duel>
) {
nestedScollView_friends.visibility = View.VISIBLE
textView_friends_recommended_users.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.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)
}
......@@ -295,6 +295,8 @@ class FriendsListFragment : Fragment(),
Toast.makeText(requireActivity(), resources.getString(R.string.user_added_to_friends, user.username), Toast.LENGTH_SHORT).show()
searchListAdapter?.friends?.add(user)
searchListAdapter?.notifyDataSetChanged()
friendsListAdapter?.mFriends?.add(0, user)
friendsListAdapter?.notifyItemInserted(0)
}
override fun onAddRecommendedUserClicked(user: User) {
......
......@@ -35,7 +35,7 @@ class RecommendedUsersAdapter(
}
override fun getItemCount(): Int {
return Constants.MAX_RECOMMENDATIONS
return recommendedUsers.size
}
override fun onBindViewHolder(holder: RecommendedUsersAdapter.RecommendedUserViewHolder, position: Int) {
......
......@@ -14,7 +14,7 @@ interface FriendsListContract {
*/
fun bindFriendListAdapter(friends: ArrayList<User>, duels: ArrayList<Duel>)
/** 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] **/
fun showFetchFriendsFailed(reason: String)
/** Let the user know that sending a challenge request has failed due to [reason] **/
......
......@@ -20,6 +20,7 @@ class FriendsListPresenter(
lateinit var allUsers: ArrayList<User>
lateinit var mFriends: ArrayList<User>
lateinit var mDuels: ArrayList<Duel>
lateinit var mRecommendedUsers: ArrayList<User>
private var userFetched: Boolean = false
private var friendsFetched: Boolean = false
......@@ -33,17 +34,17 @@ class FriendsListPresenter(
override fun fetchAllUsers() {
friendsInteractor.getAllUsers(object : FriendsDataSource.GetAllUsersCallback {
override fun onGetAllUsersSuccess(all: ArrayList<User>) {
allUsers = all
userFetched = true
handleFriendsAndDuelCallback()
}
override fun onGetAllUsersSuccess(all: ArrayList<User>) {
allUsers = all
userFetched = true
handleFriendsAndDuelCallback()
}
override fun onGetAllUsersFailed(reason: String) {
// silent?
}
override fun onGetAllUsersFailed(reason: String) {
// silent?
}
})
})
}
override fun fetchFriends() {
......@@ -75,8 +76,9 @@ class FriendsListPresenter(
}
private fun handleFriendsAndDuelCallback() {
if(friendsFetched && duelsFetched && userFetched) {
view?.bindRecommendedUsersAdapter(getRecommendedUsers(allUsers), mFriends, mDuels)
if (friendsFetched && duelsFetched && userFetched) {
mRecommendedUsers = getRecommendedUsers(allUsers)
view?.bindRecommendedUsersAdapter(mRecommendedUsers, mFriends, mDuels)
view?.bindFriendListAdapter(mFriends, mDuels)
}
}
......@@ -84,66 +86,74 @@ class FriendsListPresenter(
private fun getRecommendedUsers(allUsers: ArrayList<User>): ArrayList<User> {
val generatedIndices: ArrayList<Int> = arrayListOf()
val recommendedUsers: ArrayList<User> = arrayListOf()
val allUsersCopy: ArrayList<User> = arrayListOf()
var allUsersCopy: ArrayList<User> = arrayListOf()
allUsersCopy.addAll(allUsers)
val allUsersIterator = allUsersCopy.listIterator()
// Make sure we don't recommend someone who is already a friend
while (allUsersIterator.hasNext()) {
val user = allUsersIterator.next()
// Remove yourself fromm the list
if (user.id == ID_ME) {
allUsersIterator.remove()
}
for (friend in mFriends) {
if (user.username == friend.username) {
allUsersIterator.remove()
}
}
}
allUsersCopy = ArrayList(allUsersCopy.distinct())
// Make sure we don't recommend someone who is already a friend or ourselves
val possibleUsers = ArrayList(allUsersCopy.filter{user ->
user.id != ID_ME && !isAlreadyFriend(user)
})
if (possibleUsers.isEmpty()) return possibleUsers
val maxShownRecommendations =
if (possibleUsers.size < Constants.MAX_RECOMMENDATIONS) possibleUsers.size
else Constants.MAX_RECOMMENDATIONS
for (i in 0 until Constants.MAX_RECOMMENDATIONS) {
var index = (0 until allUsersCopy.size).random()
for (i in 0 until maxShownRecommendations) {
var index = (0 until possibleUsers.size).random()
// Make sure we do not recommend the same user twice or more
while (generatedIndices.contains(index)) {
index = (0 until allUsersCopy.size).random()
index = (0 until possibleUsers.size).random()
}
generatedIndices.add(index)
recommendedUsers.add(allUsersCopy[index])
recommendedUsers.add(possibleUsers[index])
}
return recommendedUsers
}
override fun sendChallengeRequest(friend: User, callback: ChallengeSentCallback) {
friendsInteractor.sendChallengeRequest(friend, object : FriendsDataSource.ChallengeRequestCallback {
override fun onChallengeRequestSuccess(friendName: String) {
view?.showSendChallengeRequestSuccess(friendName)
callback.indicateChallengeSentSuccessfully()
private fun isAlreadyFriend(user: User) : Boolean {
for (friend in mFriends) {
if (user.username == friend.username) {
return true
}
}
return false
}
override fun onChallengeRequestFailedNoPoolsSelected() {
view?.showSendChallengeRequestFailedNoPoolsSelected()
callback.indicateChallengeSentFailed()
}
override fun sendChallengeRequest(friend: User, callback: ChallengeSentCallback) {
friendsInteractor.sendChallengeRequest(
friend,
object : FriendsDataSource.ChallengeRequestCallback {
override fun onChallengeRequestSuccess(friendName: String) {
view?.showSendChallengeRequestSuccess(friendName)
callback.indicateChallengeSentSuccessfully()
}
override fun onChallengeRequestFailed(reason: String) {
view?.showSendChallengeRequestFailed(reason)
callback.indicateChallengeSentFailed()
}
override fun onChallengeRequestFailedNoPoolsSelected() {
view?.showSendChallengeRequestFailedNoPoolsSelected()
callback.indicateChallengeSentFailed()
}
override fun onChallengeRequestFailedNoPoolsInCommon() {
view?.showSendChallengeRequestFailedNoPoolsInCommon()
callback.indicateChallengeSentFailed()
}
override fun onChallengeRequestFailed(reason: String) {
view?.showSendChallengeRequestFailed(reason)
callback.indicateChallengeSentFailed()
}
})
override fun onChallengeRequestFailedNoPoolsInCommon() {
view?.showSendChallengeRequestFailedNoPoolsInCommon()
callback.indicateChallengeSentFailed()
}
})
}
override fun findUsersThatStartWith(startsWith: String) {
val usersThatStartsWith = ArrayList<User>()
allUsers.forEach {
if (it.username.startsWith(startsWith, true)
&& it.username != me.username) {
&& it.username != me.username
) {
usersThatStartsWith.add(it)
}
}
......
......@@ -17,6 +17,7 @@ import de.akamu.tudarmstadt.util.AppUserUtil
import de.akamu.tudarmstadt.util.Constants
import de.akamu.tudarmstadt.util.Extensions.Companion.toast
import kotlinx.android.synthetic.main.activity_login.*
import org.json.JSONObject
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() {
//Don't handle unauthorized event
}
......
......@@ -2,9 +2,11 @@ package de.akamu.tudarmstadt.features.login
import de.akamu.tudarmstadt.BasePresenter
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.View
import de.akamu.tudarmstadt.model.User
import org.json.JSONObject
/**
* Specifies the contract between the [View] and the [Presenter]
......@@ -37,6 +39,10 @@ interface LoginContract {
fun getLoginToken() : String
fun getAppUser() : User?
fun onFetchSettingsSuccess(settings: JSONObject)
fun onFetchSettingsFailed(reason: String)
}
interface Presenter : BasePresenter {
......@@ -48,6 +54,9 @@ interface LoginContract {
fun attemptLogin(username: String, password: String)
/** 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
import de.akamu.tudarmstadt.api.AkamuFirebase
import de.akamu.tudarmstadt.api.V2API
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.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 {
view?.presenter = this
}
override fun autoLogin() {
if(view?.getLoginToken() != "" && view?.getAppUser() != null) {
if (view?.getLoginToken() != "" && view?.getAppUser() != null) {
V2API.setToken(view?.getLoginToken())
view?.isLoggedIn(true, view?.getAppUser()!!)
}
......@@ -24,23 +29,27 @@ class LoginPresenter(private val interactor: LoginInteractor, var view: LoginCon
view?.showProgress(true)
AkamuFirebase.loadFirebaseToken(object : AkamuFirebase.GetFirebaseTokenCallback {
override fun onGetFirebaseTokenSuccess(firebaseToken: String) {
interactor.login(username, password, firebaseToken, object: LoginDataSource.LoginCallback {
override fun onLoginSuccess(appUser: User, loginToken: String) {
view?.saveLoginToken(loginToken)
view?.isLoggedIn(true, appUser)
view?.showProgress(false)
}
override fun onLoginFailed(message: String) {
view?.showLoginFailed()
view?.showProgress(false)
}
override fun onLoginCancelled() {
view?.showLoginCancelled()
view?.showProgress(false)
}
})
interactor.login(
username,
password,
firebaseToken,
object : LoginDataSource.LoginCallback {
override fun onLoginSuccess(appUser: User, loginToken: String) {
view?.saveLoginToken(loginToken)
view?.isLoggedIn(true, appUser)
view?.showProgress(false)
}
override fun onLoginFailed(message: String) {
view?.showLoginFailed()
view?.showProgress(false)
}
override fun onLoginCancelled() {
view?.showLoginCancelled()
view?.showProgress(false)
}
})
}
override fun onGetFirebaseTokenFail(error: String) {
......@@ -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() {
view?.navigateToSignup()
}
......
......@@ -5,6 +5,7 @@ import android.graphics.Paint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
......@@ -39,24 +40,34 @@ class SwipeAnswerShortSummaryAdapter(
inner class SwipeShortSummaryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private var answerTextView : TextView = itemView.findViewById(R.id.textView_swipe_answer_short_summary_answerText)
private var indicator : TextView = itemView.findViewById(R.id.textView_swipe_answer_short_summary_indicator)
private var rightIndicator : View = itemView.findViewById(R.id.view_swipe_answer_shortSummary_right_indicator)
private var leftIndicator : View = itemView.findViewById(R.id.view_swipe_answer_shortSummary_left_indicator)
private var finalIndicator : ImageView = itemView.findViewById(R.id.imageView_swipe_answer_shortSummary_final_indicator)
fun bind(answeredCorrectly: Boolean, answerItem: MultipleChoiceAnswerItem) {
answerTextView.text = answerItem.text
if (answeredCorrectly) {
indicator.text = mContext.getString(R.string.correct)
indicator.background = mContext.getDrawable(R.drawable.background_indicator_correct)
finalIndicator.setBackgroundResource(R.drawable.ic_checked_akamu_green)
} else {
indicator.text = mContext.getString(R.string.wrong)
indicator.background = mContext.getDrawable(R.drawable.background_indicator_wrong)
finalIndicator.setBackgroundResource(R.drawable.ic_close_red)
}
if (answerItem.isCorrect) {
leftIndicator.setBackgroundResource(R.color.akamu_green)
//answerTextView.setTextColor(ContextCompat.getColor(mContext, R.color.akamu_green))
leftIndicator.setBackgroundResource(R.drawable.background_indicator_correct)
} else {
leftIndicator.setBackgroundResource(R.color.colorLost)
//answerTextView.setTextColor(ContextCompat.getColor(mContext, R.color.colorLost))
leftIndicator.setBackgroundResource(R.drawable.background_indicator_wrong)
}
if (answerItem.isCorrect) {
if (answeredCorrectly) {
rightIndicator.setBackgroundResource(R.drawable.background_indicator_correct)
} else {
rightIndicator.setBackgroundResource(R.drawable.background_indicator_wrong)
}
} else {
if (answeredCorrectly) {
rightIndicator.setBackgroundResource(R.drawable.background_indicator_wrong)
} else {
rightIndicator.setBackgroundResource(R.drawable.background_indicator_correct)
}
}
}
}
......
......@@ -12,7 +12,7 @@ class EmailPresenter(var view: EmailContract.View?) : EmailContract.Presenter {
override fun checkEmail(email: String) {
if (BuildConfig.DEBUG) {
view?.showEmailValid()
} else if (email.endsWith("@stud.tu-darmstadt.de")) {
} else if (email.endsWith(".tu-darmstadt.de") || email.endsWith("@tu-darmstadt.de")) {
view?.showEmailValid()
} else {
view?.showEmailNotValid()
......
package de.akamu.tudarmstadt.util
import de.akamu.tudarmstadt.data.settings.SettingsDataSource
import de.akamu.tudarmstadt.interactors.SettingsInteractor
import org.json.JSONObject
class SettingsPresenter (var settingsInteractor: SettingsInteractor) {
fun fetchSettings(callback: SettingsDataSource.FetchSettingsCallback) {
settingsInteractor.fetchSettings(object : SettingsDataSource.FetchSettingsCallback {
override fun onFetchSettingsSuccess(settings: JSONObject) {
callback.onFetchSettingsSuccess(settings)
}
override fun onFetchSettingsFailed(reason: String) {
callback.onFetchSettingsFailed(reason)
}
})
}
}
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#C95D63"
android:alpha="0.8">
<path
android:fillColor="@android:color/white"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="6dp"
xmlns:tools="http://schemas.android.com/tools">
android:layout_margin="6dp">
<View
android:id="@+id/view_swipe_answer_shortSummary_left_indicator"
......@@ -24,12 +24,12 @@
android:text="Here stands a sample answer.\nLorem ipsum dolor."
android:textColor="@color/textLight"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/textView_swipe_answer_short_summary_indicator"
app:layout_constraintEnd_toStartOf="@+id/view_swipe_answer_shortSummary_right_indicator"
app:layout_constraintStart_toEndOf="@id/view_swipe_answer_shortSummary_left_indicator"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="HardcodedText" />
<TextView
<!--TextView
android:id="@+id/textView_swipe_answer_short_summary_indicator"
android:layout_width="96dp"
android:layout_height="wrap_content"
......@@ -41,6 +41,37 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="HardcodedText" />
tools:ignore="HardcodedText" /-->
<!--Button
android:id="@+id/imageView_swipe_incorrect_answer_short_summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/button_circle_ripple_red"