📖 Przegląd

Moduł Achievements umożliwia dodanie systemu osiągnięć i gamifikacji do aplikacji. Wspiera różne typy osiągnięć: jednorazowe, czasowe, streaki i ilościowe.

Szybki przykład
// Definiowanie osiągnięcia
ADict.Achievements.define("first_purchase") {
    title = "Pierwszy zakup!"
    description = "Dokonaj pierwszego zakupu"
    points = 100
    type = AchievementType.ONE_TIME
}

// Odblokowanie
ADict.Achievements.unlock("first_purchase")

// Inkrementacja postępu
ADict.Achievements.increment("articles_read", 1)

📦 Typy osiągnięć

Typ Opis Przykład
ONE_TIME Jednorazowe - odblokuj raz i zostaje Pierwszy zakup, Zarejestruj konto
PROGRESSIVE Ilościowe - wymaga wykonania N czynności Przeczytaj 100 artykułów
STREAK Streak - wymaga ciągłości 7 dni z rzędu
TIMED Czasowe - dostępne tylko przez określony czas Świąteczna promocja

📝 Definiowanie osiągnięć

define(id: String, config: DefinitionBuilder.() -> Unit)

Zdefiniuj nowe osiągnięcie.

Właściwości DefinitionBuilder

Właściwość Typ Opis
title String Tytuł osiągnięcia
description String Opis osiągnięcia
icon Int? ID zasobu ikony (@DrawableRes)
points Int Liczba punktów za osiągnięcie
type AchievementType Typ osiągnięcia
targetCount Int Cel dla PROGRESSIVE/STREAK
hidden Boolean Czy ukryte do momentu odblokowania
expiresAt Long? Timestamp wygaśnięcia (TIMED)
availableFrom Long? Timestamp dostępności (TIMED)
category String? Kategoria osiągnięcia
tier Int Poziom (1=bronze, 2=silver, 3=gold, 4=platinum)
prerequisiteIds List<String> Wymagane wcześniejsze osiągnięcia
Definiowanie różnych typów
// Jednorazowe
ADict.Achievements.define("first_purchase") {
    title = "Pierwszy zakup!"
    description = "Dokonaj pierwszego zakupu w aplikacji"
    icon = R.drawable.badge_purchase
    points = 100
    type = AchievementType.ONE_TIME
    category = "shopping"
    tier = 1 // Bronze
}

// Ilościowe (progressive)
ADict.Achievements.define("read_100_articles") {
    title = "Czytelnik"
    description = "Przeczytaj 100 artykułów"
    points = 500
    type = AchievementType.PROGRESSIVE
    targetCount = 100
    category = "reading"
    tier = 3 // Gold
}

// Streak
ADict.Achievements.define("streak_7") {
    title = "Tydzień z rzędu"
    description = "Używaj aplikacji 7 dni z rzędu"
    points = 200
    type = AchievementType.STREAK
    targetCount = 7
    tier = 2 // Silver
}

// Czasowe (np. świąteczne)
val christmasStart = Calendar.getInstance().apply {
    set(2024, Calendar.DECEMBER, 20)
}.timeInMillis
val christmasEnd = Calendar.getInstance().apply {
    set(2024, Calendar.DECEMBER, 27)
}.timeInMillis

ADict.Achievements.define("christmas_shopper") {
    title = "Świąteczny zakupoholik"
    description = "Dokonaj zakupu w okresie świątecznym"
    points = 300
    type = AchievementType.TIMED
    availableFrom = christmasStart
    expiresAt = christmasEnd
    hidden = true
}

// Z wymaganiami
ADict.Achievements.define("master_shopper") {
    title = "Mistrz zakupów"
    description = "Dokonaj 50 zakupów"
    points = 1000
    type = AchievementType.PROGRESSIVE
    targetCount = 50
    prerequisiteIds = listOf("first_purchase") // Wymaga first_purchase
    tier = 4 // Platinum
}

🔓 Odblokowywanie

unlock(id: String): Boolean

Odblokuj osiągnięcie (dla ONE_TIME).

Zwraca: true jeśli odblokowano, false jeśli już było odblokowane lub nie spełniono wymagań

Odblokowywanie osiągnięć
// Proste odblokowanie
if (ADict.Achievements.unlock("first_purchase")) {
    showAchievementUnlockedDialog("first_purchase")
}

// W kontekście zakupu
fun onPurchaseCompleted(product: Product) {
    // Sprawdź i odblokuj różne osiągnięcia
    ADict.Achievements.unlock("first_purchase")

    if (product.category == "premium") {
        ADict.Achievements.unlock("premium_buyer")
    }

    // Inkrementuj purchase counter
    ADict.Achievements.increment("total_purchases")
}

📈 Postęp i streaki

increment(id: String, amount: Int = 1): Boolean

Inkrementuj postęp (dla PROGRESSIVE).

Zwraca: true jeśli osiągnięcie zostało odblokowane

setProgress(id: String, count: Int): Boolean

Ustaw postęp bezpośrednio.

recordStreakDay(id: String): Boolean

Zapisz dzień streaka (dla STREAK).

resetStreak(id: String)

Zresetuj streak.

Zarządzanie postępem
// Progressive - inkrementacja
fun onArticleRead(articleId: String) {
    val unlocked = ADict.Achievements.increment("read_100_articles")
    if (unlocked) {
        showCongratulations("Przeczytałeś 100 artykułów!")
    }

    // Możesz też dodać więcej niż 1
    ADict.Achievements.increment("total_words_read", article.wordCount)
}

// Streak - zapisz dzień
fun onAppOpened() {
    val unlocked = ADict.Achievements.recordStreakDay("streak_7")
    if (unlocked) {
        showCongratulations("7 dni z rzędu!")
    }

    // Sprawdź aktualny streak
    val state = ADict.Achievements.get("streak_7")?.state
    Log.d("Achievements", "Current streak: ${state?.streakDays}")
}

// Ustaw postęp bezpośrednio (np. z serwera)
fun syncProgressFromServer(serverProgress: Int) {
    ADict.Achievements.setProgress("read_100_articles", serverProgress)
}

🔍 Pobieranie osiągnięć

getAll(includeHidden: Boolean = false): List<Achievement>

Pobierz wszystkie osiągnięcia.

getUnlocked(): List<Achievement>

Pobierz odblokowane osiągnięcia.

getLocked(includeHidden: Boolean = false): List<Achievement>

Pobierz zablokowane osiągnięcia.

getByCategory(category: String): List<Achievement>

Pobierz osiągnięcia w kategorii.

get(id: String): Achievement?

Pobierz pojedyncze osiągnięcie.

isUnlocked(id: String): Boolean

Sprawdź czy osiągnięcie jest odblokowane.

getProgress(id: String): Float

Pobierz postęp (0.0 - 1.0).

getTotalPoints(): Int

Pobierz sumę punktów za odblokowane osiągnięcia.

getStats(): Stats

Pobierz statystyki osiągnięć.

Pobieranie i wyświetlanie
// Lista wszystkich osiągnięć
val allAchievements = ADict.Achievements.getAll()
allAchievements.forEach { achievement ->
    println("${achievement.title}: ${if (achievement.isUnlocked) "✅" else "🔒"}")
    println("  Progress: ${(achievement.progress * 100).toInt()}%")
    println("  Points: ${achievement.points}")
}

// Odblokowane
val unlockedCount = ADict.Achievements.getUnlocked().size

// Statystyki
val stats = ADict.Achievements.getStats()
println("Odblokowane: ${stats.unlocked}/${stats.total}")
println("Punkty: ${stats.totalPoints}")
println("Completion: ${(stats.completionRate * 100).toInt()}%")

// W RecyclerView
class AchievementsAdapter : RecyclerView.Adapter<...>() {
    private var achievements = listOf()

    fun setAchievements(list: List) {
        achievements = list.sortedWith(
            compareByDescending { it.isUnlocked }
                .thenByDescending { it.definition.tier }
        )
        notifyDataSetChanged()
    }
}

// Obserwacja zmian (Flow)
lifecycleScope.launch {
    ADict.Achievements.achievements.collect { achievementsMap ->
        updateUI(achievementsMap)
    }
}

🔔 Callbacki

onUnlock(callback: (Achievement) -> Unit)

Callback wywoływany przy odblokowaniu osiągnięcia.

Callback na odblokowanie
// Globalny callback
ADict.Achievements.onUnlock { achievement ->
    // Pokaż UI
    showAchievementToast(achievement)

    // Analytics
    ADict.Analytics.log("achievement_unlocked", mapOf(
        "achievement_id" to achievement.id,
        "achievement_name" to achievement.title,
        "points" to achievement.points,
        "tier" to achievement.definition.tier
    ))

    // Może nagroda?
    if (achievement.definition.tier >= 3) {
        grantBonusCoins(achievement.points)
    }
}

fun showAchievementToast(achievement: Achievement) {
    Toast.makeText(
        context,
        "🏆 ${achievement.title} (+${achievement.points} pts)",
        Toast.LENGTH_LONG
    ).show()
}

💡 Przykłady praktyczne

Pełna konfiguracja dla gry

System osiągnięć w grze
class AchievementsSetup {
    fun setup() {
        // === ONBOARDING ===
        ADict.Achievements.define("first_level") {
            title = "Początek przygody"
            description = "Ukończ pierwszy poziom"
            points = 10
            type = AchievementType.ONE_TIME
            category = "onboarding"
            tier = 1
        }

        ADict.Achievements.define("tutorial_complete") {
            title = "Gotowy do gry"
            description = "Ukończ tutorial"
            points = 25
            type = AchievementType.ONE_TIME
            category = "onboarding"
            tier = 1
        }

        // === POSTĘP ===
        ADict.Achievements.define("levels_10") {
            title = "Podróżnik"
            description = "Ukończ 10 poziomów"
            points = 100
            type = AchievementType.PROGRESSIVE
            targetCount = 10
            category = "progress"
            tier = 2
        }

        ADict.Achievements.define("levels_50") {
            title = "Weteran"
            description = "Ukończ 50 poziomów"
            points = 500
            type = AchievementType.PROGRESSIVE
            targetCount = 50
            category = "progress"
            tier = 3
            prerequisiteIds = listOf("levels_10")
        }

        ADict.Achievements.define("levels_100") {
            title = "Legenda"
            description = "Ukończ 100 poziomów"
            points = 1000
            type = AchievementType.PROGRESSIVE
            targetCount = 100
            category = "progress"
            tier = 4
            prerequisiteIds = listOf("levels_50")
        }

        // === STREAK ===
        ADict.Achievements.define("daily_3") {
            title = "Regularny gracz"
            description = "Graj 3 dni z rzędu"
            points = 50
            type = AchievementType.STREAK
            targetCount = 3
            category = "engagement"
            tier = 1
        }

        ADict.Achievements.define("daily_7") {
            title = "Oddany gracz"
            description = "Graj 7 dni z rzędu"
            points = 150
            type = AchievementType.STREAK
            targetCount = 7
            category = "engagement"
            tier = 2
        }

        ADict.Achievements.define("daily_30") {
            title = "Uzależniony"
            description = "Graj 30 dni z rzędu"
            points = 500
            type = AchievementType.STREAK
            targetCount = 30
            category = "engagement"
            tier = 4
        }

        // === KOLEKCJONOWANIE ===
        ADict.Achievements.define("collect_all_characters") {
            title = "Kolekcjoner"
            description = "Odblokuj wszystkie postacie"
            points = 300
            type = AchievementType.ONE_TIME
            category = "collection"
            tier = 3
            hidden = true
        }

        // === SOCIAL ===
        ADict.Achievements.define("first_share") {
            title = "Społecznik"
            description = "Udostępnij wynik znajomym"
            points = 30
            type = AchievementType.ONE_TIME
            category = "social"
            tier = 1
        }

        // === SKILL ===
        ADict.Achievements.define("perfect_level") {
            title = "Perfekcja"
            description = "Ukończ poziom z 3 gwiazdkami"
            points = 50
            type = AchievementType.ONE_TIME
            category = "skill"
            tier = 2
        }

        ADict.Achievements.define("no_damage") {
            title = "Nietykalny"
            description = "Ukończ poziom bez obrażeń"
            points = 100
            type = AchievementType.ONE_TIME
            category = "skill"
            tier = 3
            hidden = true
        }

        // Callback
        ADict.Achievements.onUnlock { achievement ->
            GameManager.showAchievementPopup(achievement)
            GameManager.addCoins(achievement.points)
        }
    }
}

// Użycie w grze
class GameManager {
    fun onLevelComplete(level: Level, stars: Int, damage: Int) {
        // Podstawowe
        ADict.Achievements.unlock("first_level")
        ADict.Achievements.increment("levels_10")
        ADict.Achievements.increment("levels_50")
        ADict.Achievements.increment("levels_100")

        // Skill-based
        if (stars == 3) {
            ADict.Achievements.unlock("perfect_level")
        }
        if (damage == 0) {
            ADict.Achievements.unlock("no_damage")
        }
    }

    fun onDailyLogin() {
        ADict.Achievements.recordStreakDay("daily_3")
        ADict.Achievements.recordStreakDay("daily_7")
        ADict.Achievements.recordStreakDay("daily_30")
    }

    fun onShare() {
        ADict.Achievements.unlock("first_share")
    }

    fun onAllCharactersUnlocked() {
        ADict.Achievements.unlock("collect_all_characters")
    }
}

📚 Klasy danych

data class AchievementDefinition

  • idString
  • titleString
  • descriptionString
  • iconInt?
  • pointsInt
  • typeAchievementType
  • targetCountInt
  • hiddenBoolean
  • expiresAtLong?
  • availableFromLong?
  • categoryString?
  • tierInt
  • prerequisiteIdsList<String>

data class AchievementState

  • idString
  • unlockedBoolean
  • unlockedAtLong?
  • currentCountInt
  • streakDaysInt
  • lastStreakDateString?
  • progressFloat

data class Stats

  • totalIntŁączna liczba osiągnięć
  • unlockedIntLiczba odblokowanych
  • totalPointsIntSuma punktów
  • completionRateFloatProcent ukończenia (0-1)