Skip to content

Commit 0c6d45b

Browse files
committed
Merge branch 'feat/encounters' into 'master'
Základ pro encountery See merge request fmasa/pv239-project!82
2 parents 0f2f622 + b9c6c9a commit 0c6d45b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+2864
-1494
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@
1515
/node_modules
1616
firestore-debug.log
1717
firebase-debug.log
18+
ui-debug.log
1819
play-store.json

app/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ android {
5555
applicationIdSuffix ".debug"
5656
resValue "string", "app_name", "[Debug] WFRP Master"
5757
resValue "string", "character_ad_unit_id", "ca-app-pub-3940256099942544/6300978111"
58+
resValue "string", "game_master_ad_unit_id", "ca-app-pub-3940256099942544/6300978111"
5859
manifestPlaceholders = [analytics_deactivated: "true"]
5960
}
6061

@@ -64,6 +65,7 @@ android {
6465
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
6566
resValue "string", "app_name", "WFRP Master"
6667
resValue "string", "character_ad_unit_id", "ca-app-pub-8647604386686373/9919978313"
68+
resValue "string", "game_master_ad_unit_id", "ca-app-pub-8647604386686373/7714574658"
6769
manifestPlaceholders = [analytics_deactivated: "false"]
6870
}
6971
}

app/src/main/java/cz/muni/fi/rpg/di/container.kt

Lines changed: 19 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,17 @@ import cz.muni.fi.rpg.model.cache.CharacterRepositoryIdentityMap
1313
import cz.muni.fi.rpg.model.cache.PartyRepositoryIdentityMap
1414
import cz.muni.fi.rpg.model.domain.armour.Armor
1515
import cz.muni.fi.rpg.model.domain.character.*
16-
import cz.muni.fi.rpg.model.domain.inventory.InventoryItem
16+
import cz.muni.fi.rpg.model.domain.encounter.EncounterRepository
17+
import cz.muni.fi.rpg.model.domain.encounters.EncounterId
1718
import cz.muni.fi.rpg.model.domain.inventory.InventoryItemRepository
1819
import cz.muni.fi.rpg.model.domain.invitation.InvitationProcessor
19-
import cz.muni.fi.rpg.model.domain.party.Party
2020
import cz.muni.fi.rpg.model.domain.party.PartyRepository
21-
import cz.muni.fi.rpg.model.domain.skills.Skill
2221
import cz.muni.fi.rpg.model.domain.skills.SkillRepository
23-
import cz.muni.fi.rpg.model.domain.spells.Spell
2422
import cz.muni.fi.rpg.model.domain.spells.SpellRepository
25-
import cz.muni.fi.rpg.model.domain.talents.Talent
2623
import cz.muni.fi.rpg.model.domain.talents.TalentRepository
2724
import cz.muni.fi.rpg.model.firestore.*
2825
import cz.muni.fi.rpg.model.firestore.jackson.JacksonAggregateMapper
2926
import cz.muni.fi.rpg.model.firestore.repositories.*
30-
import cz.muni.fi.rpg.model.firestore.repositories.FirestoreCharacterFeatureRepository
31-
import cz.muni.fi.rpg.model.firestore.repositories.FirestoreCharacterRepository
32-
import cz.muni.fi.rpg.model.firestore.repositories.FirestoreInventoryItemRepository
33-
import cz.muni.fi.rpg.model.firestore.repositories.FirestorePartyRepository
34-
import cz.muni.fi.rpg.model.firestore.repositories.FirestoreSkillRepository
35-
import cz.muni.fi.rpg.model.firestore.repositories.FirestoreSpellRepository
36-
import cz.muni.fi.rpg.model.firestore.repositories.FirestoreTalentRepository
3727
import cz.muni.fi.rpg.ui.character.*
3828
import cz.muni.fi.rpg.ui.character.CharacterMiscFragment
3929
import cz.muni.fi.rpg.ui.character.edit.CharacterEditFragment
@@ -44,16 +34,17 @@ import cz.muni.fi.rpg.ui.characterCreation.CharacterInfoFormFragment
4434
import cz.muni.fi.rpg.ui.characterCreation.CharacterStatsFormFragment
4535
import cz.muni.fi.rpg.ui.common.AdManager
4636
import cz.muni.fi.rpg.ui.gameMaster.GameMasterFragment
37+
import cz.muni.fi.rpg.ui.gameMaster.encounters.EncounterDetailFragment
38+
import cz.muni.fi.rpg.ui.gameMaster.encounters.EncountersFragment
4739
import cz.muni.fi.rpg.ui.partyList.PartyListFragment
4840
import cz.muni.fi.rpg.viewModels.*
4941
import org.koin.android.viewmodel.dsl.viewModel
5042
import org.koin.androidx.fragment.dsl.fragment
5143
import org.koin.dsl.module
5244
import java.util.*
53-
import kotlin.reflect.KClass
5445

55-
private fun <T : Any> aggregateMapper(kclass: KClass<T>) =
56-
JacksonAggregateMapper(kclass, jacksonTypeRef())
46+
private inline fun <reified T : Any> aggregateMapper() =
47+
JacksonAggregateMapper(T::class, jacksonTypeRef())
5748

5849
val appModule = module {
5950

@@ -86,57 +77,20 @@ val appModule = module {
8677
/**
8778
* Repositories
8879
*/
89-
single<InventoryItemRepository> {
90-
FirestoreInventoryItemRepository(
91-
get(),
92-
aggregateMapper(InventoryItem::class)
93-
)
94-
}
80+
single<InventoryItemRepository> {FirestoreInventoryItemRepository(get(), aggregateMapper()) }
9581
single<CharacterRepository> {
96-
CharacterRepositoryIdentityMap(
97-
10,
98-
FirestoreCharacterRepository(
99-
get(),
100-
aggregateMapper(Character::class)
101-
)
102-
)
82+
CharacterRepositoryIdentityMap(10, FirestoreCharacterRepository(get(), aggregateMapper()))
10383
}
10484
single<PartyRepository> {
105-
PartyRepositoryIdentityMap(
106-
10,
107-
FirestorePartyRepository(
108-
get(),
109-
aggregateMapper(Party::class)
110-
)
111-
)
112-
}
113-
single<SkillRepository> {
114-
FirestoreSkillRepository(
115-
get(),
116-
aggregateMapper(Skill::class)
117-
)
118-
}
119-
single<TalentRepository> {
120-
FirestoreTalentRepository(
121-
get(),
122-
aggregateMapper(Talent::class)
123-
)
85+
PartyRepositoryIdentityMap(10, FirestorePartyRepository(get(), aggregateMapper()))
12486
}
125-
single<SpellRepository> {
126-
FirestoreSpellRepository(
127-
get(),
128-
aggregateMapper(Spell::class)
129-
)
130-
}
131-
87+
single<SkillRepository> { FirestoreSkillRepository(get(), aggregateMapper()) }
88+
single<TalentRepository> { FirestoreTalentRepository(get(), aggregateMapper()) }
89+
single<SpellRepository> { FirestoreSpellRepository(get(), aggregateMapper()) }
13290
single<CharacterFeatureRepository<Armor>> {
133-
FirestoreCharacterFeatureRepository(
134-
Feature.ARMOR,
135-
get(),
136-
Armor(),
137-
aggregateMapper(Armor::class)
138-
)
91+
FirestoreCharacterFeatureRepository(Feature.ARMOR, get(), Armor(), aggregateMapper())
13992
}
93+
single<EncounterRepository> { FirestoreEncounterRepository(get(), aggregateMapper()) }
14094

14195
single { AdManager(get()) }
14296

@@ -148,6 +102,8 @@ val appModule = module {
148102
viewModel { (characterId: CharacterId) -> CharacterMiscViewModel(characterId, get(), get()) }
149103
viewModel { (characterId: CharacterId) -> CharacterViewModel(characterId, get(), get())}
150104
viewModel { (partyId: UUID) -> GameMasterViewModel(partyId, get(), get()) }
105+
viewModel { (partyId: UUID) -> EncountersViewModel(partyId, get()) }
106+
viewModel { (encounterId: EncounterId) -> EncounterDetailViewModel(encounterId, get(), get()) }
151107
viewModel { (characterId: CharacterId) -> InventoryViewModel(characterId, get(), get(), get()) }
152108
viewModel { (characterId: CharacterId) -> SkillsViewModel(characterId, get()) }
153109
viewModel { (characterId: CharacterId) -> SpellsViewModel(characterId, get()) }
@@ -159,7 +115,7 @@ val appModule = module {
159115
* Fragments
160116
*/
161117
fragment { CharacterFragment(get()) }
162-
fragment { GameMasterFragment() }
118+
fragment { GameMasterFragment(get()) }
163119
fragment { NavHostFragment() }
164120
fragment { PartyListFragment(get()) }
165121
fragment { CharacterEditFragment(get()) }
@@ -172,4 +128,6 @@ val appModule = module {
172128
fragment { CharacterCreationFragment(get()) }
173129
fragment { TalentsFragment() }
174130
fragment { CharacterArmorFragment() }
131+
fragment { EncountersFragment() }
132+
fragment { EncounterDetailFragment() }
175133
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package cz.muni.fi.rpg.model.domain.encounter
2+
3+
import java.util.*
4+
5+
class Encounter(
6+
val id: UUID,
7+
name: String,
8+
description: String,
9+
position: Int
10+
) {
11+
var name: String = name
12+
private set
13+
14+
var description: String = description
15+
private set
16+
17+
var position: Int = position
18+
set(value) {
19+
require(value >= 0)
20+
field = value
21+
}
22+
23+
var completed: Boolean = false
24+
private set
25+
26+
companion object {
27+
const val NAME_MAX_LENGTH = 100
28+
const val DESCRIPTION_MAX_LENGTH = 1000
29+
}
30+
31+
init {
32+
validate(name, description)
33+
}
34+
35+
fun update(name: String, description: String) {
36+
validate(name, description)
37+
38+
this.name = name
39+
this.description = description
40+
}
41+
42+
private fun validate(name: String, description: String) {
43+
require(name.isNotBlank() && name.length <= NAME_MAX_LENGTH)
44+
require(description.length <= DESCRIPTION_MAX_LENGTH)
45+
}
46+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package cz.muni.fi.rpg.model.domain.encounter
2+
3+
import cz.muni.fi.rpg.model.domain.encounters.EncounterId
4+
5+
class EncounterNotFound(id: EncounterId, cause: Throwable?)
6+
: Exception("Encounter ${id.encounterId} was not found in party ${id.partyId}", cause)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package cz.muni.fi.rpg.model.domain.encounter
2+
3+
import androidx.lifecycle.LiveData
4+
import arrow.core.Either
5+
import cz.muni.fi.rpg.model.domain.encounters.EncounterId
6+
import java.util.*
7+
8+
interface EncounterRepository {
9+
/**
10+
* Returns current state of encounter
11+
*
12+
* @throws EncounterNotFound if encounter does not exist.
13+
*/
14+
suspend fun get(id: EncounterId): Encounter
15+
16+
/**
17+
* Returns Encounter as LiveData that are updated when stored version changes
18+
*/
19+
fun getLive(id: EncounterId): LiveData<Either<EncounterNotFound, Encounter>>
20+
21+
/**
22+
* Creates or updates encounter
23+
*/
24+
suspend fun save(partyId: UUID, vararg encounters: Encounter)
25+
26+
/**
27+
* Returns LiveData representation of all encounters in party sorted by their position
28+
*/
29+
fun findByParty(partyId: UUID): LiveData<List<Encounter>>
30+
31+
/**
32+
* Removes encounter if it exists or does nothing if it does not
33+
*/
34+
suspend fun remove(id: EncounterId)
35+
36+
/**
37+
* Returns value that can be used for new encounter so that it's sorted at the end
38+
*/
39+
suspend fun getNextPosition(partyId: UUID): Int
40+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package cz.muni.fi.rpg.model.domain.encounters
2+
3+
import android.os.Parcelable
4+
import cz.muni.fi.rpg.ui.gameMaster.encounters.UUIDParceler
5+
import kotlinx.android.parcel.Parcelize
6+
import kotlinx.android.parcel.TypeParceler
7+
import java.util.*
8+
9+
@Parcelize
10+
data class EncounterId(
11+
@TypeParceler<UUID, UUIDParceler>
12+
val partyId: UUID,
13+
14+
@TypeParceler<UUID, UUIDParceler>
15+
val encounterId: UUID
16+
) : Parcelable

app/src/main/java/cz/muni/fi/rpg/model/firestore/constants.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ package cz.muni.fi.rpg.model.firestore
33
const val COLLECTION_PARTIES = "parties"
44
const val COLLECTION_CHARACTERS = "characters"
55
const val COLLECTION_FEATURES = "features"
6+
const val COLLECTION_ENCOUNTERS = "encounters"
67
const val COLLECTION_INVENTORY_ITEMS = "inventory"
78
const val COLLECTION_SKILLS = "skills"
89
const val COLLECTION_TALENTS = "talents"
9-
const val COLLECTION_SPELLS = "spells"
10+
const val COLLECTION_SPELLS = "spells"
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package cz.muni.fi.rpg.model.firestore.repositories
2+
3+
import androidx.lifecycle.LiveData
4+
import arrow.core.Either
5+
import com.google.firebase.firestore.FirebaseFirestore
6+
import com.google.firebase.firestore.FirebaseFirestoreException
7+
import com.google.firebase.firestore.Query
8+
import com.google.firebase.firestore.SetOptions
9+
import cz.muni.fi.rpg.model.domain.encounter.Encounter
10+
import cz.muni.fi.rpg.model.domain.encounter.EncounterNotFound
11+
import cz.muni.fi.rpg.model.domain.encounter.EncounterRepository
12+
import cz.muni.fi.rpg.model.firestore.*
13+
import cz.muni.fi.rpg.model.firestore.AggregateMapper
14+
import cz.muni.fi.rpg.model.firestore.DocumentLiveData
15+
import cz.muni.fi.rpg.model.firestore.QueryLiveData
16+
import cz.muni.fi.rpg.model.domain.encounters.EncounterId
17+
import kotlinx.coroutines.tasks.await
18+
import java.util.*
19+
20+
internal class FirestoreEncounterRepository(
21+
private val firestore: FirebaseFirestore,
22+
private val mapper: AggregateMapper<Encounter>
23+
) : EncounterRepository {
24+
private val parties = firestore.collection(COLLECTION_PARTIES)
25+
26+
override suspend fun get(id: EncounterId): Encounter {
27+
try {
28+
return mapper.fromDocumentSnapshot(
29+
encounters(id.partyId)
30+
.document(id.encounterId.toString())
31+
.get()
32+
.await()
33+
)
34+
} catch (e: FirebaseFirestoreException) {
35+
throw EncounterNotFound(id, e)
36+
}
37+
}
38+
39+
override fun getLive(id: EncounterId): LiveData<Either<EncounterNotFound, Encounter>> {
40+
return DocumentLiveData(encounters(id.partyId).document(id.encounterId.toString())) {
41+
it.bimap(
42+
{ e -> EncounterNotFound(id, e) },
43+
mapper::fromDocumentSnapshot
44+
)
45+
}
46+
}
47+
48+
override suspend fun save(partyId: UUID, vararg encounters: Encounter) {
49+
firestore.runTransaction { transaction ->
50+
encounters.forEach { encounter ->
51+
transaction.set(
52+
encounters(partyId).document(encounter.id.toString()),
53+
mapper.toDocumentData(encounter),
54+
SetOptions.merge()
55+
)
56+
}
57+
}.await()
58+
}
59+
60+
override fun findByParty(partyId: UUID): LiveData<List<Encounter>> {
61+
return QueryLiveData(
62+
encounters(partyId).orderBy("position", Query.Direction.ASCENDING),
63+
mapper
64+
)
65+
}
66+
67+
override suspend fun remove(id: EncounterId) {
68+
encounters(id.partyId).document(id.encounterId.toString()).delete().await()
69+
}
70+
71+
override suspend fun getNextPosition(partyId: UUID): Int {
72+
val snapshot = encounters(partyId)
73+
.orderBy("position", Query.Direction.DESCENDING)
74+
.get()
75+
.await()
76+
77+
val lastPosition = snapshot.documents.map(mapper::fromDocumentSnapshot)
78+
.getOrNull(0)?.position ?: -1
79+
80+
return lastPosition + 1
81+
}
82+
83+
private fun encounters(partyId: UUID) =
84+
parties.document(partyId.toString()).collection(COLLECTION_ENCOUNTERS)
85+
}

app/src/main/java/cz/muni/fi/rpg/ui/common/utils.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cz.muni.fi.rpg.ui.common
22

33
import android.os.Parcelable
44
import android.view.View
5+
import androidx.appcompat.app.AlertDialog
56
import androidx.fragment.app.Fragment
67
import java.io.Serializable
78

0 commit comments

Comments
 (0)