🔍 Przegląd

Biblioteka oferuje trzy adaptery do automatycznego wstrzykiwania reklam w listy i pagery:

AdapterUżycieTyp reklam
AdInjectedAdapter RecyclerView (listy) Banner, Native
AdInjectedPagerAdapter ViewPager2 (strony z ViewHolder) Banner, Native
AdInjectedFragmentStateAdapter ViewPager2 (strony jako Fragment) Banner, Native

Wspólne cechy wszystkich adapterów:

⚙️ Konfiguracja - AdsSpec

Wszystkie adaptery przyjmują obiekt AdsSpec do konfiguracji pozycji i typów reklam:

Definicja AdsSpec
AdsSpec(
    zoneName = "feed",                      // Strefa konfiguracji reklam
    callbackName = "default",               // Callback dla eventów
    firstAdPosition = 5,                    // Pierwsza reklama po 5 elementach contentu
    interval = 8,                           // Kolejne reklamy co 8 elementów
    bannerAdSize = AdViewContainer.BANNER,  // Rozmiar bannera (jeśli używasz)
    binder = myNativeBinder,                // Binder dla reklam natywnych (opcjonalny)
    adKindForSlot = { slotIndex ->          // Typ reklamy per slot
        if (slotIndex % 2 == 0) AdKind.NATIVE else AdKind.BANNER
    }
)

Parametry

ParametrTypDomyślnieOpis
zoneNameString"default"Nazwa strefy konfiguracji reklam
callbackNameString"default"Nazwa callbacka dla eventów
firstAdPositionInt4Po ilu elementach contentu pierwsza reklama
intervalInt6Co ile elementów contentu kolejna reklama
bannerAdSizeIntBANNERRozmiar bannera (z AdViewContainer)
binderNativeAdViewBinder?nullCustomowy binder dla reklam natywnych
adKindForSlot(Int) → AdKind{ NATIVE }Funkcja określająca typ reklamy dla slotu

📜 AdInjectedAdapter - RecyclerView

Adapter dla standardowego RecyclerView z automatycznym wstrzykiwaniem reklam między elementami listy.

Implementacja adaptera
class FeedAdapter(ctx: Context) : AdInjectedAdapter<FeedItem, RecyclerView.ViewHolder>(
    ctx,
    AdsSpec(
        zoneName = "feed",
        callbackName = "default",
        firstAdPosition = 5,              // Pierwsza reklama po 5 itemach
        interval = 8,                     // Kolejne co 8 itemów contentu
        bannerAdSize = AdViewContainer.LARGE_BANNER,
        adKindForSlot = { idx ->
            // Parzyste sloty → native, nieparzyste → banner
            if (idx % 2 == 0) AdKind.NATIVE else AdKind.BANNER
        }
    )
) {
    companion object {
        private const val TYPE_TEXT = 1
        private const val TYPE_PHOTO = 2
    }

    // Mapowanie contentu na viewType (tylko dla własnych typów!)
    override fun getContentItemViewType(contentPosition: Int): Int =
        when (getContentItem(contentPosition)) {
            is FeedItem.Text -> TYPE_TEXT
            is FeedItem.Photo -> TYPE_PHOTO
        }

    override fun onCreateContentViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        return when (viewType) {
            TYPE_TEXT -> TextVH(inflater.inflate(R.layout.item_text, parent, false))
            TYPE_PHOTO -> PhotoVH(inflater.inflate(R.layout.item_photo, parent, false))
            else -> error("Unknown viewType=$viewType")
        }
    }

    override fun onBindContentViewHolder(
        holder: RecyclerView.ViewHolder,
        item: FeedItem,
        contentPosition: Int
    ) {
        when (holder) {
            is TextVH -> holder.bind(item as FeedItem.Text)
            is PhotoVH -> holder.bind(item as FeedItem.Photo)
        }
    }
}
Użycie
val adapter = FeedAdapter(requireContext())
binding.recyclerView.adapter = adapter

// Załaduj dane
adapter.submitList(listOf(
    FeedItem.Text("Tytuł 1", "Treść..."),
    FeedItem.Photo("Zdjęcie 1", R.drawable.photo1),
    FeedItem.Text("Tytuł 2", "Treść..."),
    // ... więcej itemów
))

// Ręczny refresh reklam (np. po zmianie reguł AdGate)
adapter.refreshAds()

Metody API

MetodaOpis
submitList(items: List<T>)Ustawia nową listę contentu i przebudowuje sloty reklam
getContentItem(position: Int): TPobiera element contentu na danej pozycji
refreshAds()Ręczne odświeżenie slotów reklam (po zmianach AdGate)

📖 AdInjectedPagerAdapter - ViewPager2

Adapter dla ViewPager2 używający ViewHolderów. Idealny do galerii, stories, onboardingu.

Implementacja adaptera
class StoryPagerAdapter(ctx: Context) : AdInjectedPagerAdapter<Story, RecyclerView.ViewHolder>(
    ctx,
    AdsSpec(
        zoneName = "stories",
        callbackName = "default",
        firstAdPosition = 3,              // Pierwsza reklama na stronie #3
        interval = 4,                     // Kolejne co 4 strony contentu
        adKindForSlot = { idx ->
            if (idx % 2 == 0) AdKind.NATIVE else AdKind.BANNER
        }
    )
) {
    companion object {
        private const val TYPE_TEXT = 1
        private const val TYPE_VIDEO = 2
    }

    override fun getContentItemViewType(contentPosition: Int): Int =
        when (getContentItem(contentPosition)) {
            is Story.Text -> TYPE_TEXT
            is Story.Video -> TYPE_VIDEO
        }

    override fun onCreateContentViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        return when (viewType) {
            TYPE_TEXT -> TextStoryVH(inflater.inflate(R.layout.page_text_story, parent, false))
            TYPE_VIDEO -> VideoStoryVH(inflater.inflate(R.layout.page_video_story, parent, false))
            else -> error("Unknown viewType=$viewType")
        }
    }

    override fun onBindContentViewHolder(
        holder: RecyclerView.ViewHolder,
        item: Story,
        contentPosition: Int
    ) {
        when (holder) {
            is TextStoryVH -> holder.bind(item as Story.Text)
            is VideoStoryVH -> holder.bind(item as Story.Video)
        }
    }
}
Użycie
val adapter = StoryPagerAdapter(requireContext())
binding.viewPager.adapter = adapter

adapter.submitList(listOf(
    Story.Text("Historia 1", "Treść..."),
    Story.Video("Film 1", videoUri),
    // ...
))

📄 AdInjectedFragmentStateAdapter - Fragmenty

Adapter dla ViewPager2 używający Fragmentów. Zalecany dla złożonych stron wymagających własnego lifecycle.

📌 Kiedy używać FragmentStateAdapter?
Implementacja adaptera
class OnboardingPagerAdapter(
    hostFragment: Fragment
) : AdInjectedFragmentStateAdapter<OnboardingPage>(
    hostFragment,
    AdsSpec(
        zoneName = "onboarding",
        callbackName = "default",
        firstAdPosition = 3,
        interval = 4,
        adKindForSlot = { idx ->
            if (idx % 2 == 0) AdKind.NATIVE else AdKind.BANNER
        }
    )
) {
    // Twórz fragment dla danego elementu contentu
    override fun createContentFragment(item: OnboardingPage, contentPosition: Int): Fragment =
        when (item) {
            is OnboardingPage.Welcome -> WelcomeFragment.newInstance(item.title)
            is OnboardingPage.Feature -> FeatureFragment.newInstance(item.title, item.description)
            is OnboardingPage.Final -> FinalFragment.newInstance()
        }

    // (Opcjonalnie) Stabilne ID dla lepszych animacji i zachowania stanu
    override fun stableId(item: OnboardingPage): Long =
        item.title.hashCode().toLong()
}
Użycie
class OnboardingFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // Przekaż `this` (Fragment) jako host
        val adapter = OnboardingPagerAdapter(this)
        binding.viewPager.adapter = adapter

        adapter.submitList(listOf(
            OnboardingPage.Welcome("Witaj!"),
            OnboardingPage.Feature("Funkcja 1", "Opis..."),
            OnboardingPage.Feature("Funkcja 2", "Opis..."),
            OnboardingPage.Final()
        ))
    }
}

Fragmenty reklam

Biblioteka dostarcza gotowe fragmenty dla reklam:

Są one automatycznie używane przez adapter - nie musisz ich tworzyć ręcznie.

🚧 Integracja z AdGate

Wszystkie adaptery automatycznie reagują na zmiany stanu AdGate:

Blokowanie reklam
// Zablokuj reklamy globalnie (np. użytkownik premium)
AdGate.block("premium_user")

// Odblokuj
AdGate.unblock("premium_user")

// Lokalna reguła dla strefy (np. blokuj tylko native)
AdGate.addLocalRule("feed") { request ->
    // true = pozwól, false = zablokuj
    request.format != AdGate.Format.NATIVE
}

// ⚠️ Po zmianie lokalnych reguł wywołaj refresh!
adapter.refreshAds()
⚠️ Ważne

Zmiany globalne AdGate (block/unblock) są obsługiwane automatycznie przez adaptery. Tylko po addLocalRule() / removeLocalRule() musisz wywołać adapter.refreshAds().

Jak to działa?

  1. Adaptery nasłuchują na AdGate.state (StateFlow)
  2. Gdy blocked zmieni się na true → wszystkie sloty reklam znikają
  3. Gdy blocked zmieni się na false → sloty są ponownie dodawane
  4. Widoczne reklamy są natychmiast zwijane (collapse) przy blokadzie