<repo_name>tmaciulis22/Beteasier<file_sep>/app-beteasier-kotlin/app/src/main/java/com/d/beteasier/base/BaseHolder.kt
package com.d.beteasier.base

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.IdRes
import androidx.annotation.LayoutRes
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.extensions.LayoutContainer

abstract class BaseHolder<ListItem>(itemView: View) : RecyclerView.ViewHolder(itemView),
    LayoutContainer {

    // Do not use this from subclasses, use onViewClick to deliver click actions
    internal var onViewClickListeners: (viewId: Int, data: Any?) -> Unit = { _, _ -> }
    internal var onViewLongClickListeners: (viewId: Int, data: Any?) -> Unit = { _, _ -> }

    override val containerView: View = itemView

    val parentRecyclerView: RecyclerView?
        get() {
            val parent = itemView.parent
            return if (parent is RecyclerView)
                parent
            else
                null
        }

    val context: Context
        get() = itemView.context

    constructor(parent: ViewGroup, @LayoutRes layoutRes: Int) : this(
        LayoutInflater.from(parent.context).inflate(layoutRes, parent, false)
    )

    abstract fun onBind(listItem: ListItem)

    fun onViewClick(@IdRes viewId: Int, isLongClick: Boolean = false) =
        onViewClick(viewId, Any(), isLongClick)

    fun <D : Any> onViewClick(@IdRes viewId: Int, data: D?, isLongClick: Boolean = false) {
        if (!isLongClick)
            onViewClickListeners.invoke(viewId, data)
        else
            onViewLongClickListeners.invoke(viewId, data)
    }

    fun <V : View> onViewClick(view: V, isLongClick: Boolean = false) {
        onViewClick(view, Any(), isLongClick)
    }

    fun <V : View, D : Any> onViewClick(view: V, data: D?, isLongClick: Boolean = false) {
        if (!isLongClick)
            onViewClickListeners.invoke(view.id, data)
        else
            onViewLongClickListeners.invoke(view.id, data)
    }

    fun <V : View> onViewClickWithPositionData(view: V, isLongClick: Boolean = false) {
        if (adapterPosition == -1) return
        onViewClick(view, adapterPosition, isLongClick)
    }

    fun onViewClickWithPositionData(viewId: Int, isLongClick: Boolean = false) {
        if (adapterPosition == -1) return
        onViewClick(viewId, adapterPosition, isLongClick)
    }
}<file_sep>/app-beteasier-kotlin/app/src/main/java/com/d/beteasier/api/BackEndMock.kt
package com.d.beteasier.api

object BackEndMock {

    private val users: MutableList<User> = mutableListOf(
        User(
            id = "1",
            email = "<EMAIL>",
            password = "<PASSWORD>",
            name = "Juris",
            surname = "Jurgaitis",
            country = "Zimbabwe",
            balance = 5000.0,
            monthlyLimit = 500.0,
            betLimit = 25.0,
            bets = mutableListOf(
                Bet(
                    userId = "1",
                    matchId = "7",
                    team = "PSG",
                    amount = 20.0,
                    result = Result.Lost
                ),
                Bet(
                    userId = "1",
                    matchId = "8",
                    team = "Real Madrid",
                    amount = 24.0,
                    result = Result.Won
                )
            )
        )
    )
    private var currentUser: User? = null

    private val matches: MutableList<Match> = mutableListOf(
        Match(
            "1",
            "Bayern Munich",
            "Schalke",
            "2020-06-02 19:00",
            Category.Football,
            1.1,
            3.1,
            false
        ),
        Match(
            "2",
            "Borussia",
            "Mainz",
            "2020-06-03 19:00",
            Category.Football,
            1.3,
            2.1,
            false
        ),
        Match(
            "3",
            "Real Madrid",
            "Barcelona",
            "2020-08-02 21:00",
            Category.Football,
            2.1,
            2.3,
            false
        ),
        Match(
            "4",
            "Zalgiris",
            "CSKA Moscow",
            "2020-09-10 20:00",
            Category.Basketball,
            2.0,
            1.6,
            false
        ),
        Match(
            "5",
            "Vilniaus Rytas",
            "Neptunas",
            "2020-09-12 21:00",
            Category.Basketball,
            1.1,
            2.1,
            false
        ),
        Match(
            "6",
            "<NAME>",
            "B<NAME>",
            "2020-10-02 18:00",
            Category.Baseball,
            2.1,
            2.1,
            false
            ),
        Match(
            "7",
            "PSG",
            "Juventus",
            "2020-02-02 19:00",
            Category.Football,
            1.4,
            1.3,
            true,
            1,
            2
        ),
        Match(
            "8",
            "Chelsea",
            "Real Madrid",
            "2020-02-02 19:00",
            Category.Football,
            1.1,
            2.1,
            true,
            1,
            3
        )
    )

    fun login(email: String, password: String): Boolean {
        currentUser = users.find { it.email == email && it.password == <PASSWORD> }
        return currentUser != null
    }

    fun register(email: String, password: String) =
        if (users.none { it.email == email }) {
            users.add(User((users.last().id.toInt() + 1).toString(), email, password))
            currentUser = users.last()
            true
        }
        else
            false

    fun getCurrentUser() = currentUser

    fun updateUser(
        name: String,
        surname: String,
        country: String,
        monthlyLimit: String,
        betLimit: String
    ) = currentUser?.apply {
        if (name.isNotBlank()) this.name = name
        if (surname.isNotBlank()) this.surname = surname
        if (country.isNotBlank()) this.country = country
        if (monthlyLimit.isNotBlank()) this.monthlyLimit = monthlyLimit.toDouble()
        if (betLimit.isNotBlank()) this.betLimit = betLimit.toDouble()
    }


    fun addFunds(amount: Double) {
        val currentBalance = currentUser?.balance ?: 0.0
        currentUser?.balance = currentBalance + amount
    }

    fun addBet(bet: Bet) {
        currentUser?.bets?.add(bet)
    }

    fun deleteCurrentUser() {
        users.remove(currentUser)
        currentUser = null
    }

    fun getMatches() = matches.toList()

    fun getMatchById(id: String) = matches.first { it.id == id }

    fun getBets() = currentUser?.bets ?: listOf<Bet>()

    fun getBetByMatchId(id: String) = currentUser?.bets?.first { it.matchId == id }
}<file_sep>/app-beteasier-kotlin/app/src/main/java/com/d/beteasier/api/User.kt
package com.d.beteasier.api

data class User(
    val id: String,
    val email: String,
    val password: String,
    var name: String? = null,
    var surname: String? = null,
    var country: String? = null,
    var balance: Double? = null,
    var monthlyLimit: Double? = null,
    var betLimit: Double? = null,
    val bets: MutableList<Bet> = mutableListOf()
)<file_sep>/README.md
# Beteasier
Betting app developed for Android using Kotlin for Software Engineering course at Vilnius University.

➡ New documentation and app created by:  
<NAME>  
<NAME>  
<NAME>  
<NAME>
  
➡ Old documentation created by:  
<NAME>  
<NAME>  
<NAME>  
<NAME> 
<file_sep>/app-beteasier-kotlin/app/src/main/java/com/d/beteasier/bet/BetsViewModel.kt
package com.d.beteasier.bet

import androidx.lifecycle.ViewModel
import com.d.beteasier.api.BackEndMock

class BetsViewModel : ViewModel() {

    fun getBets() = BackEndMock.getBets()
}<file_sep>/app-beteasier-kotlin/app/src/main/java/com/d/beteasier/match/MatchViewHolder.kt
package com.d.beteasier.match

import android.view.ViewGroup
import com.d.beteasier.R
import com.d.beteasier.api.Match
import com.d.beteasier.base.BaseAdapter
import com.d.beteasier.base.BaseHolder
import kotlinx.android.synthetic.main.holder_match.*

class MatchViewHolder(parent: ViewGroup) : BaseHolder<BaseAdapter.SingleTypeItem<Match>>(
    parent,
    R.layout.holder_match
) {

    override fun onBind(listItem: BaseAdapter.SingleTypeItem<Match>) {
        listItem.item.apply {
            imageView.setImageResource(category.imageRes)
            firstTeamView.text = firstTeam
            secondTeamView.text = secondTeam
            dateTimeView.text = dateTime
        }
        openButton.setOnClickListener {
            onViewClick(it, listItem.item)
        }
    }
}<file_sep>/app-beteasier-kotlin/app/src/main/java/com/d/beteasier/profile/LoginActivity.kt
package com.d.beteasier.profile

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.d.beteasier.MainActivity
import com.d.beteasier.R
import kotlinx.android.synthetic.main.activity_login.*
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
import org.koin.androidx.viewmodel.ext.android.viewModel

class LoginActivity : AppCompatActivity() {

    private val viewModel: ProfileViewModel by viewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)
        initViews()
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == 0 && resultCode == Activity.RESULT_OK)
            finish()
    }

    private fun initViews() {
        loginButton.setOnClickListener {
            val email = emailInput.text.toString()
            val password = passwordInput.text.toString()
            when {
                email.isEmpty() -> {
                    emailInput?.error = "Please enter your email"
                    emailInput?.requestFocus()
                }
                password.isEmpty() -> {
                    passwordInput?.error = "Please enter your password"
                    passwordInput?.requestFocus()
                }
                else -> {
                    if (viewModel.login(email, password)) {
                        startActivity(Intent(this, MainActivity::class.java))
                        finish()
                    } else
                        Toast.makeText(this, "User not found", Toast.LENGTH_SHORT).show()
                }
            }
        }
        registerButton.setOnClickListener {
            startActivityForResult(Intent(this, RegisterActivity::class.java), 0)
        }
    }
}<file_sep>/app-beteasier-kotlin/app/src/main/java/com/d/beteasier/match/MatchInfoActivity.kt
package com.d.beteasier.match

import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.d.beteasier.R
import com.d.beteasier.api.Match
import com.d.beteasier.api.Result
import kotlinx.android.synthetic.main.activity_match_info.*
import org.koin.androidx.viewmodel.ext.android.viewModel

class MatchInfoActivity : AppCompatActivity() {

    private val viewModel: MatchesViewModel by viewModel()

    private val match: Match
        get() = viewModel.model

    private val shouldAllowBets: Boolean by lazy {
        intent.getBooleanExtra("ALLOW_BETS", true)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_match_info)
        initViews()
    }

    private fun initViews() {
        initMatchInfo()

        if (!shouldAllowBets) {
            replaceUIElements()

            val bet = viewModel.getBetByMatchId(match.id)
            val result = bet?.result ?: Result.TBD

            bettingAmountLabel.text = result.text
            bettingAmountView.text = when (result) {
                Result.Won -> {
                    val rate = if (bet?.team == match.firstTeam)
                        match.firstRate
                    else
                        match.secondRate
                    String.format("%.2f EUR", (bet?.amount ?: 0.0) * rate)
                }
                else -> "${bet?.amount.toString()} EUR"
            }
        } else {
            betButton.setOnClickListener {
                val user = viewModel.getCurrentUser()
                val betLimit = user?.betLimit ?: 10000000.0
                val firstBetEditable = firstTeamBet.text
                val secondBetEditable = secondTeamBet.text
                val firstBet = firstBetEditable?.toString() ?: ""
                val secondBet = secondBetEditable?.toString() ?: ""

                if (firstBet.isBlank() && secondBet.isBlank())
                    Toast.makeText(
                        this,
                        "Please enter the amount you wish to bet",
                        Toast.LENGTH_SHORT
                    ).show()
                else if ((!firstBet.isBlank() && firstBet.toDouble() > betLimit)
                    || (!secondBet.isBlank() && secondBet.toDouble() > betLimit)
                )
                    Toast.makeText(this, "You cannot exceed your bet limit", Toast.LENGTH_SHORT)
                        .show()
                else {
                    if (!firstBet.isBlank())
                        viewModel.addBet(
                            userId = user?.id ?: "1",
                            matchId = match.id,
                            team = match.firstTeam,
                            amount = firstBet.toDouble()
                        )
                    if (!secondBet.isBlank())
                        viewModel.addBet(
                            userId = user?.id ?: "1",
                            matchId = match.id,
                            team = match.secondTeam,
                            amount = secondBet.toDouble()
                        )
                    finish()
                }
            }
        }
    }

    private fun initMatchInfo() {
        imageView.setImageResource(match.category.imageRes)
        dateTimeView.text = match.dateTime
        firstTeamView.text = match.firstTeam
        secondTeamView.text = match.secondTeam
        firstRateView.text = match.firstRate.toString()
        secondRateView.text = match.secondRate.toString()
    }

    private fun replaceUIElements() {
        firstTeamBet.visibility = View.GONE
        secondTeamBet.visibility = View.GONE
        betButton.visibility = View.GONE
        bettingAmountLabel.visibility = View.VISIBLE
        bettingAmountView.visibility = View.VISIBLE

        firstTeamScore.visibility = View.VISIBLE
        secondTeamScore.visibility = View.VISIBLE
        scoreDivider.visibility = View.VISIBLE
        firstTeamScore.text = match.firstTeamScore?.toString() ?: "TBD"
        secondTeamScore.text = match.secondTeamScore?.toString() ?: "TBD"
    }
}<file_sep>/app-beteasier-kotlin/settings.gradle
rootProject.name='BetEasier'
include ':app'
<file_sep>/app-beteasier-kotlin/app/src/main/java/com/d/beteasier/match/MatchesViewModel.kt
package com.d.beteasier.match

import androidx.lifecycle.ViewModel
import com.d.beteasier.api.BackEndMock
import com.d.beteasier.api.Bet
import com.d.beteasier.api.Match
import com.d.beteasier.api.User

class MatchesViewModel : ViewModel() {

    private var matchList: List<Match> = listOf()
    var user: User? = null
    lateinit var model: Match

    fun getMatches(): List<Match> {
        if (matchList.isEmpty())
            matchList = BackEndMock.getMatches()
        return matchList
    }

    fun getBetByMatchId(id: String?) = BackEndMock.getBetByMatchId(id ?: "")

    fun getCurrentUser(): User? {
        if (user == null)
            user = BackEndMock.getCurrentUser()
        return user
    }

    fun addBet(userId: String, matchId: String, team: String, amount: Double) = BackEndMock.addBet(
        Bet(
            userId,
            matchId,
            team,
            amount
        )
    )

    fun setModelFromMatchId(id: String) {
        model = BackEndMock.getMatchById(id)
    }
}<file_sep>/app-beteasier-kotlin/app/src/main/java/com/d/beteasier/api/Match.kt
package com.d.beteasier.api

import android.os.Parcelable
import androidx.annotation.DrawableRes
import com.d.beteasier.R
import kotlinx.android.parcel.Parcelize

@Parcelize
data class Match(
    val id: String,
    val firstTeam: String,
    val secondTeam: String,
    val dateTime: String,
    val category: Category,
    val firstRate: Double,
    val secondRate: Double,
    val isFinished: Boolean,
    val firstTeamScore: Int? = null,
    val secondTeamScore: Int? = null
) : Parcelable

enum class Category(@DrawableRes val imageRes: Int) {
    Football(R.drawable.ic_football),
    Basketball(R.drawable.ic_basketball),
    Baseball(R.drawable.ic_baseball);
}
<file_sep>/app-beteasier-kotlin/app/src/main/java/com/d/beteasier/base/BaseAdapter.kt
package com.d.beteasier.base

import android.view.ViewGroup
import androidx.annotation.CallSuper
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.RecyclerView
import com.d.beteasier.util.indexOfFirstOrNull

@Suppress("UNCHECKED_CAST")
abstract class BaseAdapter<ListItem> : RecyclerView.Adapter<BaseHolder<ListItem>>() {

    private var onViewClickListeners = mutableMapOf<Enum<*>, (viewId: Int, data: Any?) -> Unit>()
    private var onViewLongClickListeners = mutableMapOf<Enum<*>, (viewId: Int, data: Any?) -> Unit>()

    var items: MutableList<ListItem> = mutableListOf()
        set(value) {
            field = sort(value).toMutableList()
            notifyDataSetChanged()
        }

    /**
     * Use it for auto attach/detach RecyclerView ItemDecorations.
     * If you update after adapter is set to the RecyclerView - changes will not show
     */
    open val itemAnimator: RecyclerView.ItemAnimator = DefaultItemAnimator()
    open val itemDecorations = mutableListOf<RecyclerView.ItemDecoration>()
    open var baseRecyclerView: RecyclerView? = null

    abstract fun <VH : BaseHolder<ListItem>> getViewHolder(parent: ViewGroup, viewType: Enum<*>): VH

    open fun getViewTypeEnum(ordinal: Int): Enum<*> = BaseViewType.SINGLE

    final override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseHolder<ListItem> {
        val viewTypeEnum = getViewTypeEnum(viewType)
        val viewHolder = getViewHolder<BaseHolder<ListItem>>(parent, viewTypeEnum)
        val clickListenerPos = onViewClickListeners.keys.firstOrNull { it == BaseViewType.KEY_ALL_TYPES_ON_CLICK || it == viewTypeEnum }
        val longClickListenerPos = onViewLongClickListeners.keys.firstOrNull { it == BaseViewType.KEY_ALL_TYPES_ON_CLICK || it == viewTypeEnum }
        clickListenerPos?.let {
            viewHolder.onViewClickListeners = { int, any ->
                onViewClickListeners[clickListenerPos]?.invoke(int, any)
            }
        }
        longClickListenerPos?.let {
            viewHolder.onViewLongClickListeners = { int, any ->
                onViewLongClickListeners[clickListenerPos]?.invoke(int, any)
            }
        }

        try {
            return viewHolder
        } finally {
            onAfterCreateViewHolder(viewHolder)
        }
    }

    open fun onAfterCreateViewHolder(holder: BaseHolder<ListItem>) = Unit

    override fun getItemViewType(position: Int): Int {
        val item = items[position]
        return if (item is BaseItem<*>)
            item.viewType.ordinal
        else
            super.getItemViewType(position)
    }

    @CallSuper
    override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
        this.baseRecyclerView = recyclerView
        recyclerView.itemAnimator = itemAnimator
        itemDecorations.forEach { recyclerView.addItemDecoration(it) }
    }

    @CallSuper
    override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
        this.baseRecyclerView = null
        itemDecorations.forEach { recyclerView.removeItemDecoration(it) }
    }

    override fun onBindViewHolder(holder: BaseHolder<ListItem>, position: Int) {
        holder.onBind(items[position])
    }

    override fun getItemCount(): Int = items.size

    open fun sort(items: List<ListItem>): List<ListItem> = items

    fun <D> setOnViewClickListener(callback: (viewId: Int, data: D) -> Unit) {
        // This type is used for global click listeners
        onViewClickListeners[BaseViewType.KEY_ALL_TYPES_ON_CLICK] = { viewId, any ->
            callback(viewId, any as D)
        }
    }

    fun <D> addOnViewTypeViewClickListener(viewType: Enum<*>, callback: (viewId: Int, data: D) -> Unit) {
        onViewClickListeners[viewType] = { int, any ->
            callback.invoke(int, any as D)
        }
    }

    fun <D> setOnViewLongClickListener(callback: (viewId: Int, data: D) -> Unit) {
        // This type is used for global click listeners
        onViewLongClickListeners[BaseViewType.KEY_ALL_TYPES_ON_CLICK] = { viewId, any ->
            callback(viewId, any as D)
        }
    }

    fun <D> addOnViewTypeViewLongClickListener(viewType: Enum<*>, callback: (viewId: Int, data: D) -> Unit) {
        onViewLongClickListeners[viewType] = { int, any ->
            callback.invoke(int, any as D)
        }
    }

    fun clearViewClickListeners() = onViewClickListeners.clear()

    fun clearViewLongClickListeners() = onViewLongClickListeners.clear()

    fun <BH : BaseHolder<*>> findViewHolderByType(viewType: Enum<*>): BH? {
        val index = items.indexOfFirstOrNull { (it as? BaseItem<*>)?.viewType == viewType }
            ?: return null
        return baseRecyclerView?.findViewHolderForLayoutPosition(index) as? BH
    }

    fun <BH : BaseHolder<*>> findViewHolderByIndex(index: Int): BH? =
        baseRecyclerView?.findViewHolderForLayoutPosition(index) as? BH

    fun removeAt(position: Int, notify: Boolean = true) {
        items.removeAt(position)
        if (notify)
            notifyItemRemoved(position)
    }

    fun <ListItem> updateAt(
        position: Int,
        notify: Boolean = true,
        update: ListItem.() -> Unit
    ) {
        (items[position] as ListItem).update()
        if (notify)
            notifyItemChanged(position)
    }

    fun <T> notifyItemChanged(viewType: Enum<*>) =
        notifyItemChanged(items.map { it as? BaseItem<T> }.indexOfFirst { it?.viewType == viewType })

    fun <T> getItemModel(viewType: Enum<*>) =
        items.map { it as? BaseItem<T> }.firstOrNull { it?.viewType == viewType }?.item

    fun <T> getItemModel(position: Int) = (items[position] as? BaseItem<T>)?.item

    open class BaseItem<ListItem>(val viewType: Enum<*>, var item: ListItem)

    open class SingleTypeItem<ListItem>(item: ListItem) : BaseItem<ListItem>(BaseViewType.SINGLE, item)

    enum class BaseViewType {
        KEY_ALL_TYPES_ON_CLICK,
        SINGLE
    }
}
<file_sep>/app-beteasier-kotlin/app/src/main/java/com/d/beteasier/match/MatchesAdapter.kt
package com.d.beteasier.match

import android.view.ViewGroup
import com.d.beteasier.api.Category
import com.d.beteasier.api.Match
import com.d.beteasier.base.BaseAdapter
import com.d.beteasier.base.BaseHolder

class MatchesAdapter : BaseAdapter<BaseAdapter.SingleTypeItem<Match>>() {

    override fun <VH : BaseHolder<SingleTypeItem<Match>>> getViewHolder(
        parent: ViewGroup,
        viewType: Enum<*>
    ): VH = MatchViewHolder(parent) as VH

    fun initItems(matches: List<Match>){
        items = matches.map { SingleTypeItem(it) }.toMutableList()
    }

    fun filterItems(matches: List<Match>, category: Category) {
        items = matches.filter { match -> match.category == category }.map { SingleTypeItem(it) }.toMutableList()
    }
}<file_sep>/app-beteasier-kotlin/app/src/main/java/com/d/beteasier/profile/ProfileViewModel.kt
package com.d.beteasier.profile

import androidx.lifecycle.ViewModel
import com.d.beteasier.api.BackEndMock
import com.d.beteasier.api.User

class ProfileViewModel : ViewModel() {

    private var model: User? = null

    fun isPasswordValid(password: String) =
        password.matches(Regex("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{8,}$"))

    fun register(email: String, password: String) = BackEndMock.register(email, password)

    fun login(email: String, password: String) = BackEndMock.login(email, password)

    fun getCurrentUser(): User? {
        if (model == null)
            model = BackEndMock.getCurrentUser()
        return model
    }

    fun updateProfile(
        name: String,
        surname: String,
        country: String,
        monthlyLimit: String,
        betLimit: String
    ) {
        BackEndMock.updateUser(
            name,
            surname,
            country,
            monthlyLimit,
            betLimit
        )
        model = BackEndMock.getCurrentUser()
    }

    fun addFunds(amount: Double) = BackEndMock.addFunds(amount)

    fun deleteCurrentUser() = BackEndMock.deleteCurrentUser()
}<file_sep>/app-beteasier-kotlin/app/src/main/java/com/d/beteasier/api/Statistic.kt
package com.d.beteasier.api

class Statistic {
}<file_sep>/app-beteasier-kotlin/app/src/main/java/com/d/beteasier/bet/BetViewHolder.kt
package com.d.beteasier.bet

import android.view.ViewGroup
import com.d.beteasier.R
import com.d.beteasier.api.BackEndMock
import com.d.beteasier.api.Bet
import com.d.beteasier.api.Result
import com.d.beteasier.base.BaseAdapter
import com.d.beteasier.base.BaseHolder
import kotlinx.android.synthetic.main.holder_bet.*

class BetViewHolder(parent: ViewGroup) : BaseHolder<BaseAdapter.SingleTypeItem<Bet>>(
    parent,
    R.layout.holder_bet
) {

    override fun onBind(listItem: BaseAdapter.SingleTypeItem<Bet>) {
        val bet = listItem.item
        val match = BackEndMock.getMatchById(bet.matchId) //TODO REFACTOR do not fetch from BE here
        imageView.setImageResource(match.category.imageRes)
        firstTeamView.text = match.firstTeam
        secondTeamView.text = match.secondTeam
        dateTimeView.text = match.dateTime
        resultView.text = bet.result.text
        amountView.text = when(bet.result) {
            Result.Won -> {
                val rate = if (bet.team == match.firstTeam)
                    match.firstRate
                else
                    match.secondRate
                String.format("%.2f EUR", bet.amount * rate)
            }
            else -> "${bet.amount.toString()} EUR"
        }
        openButton.setOnClickListener {
            onViewClick(it, bet.matchId)
        }
    }
}<file_sep>/app-beteasier-kotlin/app/src/main/java/com/d/beteasier/bet/BetsAdapter.kt
package com.d.beteasier.bet

import android.view.ViewGroup
import com.d.beteasier.api.Bet
import com.d.beteasier.base.BaseAdapter
import com.d.beteasier.base.BaseHolder

class BetsAdapter : BaseAdapter<BaseAdapter.SingleTypeItem<Bet>>() {

    override fun <VH : BaseHolder<SingleTypeItem<Bet>>> getViewHolder(
        parent: ViewGroup,
        viewType: Enum<*>
    ): VH = BetViewHolder(parent) as VH

    fun initItems(bets: List<Bet>){
        items = bets.map { SingleTypeItem(it) }.toMutableList()
    }
}<file_sep>/app-beteasier-kotlin/app/src/main/java/com/d/beteasier/util/Extensions.kt
package com.d.beteasier.util

import androidx.annotation.IdRes
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentTransaction
import androidx.lifecycle.Lifecycle
import com.d.beteasier.base.BaseFragment

fun <T : BaseFragment> FragmentActivity.replaceAndCommit(
    @IdRes containerId: Int,
    fragment: T,
    extraCalls: FragmentTransaction.() -> Unit = {}
) =
    supportFragmentManager.beginTransaction().apply {
        if (fragment.isAdded) return@apply
        extraCalls()
        replace(containerId, fragment, fragment::class.java.name)
        safeCommit(lifecycle.currentState)
    }

fun FragmentTransaction.safeCommit(lifecycleState: Lifecycle.State) =
    if (lifecycleState.isAtLeast(Lifecycle.State.RESUMED))
        commit()
    else
        commitAllowingStateLoss()

inline fun <T> List<T>.indexOfFirstOrNull(predicate: (T) -> Boolean): Int? {
    val index = indexOfFirst(predicate)
    if (index == -1) {
        return null
    }
    return index
}<file_sep>/app-beteasier-kotlin/app/src/main/java/com/d/beteasier/MainActivity.kt
package com.d.beteasier

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.d.beteasier.bet.BetsFragment
import com.d.beteasier.match.MatchesFragment
import com.d.beteasier.profile.ProfileFragment
import com.d.beteasier.util.replaceAndCommit
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    private val matchesFragment: MatchesFragment by lazy {
        MatchesFragment()
    }
    private val betsFragment: BetsFragment by lazy {
        BetsFragment()
    }
    private val profileFragment: ProfileFragment by lazy {
        ProfileFragment()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initBottomNavigationView()
    }

    private fun initBottomNavigationView() =
        bottomNavigationView.apply {
            setOnNavigationItemSelectedListener {
                show(it.itemId)
                true
            }
            bottomNavigationView.selectedItemId = R.id.matchesItem
        }

    private fun show(menuItemId: Int) {
        val fragment = when (menuItemId) {
            R.id.matchesItem -> matchesFragment
            R.id.betsItem -> betsFragment
            else -> profileFragment
        }
        replaceAndCommit(R.id.fragmentContainer, fragment) {
            setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out)
        }
    }
}<file_sep>/app-beteasier-kotlin/app/src/main/java/com/d/beteasier/profile/ProfileFragment.kt
package com.d.beteasier.profile

import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.Toast
import com.d.beteasier.R
import com.d.beteasier.base.BaseFragment
import kotlinx.android.synthetic.main.fragment_profile.*
import org.koin.androidx.viewmodel.ext.android.viewModel

class ProfileFragment : BaseFragment() {

    override val fragmentLayoutRes: Int
        get() = R.layout.fragment_profile

    private val viewModel: ProfileViewModel by viewModel()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        loadUserData()
        initViews()
    }

    private fun loadUserData() = viewModel.getCurrentUser()?.apply {
        emailView.setText(email)
        balanceView.setText(balance?.let { "${it} EUR" } ?: "0.00 EUR")
        nameEditView.setText(name)
        surnameEditView.setText(surname)
        countryEditView.setText(country)
        monthlyLimitEditView.setText(monthlyLimit?.toString() ?: "")
        betLimitEditView.setText(betLimit?.toString() ?: "")
    }

    private fun initViews() {
        buttonUpdateInfo.setOnClickListener {
            val name = nameEditView.text.toString()
            val surname = surnameEditView.text.toString()
            val country = countryEditView.text.toString()
            val monthlyLimit = monthlyLimitEditView.text.toString()
            val betLimit = betLimitEditView.text.toString()

            viewModel.updateProfile(name, surname, country, monthlyLimit, betLimit)

            loadUserData()
            Toast.makeText(context, "Profile has been updated", Toast.LENGTH_SHORT).show()
        }
        addFundsButton.setOnClickListener {
            viewModel.addFunds(100.0)
            Toast.makeText(context, "Added 100 EUR from your PayPal account", Toast.LENGTH_SHORT)
                .show()
            balanceView.setText("${viewModel.getCurrentUser()?.balance.toString()} EUR")
        }
        deleteAccountButton.setOnClickListener {
            viewModel.deleteCurrentUser()
            startActivity(Intent(context, LoginActivity::class.java))
            activity?.finish()
        }
    }
}<file_sep>/app-beteasier-kotlin/app/src/main/java/com/d/beteasier/match/MatchesFragment.kt
package com.d.beteasier.match

import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import com.d.beteasier.base.BaseFragment
import com.d.beteasier.R
import com.d.beteasier.api.Category
import com.d.beteasier.api.Match
import kotlinx.android.synthetic.main.fragment_matches.*
import org.koin.androidx.viewmodel.ext.android.viewModel

class MatchesFragment : BaseFragment(), AdapterView.OnItemSelectedListener {

    override val fragmentLayoutRes: Int
        get() = R.layout.fragment_matches

    private val viewModel: MatchesViewModel by viewModel()

    private val adapter: MatchesAdapter by lazy {
        MatchesAdapter()
    }

    override fun onNothingSelected(parent: AdapterView<*>?) =
        adapter.initItems(viewModel.getMatches())

    override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) =
        when (position) {
            0 -> adapter.initItems(viewModel.getMatches())
            else -> adapter.filterItems(viewModel.getMatches(), Category.values()[position-1])
        }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        initAdapter()
        initSpinner()
    }

    override fun onResume() {
        super.onResume()
        adapter.initItems(viewModel.getMatches())
    }

    private fun initAdapter() =
        adapter.apply {
            recyclerView.adapter = this
            setOnViewClickListener<Match> { _, match ->
                viewModel.model = match
                startActivity(Intent(context, MatchInfoActivity::class.java))
            }
            initItems(viewModel.getMatches())
        }

    private fun initSpinner() =
        ArrayAdapter.createFromResource(
            requireContext(),
            R.array.category_array,
            android.R.layout.simple_spinner_item
        ).also { adapter ->
            adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
            categorySpinner.adapter = adapter
            categorySpinner.onItemSelectedListener = this@MatchesFragment
        }
}<file_sep>/app-beteasier-kotlin/app/src/main/java/com/d/beteasier/bet/BetsFragment.kt
package com.d.beteasier.bet

import android.content.Intent
import android.os.Bundle
import android.view.View
import com.d.beteasier.R
import com.d.beteasier.base.BaseFragment
import com.d.beteasier.match.MatchInfoActivity
import com.d.beteasier.match.MatchesViewModel
import kotlinx.android.synthetic.main.fragment_bets.*
import org.koin.androidx.viewmodel.ext.android.viewModel

class BetsFragment : BaseFragment() {

    override val fragmentLayoutRes: Int
        get() = R.layout.fragment_bets

    private val betsViewModel: BetsViewModel by viewModel()
    private val matchesViewModel: MatchesViewModel by viewModel() //TODO REFACTOR REMOVE DEPENDENCY ON THIS VM

    private val adapter: BetsAdapter by lazy {
        BetsAdapter()
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        initAdapter()
    }

    override fun onResume() {
        super.onResume()
        adapter.initItems(betsViewModel.getBets())
    }

    private fun initAdapter() = adapter.apply {
        recyclerView.adapter = this
        setOnViewClickListener<String> { _, matchId ->
            matchesViewModel.setModelFromMatchId(matchId)
            startActivity(
                Intent(
                    context,
                    MatchInfoActivity::class.java
                ).putExtra("ALLOW_BETS", false)
            )
        }
        initItems(betsViewModel.getBets())
    }
}<file_sep>/app-beteasier-kotlin/app/src/main/java/com/d/beteasier/api/Bet.kt
package com.d.beteasier.api

data class Bet(
    val userId: String,
    val matchId: String,
    val team: String,
    val amount: Double,
    val result: Result = Result.TBD
)

enum class Result(val text: String) {
    TBD("TBD:"),
    Won("Won:"),
    Lost("Lost:");
}<file_sep>/app-beteasier-kotlin/app/src/main/java/com/d/beteasier/profile/RegisterActivity.kt
package com.d.beteasier.profile

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.d.beteasier.MainActivity
import com.d.beteasier.R
import kotlinx.android.synthetic.main.activity_register.*
import org.koin.androidx.viewmodel.ext.android.viewModel

class RegisterActivity : AppCompatActivity() {

    private val viewModel: ProfileViewModel by viewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_register)
        initViews()
    }
    
    private fun initViews() {
        registerButton.setOnClickListener {
            val email = emailInput.text.toString()
            val password = passwordInput.text.toString()
            val repeatPassword = passwordRepeatInput.text.toString()

            when {
                email.isEmpty() -> {
                    emailInput.error = "Please enter your email"
                    emailInput.requestFocus()
                }
                password.isEmpty() -> {
                    passwordInput.error = "Please enter your password"
                    passwordInput.requestFocus()
                }
                repeatPassword.isEmpty() -> {
                    passwordRepeatInput.error = "Please repeat your password"
                    passwordRepeatInput.requestFocus()
                }
                password != repeatPassword ->
                    Toast.makeText(this, "Password do not match", Toast.LENGTH_SHORT).show()
                !viewModel.isPasswordValid(password) -> {
                    passwordInput.error = "Password has to be: minimum eight characters, at least one uppercase letter, one lowercase letter and one number"
                    passwordInput.requestFocus()
                }
                else -> {
                    if (viewModel.register(email, password)) {
                        startActivity(Intent(this, MainActivity::class.java))
                        setResult(Activity.RESULT_OK)
                        finish()
                    } else
                        Toast.makeText(this, "User exists with this email", Toast.LENGTH_SHORT).show()
                }
            }
        }
    }
}<file_sep>/app-beteasier-kotlin/app/src/main/java/com/d/beteasier/util/Koin.kt
package com.d.beteasier.util

import com.d.beteasier.bet.BetsViewModel
import com.d.beteasier.match.MatchesViewModel
import com.d.beteasier.profile.ProfileViewModel
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module

object Koin {

    val appModule = module {
        single { MatchesViewModel() }
        viewModel { ProfileViewModel() }
        viewModel { BetsViewModel() }
    }
}