Commit 10abf1b5 authored by Niklas Fix's avatar Niklas Fix 🎓

added: NewUserEvent feature

parent 35b80f57
......@@ -10,6 +10,8 @@ import de.akamu.tudarmstadt.model.Duel;
import de.akamu.tudarmstadt.model.Pool;
import de.akamu.tudarmstadt.model.Question;
import de.akamu.tudarmstadt.model.Round;
import de.akamu.tudarmstadt.model.UserAnswer;
import org.json.JSONException;
import org.json.JSONObject;
......@@ -73,7 +75,7 @@ public final class V2APIDuel {
return gsonBuilder.fromJson(response, Round.class);
}
public static Question submitTextInputAnswer(int roundID, int questionID, String input) throws JSONException, IOException, AkamuAPIException {
public static UserAnswer submitTextInputAnswer(int roundID, int questionID, String input) throws JSONException, IOException, AkamuAPIException {
Map<String, String> parameters = new ArrayMap<>(2);
parameters.put("question", Integer.toString(questionID));
parameters.put("round", Integer.toString(roundID));
......@@ -82,10 +84,10 @@ public final class V2APIDuel {
String response = V2API.patch("duel/answer", json.toString(), parameters);
Gson gsonBuilder = new GsonBuilder().create();
return gsonBuilder.fromJson(response, Question.class);
return gsonBuilder.fromJson(response, UserAnswer.class);
}
public static Question submitOptionAnswer(int roundID, int questionID, int[] selected) throws IOException, AkamuAPIException {
public static UserAnswer submitOptionAnswer(int roundID, int questionID, int[] selected) throws IOException, AkamuAPIException {
Map<String, String> parameters = new ArrayMap<>(2);
parameters.put("question", Integer.toString(questionID));
parameters.put("round", Integer.toString(roundID));
......@@ -99,7 +101,7 @@ public final class V2APIDuel {
String response = V2API.post("duel/answer", json.toString(), parameters);
Gson gsonBuilder = new GsonBuilder().create();
return gsonBuilder.fromJson(response, Question.class);
return gsonBuilder.fromJson(response, UserAnswer.class);
}
public static void reportQuestion(int questionID, String feedbackText) throws JSONException, IOException, AkamuAPIException {
......
......@@ -3,6 +3,7 @@ package de.akamu.tudarmstadt.data.duel
import de.akamu.tudarmstadt.model.Duel
import de.akamu.tudarmstadt.model.Pool
import de.akamu.tudarmstadt.model.Round
import de.akamu.tudarmstadt.model.UserAnswer
interface DuelDataSource {
......@@ -35,7 +36,7 @@ interface DuelDataSource {
fun loadRound(roundID: Int, callback: LoadRoundCallback)
interface SubmitUserAnswerCallback {
fun onSubmitUserAnswerSuccess()
fun onSubmitUserAnswerSuccess(userAnswer: UserAnswer?)
fun onSubmitUserAnswerFailed(reason: String)
}
......
......@@ -6,6 +6,7 @@ import de.akamu.tudarmstadt.exceptions.AkamuAPIException
import de.akamu.tudarmstadt.model.Duel
import de.akamu.tudarmstadt.model.Pool
import de.akamu.tudarmstadt.model.Round
import de.akamu.tudarmstadt.model.UserAnswer
class DuelDataSourceImpl : DuelDataSource {
......@@ -148,11 +149,12 @@ class DuelDataSourceImpl : DuelDataSource {
var callback: DuelDataSource.SubmitUserAnswerCallback
) : AsyncTask<Void, Void, Boolean>() {
private lateinit var errorMessage: String
private var errorMessage: String = "Oops. Something went wrong..."
private var userAnswer: UserAnswer? = null // IMPORTANT: This needs to be nullable, as we skip it's initialization in case of 403
override fun doInBackground(vararg p0: Void?): Boolean {
return try {
V2APIDuel.submitOptionAnswer(roundID, questionID, selected)
userAnswer = V2APIDuel.submitOptionAnswer(roundID, questionID, selected)
true
} catch (ae: AkamuAPIException) {
errorMessage = ae.message!!
......@@ -165,7 +167,7 @@ class DuelDataSourceImpl : DuelDataSource {
override fun onPostExecute(success: Boolean?) {
if (success!!) {
callback.onSubmitUserAnswerSuccess()
callback.onSubmitUserAnswerSuccess(userAnswer)
} else {
callback.onSubmitUserAnswerFailed(errorMessage)
}
......
......@@ -4,6 +4,7 @@ import android.graphics.drawable.Drawable
import de.akamu.tudarmstadt.BasePresenter
import de.akamu.tudarmstadt.BaseView
import de.akamu.tudarmstadt.model.User
import de.akamu.tudarmstadt.model.UserAnswer
interface DashboardContract {
......@@ -30,6 +31,8 @@ interface DashboardContract {
fun onLoadUserFail(reason: String)
/** Show infor Box **/
fun showInfoBox()
/** Shows popup if new avatars/titles/levels are unlocked **/
fun showNewUserEvent(userAnswerList: ArrayList<UserAnswer?>)
/** Destroys the login token on logout */
fun destroyLoginToken()
/** Finish the Activity **/
......@@ -51,6 +54,8 @@ interface DashboardContract {
fun fetchPools()
/** Fetch user's friends **/
fun fetchFriends()
/** Checks if user has unlocked new titles/avatars/levels **/
fun checkForNewUserEvents(userAnswerList: ArrayList<UserAnswer?>) : Boolean
}
}
\ No newline at end of file
......@@ -10,6 +10,7 @@ import de.akamu.tudarmstadt.features.questionpools.QuestionPoolsInteractor
import de.akamu.tudarmstadt.interactors.UserInteractor
import de.akamu.tudarmstadt.model.Pool
import de.akamu.tudarmstadt.model.User
import de.akamu.tudarmstadt.model.UserAnswer
import de.akamu.tudarmstadt.util.AppUserUtil
class DashboardPresenter(private var view: DashboardContract.View?, private val interactor: UserInteractor) : DashboardContract.Presenter {
......@@ -83,6 +84,24 @@ class DashboardPresenter(private var view: DashboardContract.View?, private val
})
}
override fun checkForNewUserEvents(userAnswerList: ArrayList<UserAnswer?>) : Boolean {
for (userAnswer in userAnswerList) {
if (userAnswer == null) continue
else when {
userAnswer.levels != 0 -> {
return true
}
userAnswer.titles.isNotEmpty() -> {
return true
}
userAnswer.avatars.isNotEmpty() -> {
return true
}
}
}
return false
}
override fun stop() {
view = null
}
......
......@@ -16,12 +16,13 @@ import de.akamu.tudarmstadt.data.user.UserDataSourceImpl
import de.akamu.tudarmstadt.features.dashboard.duels.DuelListFragment
import de.akamu.tudarmstadt.features.dashboard.memorystore.MemoryStoreFragment
import de.akamu.tudarmstadt.features.dashboard.training.TrainingFragment
import de.akamu.tudarmstadt.features.dashboard.userevent.NewUserEventDialogFragment
import de.akamu.tudarmstadt.features.login.LoginActivity
import de.akamu.tudarmstadt.features.profile.ProfileActivity
import de.akamu.tudarmstadt.features.questionpools.QuestionPoolsActivity
import de.akamu.tudarmstadt.features.settings.SettingsActivity
import de.akamu.tudarmstadt.interactors.UserInteractor
import de.akamu.tudarmstadt.model.User
import de.akamu.tudarmstadt.model.*
import de.akamu.tudarmstadt.util.AkamuResource
import de.akamu.tudarmstadt.util.AppUserUtil
import de.akamu.tudarmstadt.util.Constants
......@@ -95,6 +96,20 @@ class MainActivity : BaseActivity(), DashboardContract.View, NextLevelEventHandl
Constants.CHALLENGE_FRAGMENT -> navigateToChallenge()
else -> navigateToDuelList()
}
// val t0 = Title("Debugger-King", Subject(100, "ds", "dep", "uni", "descr"), 10)
// val t1 = Title("Herzchen", Subject(200, "ds", "dep", "uni", "descr"), 20)
// val a0 = Avatar(2, "2", 2)
// val a1 = Avatar(3, "3", 3)
// val titles = arrayOf(t0, t1)
// val avatars = arrayOf(a0, a1)
// val ua1 = UserAnswer(100, 17, titles, avatars, 10)
// val ua2 = UserAnswer(200, 18, titles, avatars, 20)
// val userAnswerList = arrayListOf(ua1, ua2)
val userAnswerList = intent.getParcelableArrayListExtra<UserAnswer>(Constants.KEY_USER_ANSWER)
if(userAnswerList != null && presenter.checkForNewUserEvents(userAnswerList)) {
showNewUserEvent(userAnswerList)
}
}
override fun navigateToProfile() {
......@@ -175,9 +190,6 @@ class MainActivity : BaseActivity(), DashboardContract.View, NextLevelEventHandl
activeFragment = Constants.MEMORY_STORE_FRAGMENT
}
private fun fillProfile() {
// Fill in the user's avatar, name, title and level
AkamuResource.smartLoad(useravatar_dashboard, user?.avatar?.image!!, this)
......@@ -263,4 +275,20 @@ class MainActivity : BaseActivity(), DashboardContract.View, NextLevelEventHandl
constraintlayout_main_info.visibility = View.GONE
}
}
override fun showNewUserEvent(userAnswerList: ArrayList<UserAnswer?>) {
if (userAnswerList.isEmpty()) return
else {
// val ft: FragmentTransaction = supportFragmentManager.beginTransaction()
// val prev: Fragment? = supportFragmentManager.findFragmentByTag(NewUserEventDialogFragment.TAG)
// if (prev != null) {
// ft.remove(prev)
// }
// ft.addToBackStack(null)
val filteredUserAnswerList = ArrayList(userAnswerList.filterNotNull())
val newUserEventDialogFragment = NewUserEventDialogFragment.newInstance(filteredUserAnswerList)
newUserEventDialogFragment.show(supportFragmentManager, newUserEventDialogFragment.tag)
}
}
}
package de.akamu.tudarmstadt.features.dashboard.userevent
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import de.akamu.tudarmstadt.R
import de.akamu.tudarmstadt.model.Avatar
import de.akamu.tudarmstadt.util.AkamuResource
import de.akamu.tudarmstadt.util.Constants
import kotlinx.android.synthetic.main.item_nue_avatar.*
class NewAvatarEventFragment : Fragment() {
lateinit var avatar: Avatar
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.item_nue_avatar, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
AkamuResource.smartLoad(imageView_nue_avatar_bg, avatar.image, requireActivity())
}
override fun onAttach(context: Context) {
super.onAttach(context)
arguments?.getParcelable<Avatar>(Constants.KEY_NUE_AVATAR)?.let {
avatar = it
}
}
companion object {
fun newInstance(
avatar: Avatar
): NewAvatarEventFragment {
val args = Bundle()
args.putParcelable(Constants.KEY_NUE_AVATAR, avatar)
val fragment = NewAvatarEventFragment()
fragment.arguments = args
return fragment
}
}
}
\ No newline at end of file
package de.akamu.tudarmstadt.features.dashboard.userevent
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import de.akamu.tudarmstadt.R
import de.akamu.tudarmstadt.util.Constants
import kotlinx.android.synthetic.main.item_nue_level.*
class NewLevelEventFragment : Fragment() {
private var level : Int = 0
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.item_nue_level, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
textView_nue_level_number.text = level.toString()
}
override fun onAttach(context: Context) {
super.onAttach(context)
arguments?.getInt(Constants.KEY_NUE_LEVEL)?.let {
level = it
}
}
companion object {
fun newInstance(
level: Int
): NewLevelEventFragment {
val args = Bundle()
args.putInt(Constants.KEY_NUE_LEVEL, level)
val fragment = NewLevelEventFragment()
fragment.arguments = args
return fragment
}
}
}
\ No newline at end of file
package de.akamu.tudarmstadt.features.dashboard.userevent
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import de.akamu.tudarmstadt.R
import de.akamu.tudarmstadt.model.Title
import de.akamu.tudarmstadt.util.Constants
import kotlinx.android.synthetic.main.item_nue_title.*
class NewTitleEventFragment : Fragment() {
lateinit var title: Title
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.item_nue_title, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
textView_nue_title_title.text = title.name
}
override fun onAttach(context: Context) {
super.onAttach(context)
arguments?.getParcelable<Title>(Constants.KEY_NUE_TITLE)?.let {
title = it
}
}
companion object {
fun newInstance(
title: Title
): NewTitleEventFragment {
val args = Bundle()
args.putParcelable(Constants.KEY_NUE_TITLE, title)
val fragment = NewTitleEventFragment()
fragment.arguments = args
return fragment
}
}
}
\ No newline at end of file
package de.akamu.tudarmstadt.features.dashboard.userevent
import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import de.akamu.tudarmstadt.model.Avatar
import de.akamu.tudarmstadt.model.Title
import de.akamu.tudarmstadt.model.UserAnswer
class NewUserEventAdapter(
userAnswerList: ArrayList<UserAnswer>,
f : Fragment
) : FragmentStateAdapter(f) {
var titlesIdx = 0
var avatarsIdx = 0
var numAvatars = 0
var numEvents = 0
var levels = ArrayList<Int>()
var titles = ArrayList<Title>()
var avatars = ArrayList<Avatar>()
init {
/* Create adapter data as follows:
* - New Level
* - New Title A
* - New Title B
* - New Avatar A
* - New Avatar B
*/
for (ua in userAnswerList) {
if (ua.levels != 0) {
levels.add(ua.levels)
titlesIdx++
}
if (ua.titles.isNotEmpty()) {
titles.addAll(ua.titles)
avatarsIdx += ua.titles.size
}
if (ua.avatars.isNotEmpty()) {
avatars.addAll(ua.avatars)
numAvatars += ua.avatars.size
}
}
avatarsIdx += titlesIdx
numEvents = avatarsIdx + numAvatars
}
override fun getItemCount(): Int {
return numEvents
}
override fun createFragment(position: Int): Fragment {
return when {
position < titlesIdx -> NewLevelEventFragment.newInstance(levels[position])
position < avatarsIdx -> NewTitleEventFragment.newInstance(titles[position - titlesIdx])
else -> NewAvatarEventFragment.newInstance(avatars[position - avatarsIdx])
}
}
}
package de.akamu.tudarmstadt.features.dashboard.userevent
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.viewpager2.widget.ViewPager2
import de.akamu.tudarmstadt.R
import de.akamu.tudarmstadt.model.UserAnswer
import de.akamu.tudarmstadt.util.Constants
class NewUserEventDialogFragment : DialogFragment() {
private lateinit var userAnswerList: ArrayList<UserAnswer>
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val v = LayoutInflater.from(requireActivity()).inflate(R.layout.popup_new_user_event, null)
val viewPager = v.findViewById<ViewPager2>(R.id.viewpager_newUserEvent)
viewPager.adapter = NewUserEventAdapter(userAnswerList, this)
return AlertDialog.Builder(requireActivity())
.setView(v)
.setPositiveButton(getString(R.string.OK), null)
.create()
}
// override fun onCreateView(
// inflater: LayoutInflater,
// container: ViewGroup?,
// savedInstanceState: Bundle?
// ): View? {
// return v
// }
/* override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewpager_newUserEvent.adapter = NewUserEventAdapter(userAnswerList, childFragmentManager, lifecycle)
}*/
override fun onAttach(context: Context) {
super.onAttach(context)
arguments?.getParcelableArrayList<UserAnswer>(Constants.KEY_USER_ANSWER)?.let {
userAnswerList = it
}
}
companion object {
fun newInstance(userAnswerList: ArrayList<UserAnswer>) : NewUserEventDialogFragment {
val f = NewUserEventDialogFragment()
val args = Bundle()
args.putParcelableArrayList(Constants.KEY_USER_ANSWER, userAnswerList)
f.arguments = args
return f
}
}
}
\ No newline at end of file
......@@ -28,6 +28,7 @@ import de.akamu.tudarmstadt.util.AkamuResource
import de.akamu.tudarmstadt.util.Constants
import de.akamu.tudarmstadt.util.Constants.Companion.KEY_DUEL
import de.akamu.tudarmstadt.util.Constants.Companion.KEY_POOL
import de.akamu.tudarmstadt.util.Constants.Companion.KEY_USER_ANSWER
import de.akamu.tudarmstadt.util.Extensions.Companion.toast
import kotlinx.android.synthetic.main.activity_question.*
import kotlinx.android.synthetic.main.layout_load_failed.*
......@@ -238,6 +239,7 @@ class QuestionActivity :
// i.putExtra(KEY_DUEL, mDuel)
// As long as this is not implemented, directly navigate back to the dashboard
val i = Intent(this, MainActivity::class.java)
i.putExtra(KEY_USER_ANSWER, presenter.userAnswerList)
i.addFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK)
startActivity(i)
finish()
......
......@@ -43,7 +43,9 @@ interface QuestionContract {
interface Presenter : BasePresenter{
/** Indicates whether the user answered correctly or not **/
var userAnswers: ArrayList<Boolean>
var userGuesses: ArrayList<Boolean>
/** List of received UserAnswer objects i.e. for the NewUserEvent logic (unlocked titles etc.) **/
var userAnswerList: ArrayList<UserAnswer?>
/** Indicates whether the duel has started **/
var duelHasStarted: Boolean
/** Load the current round from the server **/
......
......@@ -20,7 +20,8 @@ class QuestionPresenter(
val QUESTIONS_MAX: Int = 2
override var userAnswers: ArrayList<Boolean> = arrayListOf()
override var userGuesses: ArrayList<Boolean> = arrayListOf()
override var userAnswerList: ArrayList<UserAnswer?> = arrayListOf()
override var duelHasStarted: Boolean = false
lateinit var mRound: Round
......@@ -75,7 +76,7 @@ class QuestionPresenter(
override fun toExplanation() {
val explanation = currentQuestion.explanation.text
view?.showExplanation(explanation, userAnswers)
view?.showExplanation(explanation, userGuesses)
}
override fun sendQuestionReport(feedbackText: String) {
......@@ -98,7 +99,7 @@ class QuestionPresenter(
view?.showDuelInfo()
view?.updateDuelInfo()
userAnswers.clear()
userGuesses.clear()
++questionNumber
view?.showQuestionNumber(questionNumber)
currentQuestion = mRound.questions[questionNumber - 1]
......@@ -151,7 +152,8 @@ class QuestionPresenter(
currentQuestion.id,
selectedAnswerItemIds,
object : DuelDataSource.SubmitUserAnswerCallback {
override fun onSubmitUserAnswerSuccess() {
override fun onSubmitUserAnswerSuccess(userAnswer: UserAnswer?) {
userAnswerList.add(userAnswer)
view?.showContinueOptions()
}
......
......@@ -14,7 +14,6 @@ import de.akamu.tudarmstadt.features.questions.QuestionActivity
import de.akamu.tudarmstadt.features.questions.QuestionContract
import de.akamu.tudarmstadt.model.MultipleChoiceAnswer
import de.akamu.tudarmstadt.model.MultipleChoiceAnswerItem
import de.akamu.tudarmstadt.util.AkamuResource
import de.akamu.tudarmstadt.util.Constants
import kotlinx.android.synthetic.main.fragment_long_options_answer.*
......@@ -118,10 +117,10 @@ class LongOptionsAnswerFragment : AnswerFragment(), View.OnClickListener, View.O
}
}
if (answer.items[i].isCorrect) {
presenter.userAnswers.add(true)
presenter.userGuesses.add(true)