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
......@@ -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,38 +86,45 @@ 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()
allUsersCopy = ArrayList(allUsersCopy.distinct())
// 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()
}
}
}
// 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)
})
for (i in 0 until Constants.MAX_RECOMMENDATIONS) {
var index = (0 until allUsersCopy.size).random()
if (possibleUsers.isEmpty()) return possibleUsers
val maxShownRecommendations =
if (possibleUsers.size < Constants.MAX_RECOMMENDATIONS) possibleUsers.size
else Constants.MAX_RECOMMENDATIONS
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
}
private fun isAlreadyFriend(user: User) : Boolean {
for (friend in mFriends) {
if (user.username == friend.username) {
return true
}
}
return false
}
override fun sendChallengeRequest(friend: User, callback: ChallengeSentCallback) {
friendsInteractor.sendChallengeRequest(friend, object : FriendsDataSource.ChallengeRequestCallback {
friendsInteractor.sendChallengeRequest(
friend,
object : FriendsDataSource.ChallengeRequestCallback {
override fun onChallengeRequestSuccess(friendName: String) {
view?.showSendChallengeRequestSuccess(friendName)
callback.indicateChallengeSentSuccessfully()
......@@ -143,7 +152,8 @@ class FriendsListPresenter(
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,7 +29,11 @@ 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 {
interactor.login(
username,
password,
firebaseToken,
object : LoginDataSource.LoginCallback {
override fun onLoginSuccess(appUser: User, loginToken: String) {
view?.saveLoginToken(loginToken)
view?.isLoggedIn(true, appUser)
......@@ -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"
android:contentDescription="@string/wrong_answer"
android:drawableEnd="@drawable/ic_cross_light"
android:clickable="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/ind"
app:layout_constraintTop_toTopOf="parent" /-->
<View
android:id="@+id/view_swipe_answer_shortSummary_right_indicator"
android:layout_width="12dp"
android:layout_height="0dp"
android:background="@color/colorLost"
android:layout_marginEnd="12dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/imageView_swipe_answer_shortSummary_final_indicator"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/imageView_swipe_answer_shortSummary_final_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/ic_checked_akamu_green"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
......@@ -54,7 +54,7 @@
<string name="enter_new_password">Neues Passwort eingeben</string>
<string name="forgot_password">Passwort vergessen?</string>
<string name="subjects">Fächer</string>
<string name="signup_email_description_top">Bevor du Zugang zu einer komplett neuen Art des Lernens erlangst, benötigen wir deine Universitäts-Email-Adresse (mit Endung auf @stud.tu-darmstadt.de).</string>
<string name="signup_email_description_top">Bevor du Zugang zu einer komplett neuen Art des Lernens erlangst, benötigen wir deine Universitäts-Email-Adresse (mit Endung auf \"tu-darmstadt.de\").</string>
<string name="signup_email_description_bottom">Wir werden dir eine Email an diese Adresse senden, welche du zuerst bestätigen musst. Es kann manchmal ein paar Minuten dauern bevor die Email ankommt. Warum nicht die Zeit nutzen und deinen Freunden von Akamu erzählen?</string>
<string name="sign_out">Ausloggen</string>
<string name="alphabetic">Alphabetisch</string>
......@@ -67,7 +67,7 @@
<string name="hint_search_friend">Suche User</string>
<string name="no_active_duels">Du hast zur Zeit keine laufenden Duelle :( \nFüge zuerst ein paar Freunde hinzu.</string>
<string name="opponent_avatar">Gegner Avatar</string>
<string name="error_signup_invalidemail">Deine Email-Adresse muss auf \"@stud.tu-darmstadt.de\" enden.</string>
<string name="error_signup_invalidemail">Deine Email muss auf \"tu-darmstadt.de\" enden</string>
<string name="error_invalid_username_short">Dein Username muss mindestens 4 Zeichen lang sein.</string>
<string name="error_invalid_username_notalpha">Dein Username darf nur alphabetische oder numerische Zeichen enthalten</string>
<string name="error_invalid_username_long">Dein Username darf nicht länger als 12 Zeichen sein.</string>
......
......@@ -34,9 +34,7 @@
<string name="error_invalid_password_notequal">Passwords do not match
</string>
<string name="hint_signup_email">Enter your email address</string>
<string name="error_signup_invalidemail">Your email must end with
\"@stud.tu-darmstadt.de\"
</string>
<string name="error_signup_invalidemail">Your email must end with \"tu-darmstadt.de\"</string>
<!-- String related to Challenge -->
<string name="prompt_challenged_successful">has been challenged</string>
......@@ -108,7 +106,7 @@
exam…
</string>
<string name="signup_email_description_head">University Email</string>
<string name="signup_email_description_top">Before you get access to a complete new way of learning, we need your student email address (which ends on @stud.tu-darmstadt.de).</string>
<string name="signup_email_description_top">Before you get access to a complete new way of learning, we need your university email address (which ends on \"tu-darmstadt.de\").</string>
<string name="signup_email_description_bottom">We will send you an email to this address, which you have to confirm first. Sometimes it takes a few minutes before the email arrives. Why not use the time to tell your friends about Akamu?</string>
<string name="no_active_duels">You currently have no active duels :(\nStart with adding some friends.</string>
<string name="subjects">Subjects</string>
......
......@@ -115,9 +115,7 @@
<string name="error_invalid_password_notequal">Passwords do not match
</string>
<string name="hint_signup_email">Enter your email address</string>
<string name="error_signup_invalidemail">Your email must end with
\"@stud.tu-darmstadt.de\"
</string>
<string name="error_signup_invalidemail">Your email must end with \"tu-darmstadt.de\"</string>
<!-- String related to Challenge -->
<string name="prompt_challenged_successful">has been challenged</string>
......@@ -231,7 +229,7 @@
exam…
</string>
<string name="signup_email_description_head">University Email</string>
<string name="signup_email_description_top">Before you get access to a complete new way of learning, we need your student email address (which ends on @stud.tu-darmstadt.de).</string>
<string name="signup_email_description_top">Before you get access to a complete new way of learning, we need your university email address (which ends on \"tu-darmstadt.de\").</string>
<string name="signup_email_description_bottom">We will send you an email to this address, which you have to confirm first. Sometimes it takes a few minutes before the email arrives. Why not use the time to tell your friends about Akamu?</string>
<string name="no_active_duels">You currently have no active duels :(\nStart with adding some friends.</string>
<string name="subjects">Subjects</string>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment