WPKit

Biblioteka kliencka Android do komunikacji z API REST WordPressa.
Posty, strony, kategorie, tagi, autorzy, menu — z paginacją, cache i gotowymi widokami UI.
Integracja z Parse Server: uwierzytelnianie, profil użytkownika, push notyfikacje, dane.

Android client library for the WordPress REST API.
Posts, pages, categories, tags, authors, menus — with pagination, offline cache & ready-made UI views.
Parse Server integration: authentication, user profiles, push notifications, data queries.

v1.1.0  ·  Kotlin  ·  KitsuneNET  ·  KitsuneDB  ·  Paging 3  ·  Jetpack Compose  ·  Parse SDK

Przegląd

Overview

WPKit to biblioteka Android umożliwiająca łatwe pobieranie treści WordPress przez REST API. Obsługuje dwa tryby pracy: standardowe WP REST API (bez tokenu) oraz niestandardowe API pluginu WPKit Workspace (z uwierzytelnianiem tokenem). Gdy token workspace jest skonfigurowany, biblioteka używa wyłącznie pluginu — dane są filtrowane do bieżącego workspace'u.

WPKit is an Android library for easy consumption of WordPress content via REST API. It supports two modes: standard WP REST API (no token) and the custom WPKit Workspace plugin API (token auth). When a workspace token is configured, the library exclusively uses the plugin — data is scoped to the current workspace.

core rip.nerd.wpkit.core

  • WPKit — singleton inicjalizujący klientów HTTP
  • WPKit — singleton initializing HTTP clients
  • WPConfig — konfiguracja biblioteki
  • WPConfig — library configuration
  • WPService — standardowe WP REST API
  • WPService — standard WP REST API
  • WPkitService — API pluginu workspace
  • WPkitService — workspace plugin API
  • WordPressRepository — warstwa repozytorium
  • WordPressRepository — repository layer
  • WPState, WPResult — modele stanu UI
  • WPState, WPResult — UI state models
  • WPPostsViewModel — bazowy ViewModel
  • WPPostsViewModel — base ViewModel
  • WPBookmarkManager, WPShareUtils, WPKitLogger
  • WPBookmarkManager, WPShareUtils, WPKitLogger
  • Modele domenowe i DTO, rozszerzenia
  • Domain & DTO models, extensions
  • Paginacja (Paging 3) + cache offline (KitsuneDB)
  • Pagination (Paging 3) + offline cache (KitsuneDB)

ui rip.nerd.wpkit.ui

  • PostListView — lista postów z paginacją (RecyclerView)
  • PostListView — paginated post list (RecyclerView)
  • WPContentView — widok HTML (View-based, Jsoup)
  • WPContentView — HTML view (View-based, Jsoup)
  • WPContent — Composable renderujący HTML
  • WPContent — Composable rendering HTML
  • CategoryChipsView — chipy kategorii (Material)
  • CategoryChipsView — category chips (Material)
  • MenuBarView — poziomy pasek menu
  • MenuBarView — horizontal menu bar
  • WPPostCard, WPPostGrid, WPRelatedPostsRow…
  • WPPostCard, WPPostGrid, WPRelatedPostsRow…
  • WPSearchBar, WPTagChips, WPCategoryChips…
  • WPSearchBar, WPTagChips, WPCategoryChips…
  • WPPostFilterSheet — dolna szuflada z filtrami
  • WPPostFilterSheet — bottom sheet with filters

core Parse Module

  • ParseKit — singleton zarządzający SDK Parse, sesją, push
  • ParseKit — singleton managing Parse SDK, session, push
  • ParseRepository — warstwa repozytorium Parse
  • ParseRepository — Parse repository layer
  • ParseUserInfo — profil użytkownika
  • ParseUserInfo — user profile model
  • ParseObject — generyczny obiekt Parse
  • ParseObject — generic Parse object
  • Anonimowi użytkownicy + push (ParseInstallation)
  • Anonymous users + push (ParseInstallation)
  • Obsługa banów, odtwarzanie sesji
  • Ban handling, session restore

Instalacja

Installation

Krok 1 — Repozytorium GitHub Packages

Step 1 — GitHub Packages Repository

Dodaj repozytorium do pliku settings.gradle(.kts). Wymagany Personal Access Token z uprawnieniem read:packages.

Add the repository to settings.gradle(.kts). A Personal Access Token with read:packages permission is required.

// settings.gradle.kts
dependencyResolutionManagement {
    repositories {
        maven {
            url = uri("https://maven.pkg.github.com/nerdrip/wpkit")
            credentials {
                username = providers.gradleProperty("gpr.user").orNull
                           ?: System.getenv("GITHUB_ACTOR")
                password = providers.gradleProperty("gpr.token").orNull
                           ?: System.getenv("GITHUB_TOKEN")
            }
        }
    }
}
⚠️ Przechowuj dane logowania w local.properties lub jako zmienne środowiskowe. Nie commituj tokenów do repozytorium! ⚠️ Store credentials in local.properties or as environment variables. Never commit tokens to version control!

Krok 2 — Zależność

Step 2 — Dependency

// build.gradle.kts (moduł aplikacji / app module)
dependencies {
    implementation("rip.nerd.wpkit:wpkit:1.0.1")
}

Krok 3 — Uprawnienia AndroidManifest

Step 3 — AndroidManifest permissions

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

Krok 4 — Plugin WordPress (opcjonalny, zalecany)

Step 4 — WordPress Plugin (optional but recommended)

Skopiuj katalog wordpress-plugin/ do wp-content/plugins/wpkit-workspace/ i aktywuj plugin w panelu WordPress. Bez pluginu biblioteka korzysta ze standardowego WP REST API (bez filtrowania workspace, bez autoryzacji tokenem).

Copy the wordpress-plugin/ directory to wp-content/plugins/wpkit-workspace/ and activate it in WordPress admin. Without the plugin, the library falls back to the standard WP REST API (no workspace filtering, no token auth).

Konfiguracja

Configuration

Inicjalizuj WPKit w klasie Application:

Initialize WPKit in your Application class:

class App : Application() {
    override fun onCreate() {
        super.onCreate()
        WPKit.init(
            app   = this,
            cfg   = WPConfig(
                baseUrl        = "https://your-wp-site.com/",
                workspaceToken = "your_64_char_token",  // optional
                cacheMB        = 20,
                timeoutMs      = 30_000L,
                retryOnError   = 1,
                logger         = if (BuildConfig.DEBUG)
                                     WPKitLogger.Android
                                 else WPKitLogger.Noop
            ),
            debug = BuildConfig.DEBUG,
            registerForNotifications = true   // default: false
        )
    }
}
💡 debug = true włącza pełne logowanie HTTP (ciała żądań i odpowiedzi) przez KNETLoggingInterceptor. Wyłącz w buildach produkcyjnych. 💡 debug = true enables full HTTP logging (request/response bodies) via KNETLoggingInterceptor. Disable in production builds.
💡 registerForNotifications = true aktywuje rejestrację urządzenia w ParseInstallation dla push notyfikacji. Domyślnie wyłączone (false) — Parse SDK jest inicjalizowane, ale urządzenie nie jest rejestrowane do push-ów. 💡 registerForNotifications = true enables device registration in ParseInstallation for push notifications. Disabled by default (false) — the Parse SDK is still initialized but the device is not registered for push.

WPKit (singleton)

WPKit to obiekt Kotlin pełniący rolę głównego punktu wejścia biblioteki. Po wywołaniu init() udostępnia:

WPKit is a Kotlin object serving as the library's main entry point. After init(), it exposes:

Parametry init()

init() parameters

ParametrParameter TypType DomyślnieDefault Opis / Description
app Application Instancja Application Androida. Android Application instance.
cfg WPConfig Konfiguracja biblioteki — patrz WPConfig. Library configuration — see WPConfig.
debug Boolean Włącza pełne logowanie HTTP (ciała żądań/odpowiedzi). Enables full HTTP logging (request/response bodies).
registerForNotifications Boolean false Gdy true, urządzenie jest rejestrowane w ParseInstallation dla push notyfikacji. Tworzona jest też anonimowa sesja jeśli użytkownik nie jest zalogowany. Gdy false (domyślnie), Parse SDK jest inicjalizowane, ale urządzenie nie jest rejestrowane do push-ów. When true, the device is registered in ParseInstallation for push notifications. An anonymous session is also created if the user is not logged in. When false (default), the Parse SDK is initialized but the device is not registered for push.

Pola dostępne po inicjalizacji

Fields available after initialization

PoleField TypType Opis / Description
api WPService Klient standardowego WP REST API (KitsuneNET). Standard WP REST API client (KitsuneNET).
pluginApi WPkitService? Klient API pluginu workspace. Non-null gdy podano workspaceToken. Workspace plugin API client. Non-null when workspaceToken is provided.
cfg WPConfig Konfiguracja przekazana podczas inicjalizacji. Configuration passed during initialization.
log WPKitLogger Skonfigurowany logger. Configured logger instance.

WPConfig

Klasa danych (data class) przechowująca konfigurację biblioteki.

A data class holding the library configuration.

data class WPConfig(
    val baseUrl: String,
    val workspaceToken: String? = null,
    val authTokenProvider: (() -> String?)? = null,
    val cacheMB: Int = 10,
    val timeoutMs: Long = 30_000L,
    val retryOnError: Int = 1,
    val logger: WPKitLogger = WPKitLogger.Android
)
ParametrParameter DomyślnieDefault Opis / Description
baseUrl URL witryny WordPress, np. "https://example.com/" WordPress site URL, e.g. "https://example.com/"
workspaceToken null Token workspace z panelu WP (plugin WPKit). Aktywuje WPkitService. Workspace token from WP admin (WPKit plugin). Activates WPkitService.
authTokenProvider null Lambda zwracająca token WP (np. "Bearer …") dla standardowego REST API. Lambda returning a WP token (e.g. "Bearer …") for the standard REST API.
cacheMB 10 Rozmiar dyskowego cache HTTP w MB. HTTP disk cache size in MB.
timeoutMs 30000 Timeout sieci w milisekundach. Network timeout in milliseconds.
retryOnError 1 Liczba automatycznych ponowień przy błędzie sieci. Number of automatic retries on network error.
logger WPKitLogger.Android Implementacja loggera. Logger implementation.

WPService core

Klient standardowego WordPress REST API (/wp-json/wp/v2/). Wszystkie metody są suspend. Używany jako fallback gdy nie skonfigurowano workspaceToken.

Standard WordPress REST API client (/wp-json/wp/v2/). All methods are suspend. Used as fallback when no workspaceToken is configured.

MetodaMethodEndpointEndpointZwracaReturns
posts(page?, perPage?, categories?, search?, orderBy?, order?, after?, before?) GET /wp/v2/posts List<PostDto>
pages(perPage?, slug?, orderBy?, order?) GET /wp/v2/pages List<PostDto>
categories(perPage?, slug?) GET /wp/v2/categories List<CategoryDto>
tags(perPage?, slug?) GET /wp/v2/tags List<CategoryDto>
menus(locationsCsv?) GET /wpkit/v1/menus List<MenuDto>
menu(location) GET /wpkit/v1/menu MenuDto

WPkitService plugin

Klient API pluginu WPKit Workspace (/wp-json/wpkit/v1/). Aktywny gdy podano workspaceToken. Token jest wstrzykiwany automatycznie do nagłówka X-WPKit-Token.

WPKit Workspace plugin API client (/wp-json/wpkit/v1/). Active when workspaceToken is provided. The token is injected automatically into the X-WPKit-Token header.

MetodaMethodOpis / Description
info() Wersja pluginu (publiczny, bez tokenu). Plugin version (public, no token).
siteInfo() Informacje o stronie: nazwa, tagline, logo, strefa czasowa. Site info: name, tagline, logo, timezone.
workspace() Informacje o workspace i jego możliwościach. Workspace info and capabilities.
workspaceSettings() Niestandardowe ustawienia klucz-wartość. Custom key-value settings.
stats() Statystyki workspace (liczniki + śledzenie żądań). Workspace statistics (counts + request tracking).
posts(page?, perPage?, search?, categories?, tags?, orderBy?, order?, after?, before?, authorId?) Stronicowane posty workspace. Paginated workspace posts.
post(id) Pojedynczy post po ID.Single post by ID.
postBySlug(slug) Pojedynczy post po slug.Single post by slug.
relatedPosts(id, limit) Powiązane posty.Related posts.
pages(page?, perPage?, search?, orderBy?, order?) Stronicowane strony workspace.Paginated workspace pages.
page(id) Strona po ID.Page by ID.
pageBySlug(slug) Strona po slug.Page by slug.
categories() Kategorie workspace.Workspace categories.
tags() Tagi workspace.Workspace tags.
menus() / menu(location) Wszystkie menu / menu po lokalizacji.All menus / menu by location.
search(query, type?, page?, perPage?) Pełnotekstowe wyszukiwanie.Full-text search.
featuredPosts(type, limit) Posty oznaczone jako wyróżnione.Featured posts/pages.
authors() Autorzy postów w workspace.Workspace post authors.
💡 Wszystkie metody zwracają DTO. Użyj toDomain() lub WordPressRepository, by uzyskać modele domenowe. 💡 All methods return DTOs. Use toDomain() or WordPressRepository to get domain models.

WordPressRepository

Warstwa repozytorium łącząca oba klienty API. Gdy pluginApi jest skonfigurowany, używa wyłącznie pluginu — błędy propagują się jako wyjątki. Bez pluginu używa standardowego WP REST API.

Repository layer combining both API clients. When pluginApi is configured, it uses only the plugin — errors propagate as exceptions. Without the plugin, falls back to standard WP REST API.

val repo = WordPressRepository(WPKit.api, WPKit.pluginApi, WPKit.log)

Posty i strony

Posts & pages

// Posty paginowane / Paginated posts
repo.pagedPosts(
    perPage = 10, query = "kotlin",
    cats = "12,34", tags = "5",
    orderBy = "date", order = "desc",
    after = "2024-01-01", before = null,
    authorId = 3L
).collectLatest { adapter.submitData(it) }

// Pojedynczy post / Single post
val post: Post? = repo.getPost(id = 42L)
val post: Post? = repo.postBySlug("hello-world")

// Strony / Pages (lista)
val pages: List<Post> = repo.pages(perPage = 50)
val page: Post?       = repo.pageById(id = 7L)
val page: Post?       = repo.pageBySlug("about-us")

// Paginowane strony (Flow)
repo.pagedPages(perPage = 20).collectLatest { adapter.submitData(it) }

Kategorie, tagi, autorzy

Categories, tags, authors

val categories: List<Category> = repo.categories()
val tags:       List<Tag>       = repo.tags()
val authors:    List<Author>    = repo.authors()  // plugin only

Menu

Menus

val menu: Menu            = repo.menu("primary")
val menus: Map<String, Menu> = repo.menus("primary", "footer")

menu.items.forEach { item ->
    println("${item.title} | ${item.itemType} | ref=${item.referenceId}")
    // item.children — podmenu
}

MenuItem.itemType: "external" | "post" | "page" | "category"
MenuItem.referenceId — ID WordPressa referencjonowanego obiektu (0 = brak).

MenuItem.itemType: "external" | "post" | "page" | "category"
MenuItem.referenceId — WordPress ID of the referenced object (0 = none).

Wyróżnione i powiązane posty

Featured & related posts

val featured: List<Post> = repo.featuredPosts(type = "post", limit = 10)
val related:  List<Post> = repo.relatedPosts(post, limit = 5)

Wyszukiwanie

Search

val results: List<Post> = repo.search("wordpress", type = "post")
repo.pagedSearch("kotlin", perPage = 10).collectLatest { adapter.submitData(it) }

Workspace i informacje o stronie

Workspace & site info

val info:     WorkspaceInfo?     = repo.workspaceInfo()
val settings: WorkspaceSettings? = repo.workspaceSettings()
val stats:    WorkspaceStats?    = repo.workspaceStats()
val site:     SiteInfo?          = repo.siteInfo()

val color = settings?.getString("primary_color")
val ads   = settings?.getBool("show_ads")
val limit = settings?.getInt("post_limit")

val available: Boolean = repo.isPluginAvailable()
💡 Każda metoda posiada wariant *Result() (np. getPostResult()) zwracający WPResult<T> zamiast wyrzucać wyjątki. 💡 Every method has a *Result() variant (e.g. getPostResult()) returning WPResult<T> instead of throwing.

Paginacja (Paging 3)

Pagination (Paging 3)

Biblioteka używa Jetpack Paging 3 do ładowania postów strona po stronie. Metody pagedPosts(), pagedPages() i pagedSearch() zwracają Flow<PagingData<Post>>.

The library uses Jetpack Paging 3 for page-by-page post loading. pagedPosts(), pagedPages() and pagedSearch() return Flow<PagingData<Post>>.

class PostViewModel : ViewModel() {
    private val repo = WordPressRepository(WPKit.api, WPKit.pluginApi, WPKit.log)
    val posts = repo.pagedPosts(perPage = 10).cachedIn(viewModelScope)
}

// W Fragment / In Fragment:
viewModel.posts.collectLatest { pagingData ->
    postListView.submitData(pagingData)
    // lub / or:
    adapter.submitData(lifecycle, pagingData)
}

Paging automatycznie ładuje kolejne strony podczas przewijania listy. Konfiguracja przez PagingConfig(pageSize, prefetchDistance).

Paging automatically loads the next pages as the user scrolls. Configured via PagingConfig(pageSize, prefetchDistance).

Cache offline

Offline Cache

pagedPostsCached() łączy paginację Paging 3 z lokalną bazą danych KitsuneDB. Przy każdym odświeżeniu pobiera z serwera tylko posty nowsze niż ostatnio pobrane (synchronizacja przyrostowa oparta na polu modified). Gdy sieć jest niedostępna, wyświetlane są dane z lokalnej bazy.

pagedPostsCached() combines Paging 3 with a local KitsuneDB database. On each refresh it fetches only posts newer than the latest local entry (incremental sync based on the modified field). When the network is unavailable, cached data is displayed transparently.

// Utwórz cache raz (np. w Application lub ViewModel)
// Create cache once (e.g. in Application or ViewModel)
val cache = PostCacheStore.create(context, workspaceSlug = "my-workspace")

// Używaj jak zwykłego pagedPosts / Use like regular pagedPosts
repo.pagedPostsCached(
    cache   = cache,
    perPage = 50,    // rozmiar batcha synchronizacji / sync batch size
    query   = null,
    cats    = null
).cachedIn(viewModelScope)
 .collectLatest { adapter.submitData(it) }
✅ Kolejne strony po pierwszej są ładowane bezpośrednio z bazy lokalnej — bez dodatkowych żądań sieciowych. ✅ All pages after the first are loaded directly from the local database — without additional network requests.

WPState<T>

Trójstanowy model UI dla ViewModeli: LoadingSuccess | Error. Zaprojektowany do użycia ze StateFlow.

Three-state UI model for ViewModels: LoadingSuccess | Error. Designed for use with StateFlow.

sealed class WPState<out T> {
    data object Loading                      : WPState<Nothing>()
    data class  Success<T>(val data: T)      : WPState<T>()
    data class  Error(val message: String,
                      val cause: Throwable?,
                      val code: Int = 0)     : WPState<Nothing>()
}
// Collect in Fragment
viewModel.categories.collect { state ->
    when (state) {
        is WPState.Loading -> showSpinner()
        is WPState.Success -> showCategories(state.data)
        is WPState.Error   -> showError(state.message)
    }
}

// Functional combinators
state
    .onLoading { showSpinner() }
    .onSuccess { data -> render(data) }
    .onError   { msg, _, _ -> showError(msg) }

// Wrap any suspend block
val state: WPState<List<Post>> = WPState.from { repo.categories() }

// Accessors
val data:  T?      = state.getOrNull()
val data:  T       = state.getOrElse(emptyList())
val error: String? = state.errorOrNull()

WPResult<T>

Bezpieczny typ wyniku operacji sieciowych. Każda metoda repozytorium posiada wariant *Result() zwracający WPResult<T> zamiast wyrzucać wyjątki.

Safe result type for network operations. Every repository method has a *Result() variant returning WPResult<T> instead of throwing.

sealed class WPResult<out T> {
    data class Success<T>(val data: T)                         : WPResult<T>()
    data class Error(val code: Int, val message: String,
                     val cause: Throwable?)                     : WPResult<Nothing>()
}
val result: WPResult<Post> = repo.postBySlugResult("hello-world")

result
    .onSuccess { post -> showPost(post) }
    .onError   { code, message, cause -> showError(message) }

// Transforms
val title: String = result.map { it.title }.getOrElse("Untitled")

// Unsafe unwrap (throws on Error)
val post: Post = result.getOrThrow()

// Combine results
val result2 = result.flatMap { post -> repo.getPostResult(post.id) }

WPPostsViewModel

Abstrakcyjna klasa bazowa ViewModel obsługująca paginowaną listę postów z pełnym wsparciem dla filtrów. Podklasa musi dostarczyć instancję WordPressRepository.

Abstract base ViewModel handling a paginated post list with full filter support. Subclass must provide a WordPressRepository instance.

class MyViewModel : WPPostsViewModel() {
    override val repo = WordPressRepository(WPKit.api, WPKit.pluginApi, WPKit.log)
}
Flow / StateFlowFlow / StateFlowOpis / Description
posts: Flow<PagingData<Post>> Paginowane posty. Automatycznie ponownie ładowane przy zmianie filtrów (debounce 300 ms). Paginated posts. Auto-reloads on filter change (300 ms debounce).
categories: StateFlow<WPState<List<Category>>> Kategorie (ładowane przez loadSideData()). Categories (loaded by loadSideData()).
tags, featured, siteInfo Analogicznie jak categories. Same pattern as categories.

Polecenia filtrowania

Filter commands

viewModel.loadSideData()            // ładuje categories, tags, siteInfo, featured
viewModel.setQuery("kotlin")
viewModel.clearQuery()
viewModel.toggleCategory(12L)
viewModel.setCategories(setOf(12L, 34L))
viewModel.clearCategories()
viewModel.toggleTag(5L)
viewModel.setTags(setOf(5L))
viewModel.setOrderBy("date", ascending = false)
viewModel.setDateRange(after = "2024-01-01", before = null)
viewModel.setDatePreset(WPDateRangePreset.LAST_7_DAYS)
viewModel.clearDateRange()
viewModel.setPerPage(20)
viewModel.clearFilters()

WPDateRangePreset

Enum z predefiniowanymi zakresami dat. Każda wartość oblicza zakres względem aktualnej daty i zwraca obiekty DateRange(after, before) w formacie ISO 8601.

Enum with predefined date ranges. Each value computes the range relative to the current date and returns DateRange(after, before) objects in ISO 8601 format.

val range = WPDateRangePreset.LAST_30_DAYS.toRange()
// range.after = "2024-02-17T00:00:00"
// range.before = null

repo.pagedPosts(after = range.after, before = range.before)

// Dostępne presets / Available presets:
// TODAY, YESTERDAY, LAST_7_DAYS, LAST_30_DAYS,
// THIS_WEEK, LAST_WEEK, THIS_MONTH, LAST_MONTH,
// THIS_YEAR, LAST_YEAR, ALL_TIME

Modele domenowe

Domain Models

Czyste modele Kotlin używane przez warstwę UI. Tworzone z obiektów DTO metodą rozszerzającą toDomain().

Clean Kotlin models used by the UI layer. Created from DTO objects via the toDomain() extension function.

Post

data class Post(
    val id: Long,
    val slug: String,
    val type: String,            // "post" | "page"
    val title: String,
    val excerptHtml: String,
    val contentHtml: String,
    val featuredUrl: String?,
    val date: String,            // ISO 8601
    val modified: String,        // ISO 8601
    val author: Author?,
    val categories: List<Category>,
    val tags: List<Tag>,
    val link: String,
    val featured: Boolean,       // wyróżniony w workspace / featured in workspace
    val readingTimeMinutes: Int, // -1 = nieznany / unknown
    val wordCount: Int           // -1 = nieznany / unknown
)

Category / Tag / Author

data class Category(val id: Long, val name: String, val slug: String,
                    val description: String, val parent: Long, val count: Int)

data class Tag(val id: Long, val name: String, val slug: String,
               val description: String, val count: Int)

data class Author(val id: Long, val name: String, val avatarUrl: String?,
                  val bio: String, val url: String, val postCount: Int)

Menu & MenuItem

data class Menu(val id: Long, val name: String, val slug: String,
                val location: String?, val items: List<MenuItem>)

data class MenuItem(
    val id: Long,
    val title: String,
    val url: String,
    val target: String,
    val itemType: String,    // "external" | "post" | "page" | "category"
    val referenceId: Long,   // WP ID referencji (0 = brak) / reference WP ID (0 = none)
    val children: List<MenuItem>
)

WorkspaceInfo / WorkspaceSettings / WorkspaceStats / SiteInfo

data class WorkspaceInfo(val id: Int, val slug: String, val name: String,
                         val description: String, val version: String,
                         val capabilities: Map<String, Boolean>)

data class WorkspaceSettings(val data: Map<String, Any?>) {
    fun getString(key: String): String?
    fun getInt(key: String): Int?
    fun getBool(key: String): Boolean?
    fun getList(key: String): List<*>?
}

data class WorkspaceStats(val postCount: Int, val pageCount: Int, val menuCount: Int,
                          val tagCount: Int, val featuredCount: Int,
                          val requestCount: Long, val lastRequestAt: String?)

data class SiteInfo(val name: String, val tagline: String, val url: String,
                    val language: String, val timezone: String,
                    val logoUrl: String?, val iconUrl: String?, val adminEmail: String)

Rozszerzenia

Extensions

Rozszerzenia Post

Post extensions

post.readingTimeMinutes()          // server-side lub ~200 wpm; min 1
post.wordCount()                   // server-side lub z HTML
post.toPlainText()                 // usunięcie tagów HTML
post.formattedDate("dd MMM yyyy")  // "01 Jan 2024"
post.formattedModified()
post.isRecent(days = 7)            // true jeśli opublikowany w ostatnich N dniach
post.hasCategory("tech")           // wg slug
post.hasTag("kotlin")              // wg slug
post.shareText()                   // "Tytuł\nhttps://link"
post.primaryCategory()             // pierwsza kategoria lub null

Rozszerzenia List<Post>

List<Post> extensions

posts.groupByMonth()               // Map<"January 2024", List<Post>>
posts.filterByCategory("tech")
posts.filterByTag("kotlin")
posts.featured()                   // tylko posty z featured = true

Rozszerzenia dat (String)

Date extensions (String)

"2024-01-15T10:00:00".parseWPDate()            // → Date?
"2024-01-15T10:00:00".formatWPDate("MMM d")    // → "Jan 15"

Rozszerzenia Menu

Menu extensions

menu.flatItems()                   // płaska lista wszystkich MenuItem (depth-first)
menu.findByUrl("/about")           // dopasowanie dokładne URL
menu.findByTitle("About")          // dopasowanie tytułu (case-insensitive)

Rozszerzenia kategorii i tagów

Category & tag extensions

categories.sortedByName()
categories.sortedByCount()         // sortowanie malejące wg count
categories.findBySlug("tech")

tags.sortedByCount()
tags.withMinCount(5)               // tylko tagi z ≥ 5 postami
tags.findBySlug("kotlin")

Rozszerzenia autorów

Author extensions

authors.topAuthor()                // autor z największą liczbą postów

WPBookmarkManager

Zarządzanie zakładkami postów zapisywanymi w SharedPreferences. Reaktywny dostęp przez Flow.

Manages post bookmarks persisted in SharedPreferences. Reactive access via Flow.

val bookmarks = WPBookmarkManager.create(context, workspaceSlug = "default")

// Dodaj / usuń / przełącz
bookmarks.add(post)
bookmarks.remove(post.id)
val added: Boolean = bookmarks.toggle(post)

// Sprawdź
if (bookmarks.isBookmarked(post.id)) { … }

// Obserwuj reaktywnie / Observe reactively
lifecycleScope.launch {
    bookmarks.bookmarkedIds.collect { ids -> adapter.updateBookmarks(ids) }
    bookmarks.count.collect { n -> badge.text = n.toString() }
}

// Filtruj listę postów do zakładkowanych
val saved: List<Post> = bookmarks.filterBookmarked(posts)

// Snapshot i czyszczenie
val ids: Set<Long> = bookmarks.snapshot()
bookmarks.clearAll()

WPShareUtils

Narzędzia do udostępniania postów i otwierania URL-i w przeglądarce.

Utilities for sharing posts and opening URLs in the browser.

// Natywny share sheet / Native share sheet
WPShareUtils.sharePost(context, post)
WPShareUtils.sharePost(context, post, chooserTitle = "Share via…")

// Ręczna budowa Intentu / Manual Intent
val intent = WPShareUtils.buildShareIntent(post)
startActivity(Intent.createChooser(intent, "Share"))

// Otwórz w przeglądarce / Open in browser
WPShareUtils.openInBrowser(context, post)
WPShareUtils.openUrl(context, "https://example.com")

// Tekst do udostępnienia / Share text
val text: String = WPShareUtils.buildShareText(post)  // "Title\nhttps://link"

WPKitLogger

Interfejs loggera z wbudowanymi implementacjami i konstruktorem własnego loggera.

Logger interface with built-in implementations and a custom logger builder.

// Wbudowane / Built-in
WPKitLogger.Android    // → android.util.Log (domyślny / default)
WPKitLogger.Noop       // → cichy / silent

// Własny (np. Timber) / Custom (e.g. Timber)
val logger = WPKitLogger.custom(
    onDebug = { tag, msg -> Timber.tag(tag).d(msg) },
    onWarn  = { tag, msg, t -> Timber.tag(tag).w(t, msg) },
    onError = { tag, msg, t -> Timber.tag(tag).e(t, msg) }
)

WPKit.init(app, cfg = WPConfig(baseUrl = "…", logger = logger), debug = false)

ParseKit core (singleton)

ParseKit to singleton Kotlin zarządzający cyklem życia Parse Android SDK, persystencją sesji, rejestracją push notyfikacji (ParseInstallation) i cache profilu użytkownika. Jest inicjalizowany automatycznie przez WPKit.init().

ParseKit is a Kotlin singleton managing the Parse Android SDK lifecycle, session persistence, push notification registration (ParseInstallation) and user profile caching. It is initialized automatically by WPKit.init().

Proces inicjalizacji (bootstrap)

Bootstrap flow

Cały proces jest uruchamiany automatycznie przez WPKit.init():

The entire process is triggered automatically by WPKit.init():

  1. Natychmiastowa inicjalizacja Parse SDK z konfiguracji zapisanej w SharedPreferences (zero latencji).
  2. Instant Parse SDK initialization from cached SharedPreferences config (zero latency).
  3. W tle: pobranie świeżej konfiguracji z WordPress bridge (/wpkit/v1/parse/config).
  4. Background: fetch fresh config from WordPress bridge (/wpkit/v1/parse/config).
  5. Odtworzenie sesji użytkownika: ParseUser.become(token) + rejestracja ParseInstallation (push) — tylko gdy registerForNotifications = true.
  6. Session restore: ParseUser.become(token) + ParseInstallation registration (push) — only when registerForNotifications = true.
  7. Odświeżenie profilu z serwera (wykrywanie banów).
  8. Profile refresh from server (ban detection).
  9. Jeśli brak zalogowanego użytkownika i registerForNotifications = true → automatyczne utworzenie anonimowego użytkownika dla push notyfikacji.
  10. If no logged-in user and registerForNotifications = true → automatic anonymous user creation for push notifications.
💡 Gdy registerForNotifications = false (domyślnie), kroki rejestracji ParseInstallation i tworzenia anonimowych użytkowników są pomijane. Parse SDK jest inicjalizowane, a logowanie/rejestracja działają normalnie — jedynie urządzenie nie jest rejestrowane do otrzymywania push notyfikacji. 💡 When registerForNotifications = false (default), the ParseInstallation registration and anonymous user creation steps are skipped. The Parse SDK is still initialized and login/registration works normally — only the device is not registered to receive push notifications.

Anonimowi użytkownicy

Anonymous users

Gdy registerForNotifications = true, SDK Parse jest gotowe i nie istnieje sesja prawdziwego użytkownika, ParseKit automatycznie tworzy anonimowego użytkownika za pomocą ParseAnonymousUtils. Dzięki temu urządzenie jest natychmiast zarejestrowane w ParseInstallation — push notyfikacje działają od razu, jeszcze przed logowaniem.

When registerForNotifications = true, the Parse SDK is ready and no real user session exists, ParseKit automatically creates an anonymous user via ParseAnonymousUtils. This immediately registers the device in ParseInstallation — push notifications work right away, even before login.

Przy późniejszym logowaniu (login) lub rejestracji (registerAndLogin) sesja anonimowa jest zastępowana / aktualizowanaParseInstallation jest ponownie powiązana z prawdziwym użytkownikiem.

On subsequent login (login) or registration (registerAndLogin), the anonymous session is replaced / upgraded — the ParseInstallation is reassociated with the real user.

Persystencja sesji

Session persistence

Po udanym logowaniu token sesji i pełny profil użytkownika (ParseUserInfo) są zapisywane w SharedPreferences. Przy kolejnym uruchomieniu aplikacji:

After a successful login, the session token and full user profile (ParseUserInfo) are persisted in SharedPreferences. On the next app launch:

Obsługa banów

Ban handling

WordPress Parse bridge egzekwuje bany po stronie serwera. Każde wywołanie API, które napotka zbanowanego użytkownika (HTTP 403, kod user_banned), automatycznie czyści lokalną sesję i emituje null na userState.

The WordPress Parse bridge enforces bans server-side. Any API call that encounters a banned user (HTTP 403, code user_banned) automatically clears the local session and emits null on userState.

Typ banaBan type Opis / Description
PermanentnyPermanent banned = true, ban_timestamp = 0 banned = true, ban_timestamp = 0
TymczasowyTemporary banned = true, ban_timestamp > now (Unix epoch) banned = true, ban_timestamp > now (Unix epoch)
WygasłyExpired Serwer automatycznie zdejmuje ban przy kolejnym logowaniu. Server auto-lifts the ban on the next login attempt.

Obserwowalny stan

Observable state

// Obserwacja stanu użytkownika / Observe user state
ParseKit.userState.collect { user: ParseUserInfo? ->
    if (user != null) showProfile(user)
    else showLoginScreen()
}

// Stan anonimowy / Anonymous state
ParseKit.isAnonymous.collect { anon: Boolean ->
    // true = sesja tymczasowa (push działa, ale użytkownik niezarejestrowany)
    // true = temporary session (push works, but user not registered)
}

// Gotowość SDK / SDK readiness
val ready: Boolean = ParseKit.awaitReady()

ParseRepository core

Warstwa repozytorium dla wszystkich operacji Parse. Deleguje do ParseKit. Przeznaczona do użycia w ViewModelach i warstwach biznesowych.

Repository layer for all Parse operations. Delegates to ParseKit. Designed for use in ViewModels and business layers.

val parseRepo = ParseRepository(logger = WPKit.log)

Uwierzytelnianie

Authentication

// Login — zwraca profil / returns profile
val result: WPResult<ParseUserInfo> = parseRepo.login("username", "password")
result
    .onSuccess { user -> showProfile(user) }
    .onError   { code, msg, _ -> showError(msg) }

// Rejestracja bez logowania / Register only (no auto-login)
parseRepo.register("username", "password", "user@example.com")

// Rejestracja + auto-login (aktualizuje anonimowego użytkownika, jeśli istnieje)
// Register + auto-login (upgrades anonymous user if present)
val result = parseRepo.registerAndLogin("username", "password", "user@example.com")

// Wylogowanie — czyści sesję serwera + lokalną, tworzy nowego anonimowego użytkownika
// Logout — clears server + local session, re-creates anonymous user for push
parseRepo.logout()

Profil użytkownika

User profile

// Natychmiast z cache (bez sieci) / Instant from cache (no network)
val user: ParseUserInfo? = parseRepo.cachedUser()

// Świeży z serwera (wykrywa bany) / Fresh from server (detects bans)
val result: WPResult<ParseUserInfo?> = parseRepo.refreshUser()

// Aktualizacja pól (profil odświeżany automatycznie po zapisie)
// Update fields (profile refreshed automatically after save)
parseRepo.updateUser(mapOf("username" to "new_name"))
parseRepo.updateUser(mapOf("email" to "new@example.com"))
parseRepo.updateUser(mapOf("points" to "+10"))   // punkty relatywne / relative points
parseRepo.updateUser(mapOf("points" to "-5"))

// Email resetowania hasła / Password reset email
parseRepo.requestPasswordReset("user@example.com")

Obserwowalny stan autoryzacji

Observable auth state

// Obserwuj w ViewModel / Fragment
// Observe in ViewModel / Fragment
parseRepo.userState.collect { user: ParseUserInfo? ->
    // null = wylogowany / zbanowany / sesja wygasła
    // null = logged out / banned / expired
}

parseRepo.isAnonymousState.collect { isAnon: Boolean ->
    // true = tymczasowa sesja anonimowa
    // true = temporary anonymous session
}

Sprawdzanie statusu

Status checks

val loggedIn:  Boolean = parseRepo.isLoggedIn()
val anonymous: Boolean = parseRepo.isAnonymous()
val token:     String? = parseRepo.getSessionToken()

Zapytania o dane Parse

Parse data queries

val result: WPResult<List<ParseObject>> = parseRepo.queryObjects("GameScore")
result.onSuccess { objects ->
    objects.forEach { obj ->
        val score = obj.getInt("score")
        val name  = obj.getString("playerName")
        val id    = obj.objectId
    }
}
MetodaMethod ZwracaReturns Opis / Description
login(user, pass) WPResult<ParseUserInfo> Login przez WP bridge. Cache token + profil. Login via WP bridge. Caches token + profile.
register(user, pass, email) WPResult<String> Rejestracja (bez logowania). Register only (no login).
registerAndLogin(…) WPResult<ParseUserInfo> Rejestracja + login. Aktualizuje anonimowego użytkownika. Register + login. Upgrades anonymous user.
logout() WPResult<Unit> Wylogowanie + ponowne utworzenie anonimowej sesji. Logout + re-creates anonymous session.
cachedUser() ParseUserInfo? Profil z cache (natychmiast, bez sieci). Cached profile (instant, no network).
refreshUser() WPResult<ParseUserInfo?> Świeży profil z serwera. Wykrywa bany. Fresh profile from server. Detects bans.
updateUser(fields) WPResult<String> Aktualizacja pól: username, email, password, points ("+10", "-5"). Update fields: username, email, password, points ("+10", "-5").
requestPasswordReset(email) WPResult<String> Wysyłka emaila resetowania hasła. Sends password reset email.
queryObjects(className) WPResult<List<ParseObject>> Zapytanie o obiekty danej klasy Parse. Query objects of a given Parse class.
userState StateFlow<ParseUserInfo?> Obserwowalny stan użytkownika (null = wylogowany). Observable user state (null = logged out).
isAnonymousState StateFlow<Boolean> Obserwowalny stan anonimowy. Observable anonymous state.
isLoggedIn() Boolean Czy istnieje lokalna sesja (token + profil). Whether a local session exists (token + profile).
isAnonymous() Boolean Czy bieżąca sesja jest anonimowa. Whether current session is anonymous.
getSessionToken() String? Zapisany token sesji Parse. Stored Parse session token.

Modele Parse core

Parse Models core

ParseUserInfo

Profil uwierzytelnionego użytkownika Parse. Tworzony z DTO metodą toDomain().

Authenticated Parse user profile. Created from DTO via toDomain().

data class ParseUserInfo(
    val objectId: String,
    val username: String,
    val email: String,
    val emailVerified: Boolean = false,
    val points: Int = 0,
    val banned: Boolean = false,
    val banTimestamp: Long = 0L,   // 0 = ban permanentny / permanent ban (when banned=true)
    val createdAt: String = "",    // ISO 8601
    val updatedAt: String = ""     // ISO 8601
)

ParseObject

Generyczny obiekt Parse (klucz-wartość). Zwracany przez queryObjects().

Generic Parse object (key-value). Returned by queryObjects().

data class ParseObject(
    val objectId: String,
    val className: String,
    val data: Map<String, Any?>
) {
    fun getString(key: String): String?
    fun getInt(key: String): Int?
    fun getLong(key: String): Long?
    fun getBool(key: String): Boolean?
    operator fun get(key: String): Any?   // raw access
}

Przykład użycia:

Usage example:

val obj: ParseObject = …
val name:  String? = obj.getString("playerName")
val score: Int?    = obj.getInt("score")
val raw:   Any?    = obj["customField"]

Endpointy Parse bridge plugin

Parse Bridge Endpoints plugin

Endpointy WordPress bridge dla Parse Server. Dostępne pod /wp-json/wpkit/v1/parse/. Wymagają nagłówka X-WPKit-Token (workspace token) lub tokenu sesji Parse.

WordPress bridge endpoints for Parse Server. Available under /wp-json/wpkit/v1/parse/. Require the X-WPKit-Token header (workspace token) or a Parse session token.

Endpoint AuthAuth Opis / Description
GET /wpkit/v1/parse/config token Konfiguracja Parse Server (appId, serverUrl, clientKey). Używana do inicjalizacji SDK. Parse Server config (appId, serverUrl, clientKey). Used for SDK initialization.
POST /wpkit/v1/parse/login token Login — zwraca sessionToken. Body: username, password. Login — returns sessionToken. Body: username, password.
POST /wpkit/v1/parse/register token Rejestracja użytkownika. Body: username, password, email. User registration. Body: username, password, email.
POST /wpkit/v1/parse/logout session Invalidacja sesji na serwerze. Header: X-Parse-Session. Server-side session invalidation. Header: X-Parse-Session.
GET /wpkit/v1/parse/user session Pełny profil użytkownika. Header: X-Parse-Session. Full user profile. Header: X-Parse-Session.
POST /wpkit/v1/parse/user/update session Aktualizacja pól użytkownika. Obsługiwane klucze: username, email, password, points (wartości relatywne: "+10", "-5"). Update user fields. Supported keys: username, email, password, points (relative values: "+10", "-5").
POST /wpkit/v1/parse/password-reset token Wysyłka emaila resetowania hasła. Body: email. Send password reset email. Body: email.
GET /wpkit/v1/parse/objects/{class} session Zapytanie o wszystkie obiekty danej klasy Parse. Query all objects of a given Parse class.
💡 Auth types: token = nagłówek X-WPKit-Token (workspace token); session = nagłówek X-Parse-Session (token sesji użytkownika). Oba są wstrzykiwane automatycznie przez WPkitService. 💡 Auth types: token = X-WPKit-Token header (workspace token); session = X-Parse-Session header (user session token). Both are injected automatically by WPkitService.

Dobre praktyki — Parse core

Best Practices — Parse core

Obserwacja stanu autoryzacji

Observing auth state

Zamiast odpytywać isLoggedIn(), obserwuj userState jako StateFlow — to gwarantuje reaktywne aktualizacje UI przy logowaniu, wylogowaniu i banach.

Instead of polling isLoggedIn(), observe userState as a StateFlow — this guarantees reactive UI updates on login, logout and bans.

class ProfileViewModel : ViewModel() {
    private val parseRepo = ParseRepository()

    val user: StateFlow<ParseUserInfo?> = parseRepo.userState

    fun login(username: String, password: String) {
        viewModelScope.launch {
            parseRepo.login(username, password)
                .onError { _, msg, _ -> _error.value = msg }
        }
    }

    fun logout() {
        viewModelScope.launch { parseRepo.logout() }
    }
}

Bezpieczne korutyny we Fragmentach

Safe coroutines in Fragments

We Fragmentach zawsze używaj viewLifecycleOwner.lifecycleScope zamiast lifecycleScope. Zapobiega to NullPointerException gdy korutyna wznowi się po onDestroyView() (np. użytkownik zmieni zakładkę podczas ładowania danych).

In Fragments always use viewLifecycleOwner.lifecycleScope instead of lifecycleScope. This prevents NullPointerException when a coroutine resumes after onDestroyView() (e.g. user switches tabs while data is loading).

// ✅ Poprawnie / Correct — anuluje się automatycznie z widokiem
// ✅ Cancels automatically with the view lifecycle
viewLifecycleOwner.lifecycleScope.launch {
    val result = parseRepo.refreshUser()
    _binding?.textView?.text = result.getOrNull()?.username
}

// ❌ Źle / Wrong — korutyna przeżywa zniszczenie widoku
// ❌ Coroutine survives view destruction → NPE on _binding!!
lifecycleScope.launch {
    val result = parseRepo.refreshUser()
    binding.textView.text = result.getOrNull()?.username  // 💥
}

Push notyfikacje przy odtwarzaniu sesji

Push notifications on session restore

Od wersji 1.1.0 ParseKit automatycznie wywołuje saveInstallation() po udanym ParseUser.become() podczas odtwarzania sesji. Dzięki temu urządzenie jest zawsze zarejestrowane dla push notyfikacji — także po restarcie aplikacji z istniejącą sesją.

Since v1.1.0, ParseKit automatically calls saveInstallation() after a successful ParseUser.become() during session restore. This ensures the device is always registered for push notifications — even after an app restart with an existing session.

⚠️ Nie wywołuj ręcznie ParseInstallation.saveInBackground()ParseKit zarządza rejestracją instalacji automatycznie przy logowaniu, wylogowaniu i odtwarzaniu sesji. ⚠️ Do not call ParseInstallation.saveInBackground() manually — ParseKit manages installation registration automatically on login, logout and session restore.

PostListView UI

Gotowy widok Android wyświetlający listę postów z paginacją Paging 3. Bazuje na RecyclerView + PagingDataAdapter + SwipeRefreshLayout.

Ready-made Android View displaying a paginated post list using Paging 3. Based on RecyclerView + PagingDataAdapter + SwipeRefreshLayout.

<rip.nerd.wpkit.ui.PostListView
    android:id="@+id/postList"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
MetodaMethodOpis / Description
submitData(flow) Podłącza Flow<PagingData<Post>>. Automatycznie obserwuje lifecycle. Connects a Flow<PagingData<Post>>. Automatically observes lifecycle.
submitList(posts) Wyświetla zwykłą listę postów (bez paginacji). Displays a plain post list (no paging).
setOnPostClick(l) Callback kliknięcia posta. Post click callback.
setOnPostLongClick(l) Callback długiego przytrzymania posta. Post long-press callback.
setRefreshing(bool) Programowe sterowanie wskaźnikiem odświeżania. Programmatic refresh indicator control.

Każdy element zawiera: tytuł, skrót HTML i opcjonalny obrazek wyróżniający (Coil).

Each item shows: title, HTML excerpt and optional featured image (Coil).

WPContentView UI / XML

Widok Android renderujący treść HTML WordPress natywnie — bez WebView. Buduje układ z TextView, ImageView itp. Parsowanie przez Jsoup.

Android View that renders WordPress HTML content natively — no WebView. Builds the layout from TextView, ImageView etc. Parsing via Jsoup.

<rip.nerd.wpkit.ui.WPContentView
    android:id="@+id/wpContent"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:wp_paragraphTextSize="16sp"
    app:wp_h1TextSize="28sp"
    app:wp_linkColor="@color/primary" />

Atrybuty stylizacji

Styling attributes

AttributeTypeDomyślnieDefault
wp_textColorcolortextColorPrimary
wp_bodyTextColorcolortextColorSecondary
wp_captionTextColorcolortextColorTertiary
wp_dividerColorcolor#1A000000
wp_quoteColorcolor#33000000
wp_codeBackgroundColorcolor#0D000000
wp_codeTextColorcolortextColorPrimary
wp_linkColorcolorcolorPrimary
wp_titleTextSizedimension28px
wp_h1TextSize … wp_h6TextSizedimension26–15px
wp_paragraphTextSizedimension15px

Publiczne metody

Public methods

MetodaMethodOpis / Description
renderPost(post: Post) Renderuje cały post: tytuł, obrazek wyróżniający, treść HTML. Renders the full post: title, featured image, HTML content.
renderHtml(html, baseUrl?) Renderuje dowolny fragment HTML. Renders arbitrary HTML fragment.
clear() Czyści widok i przewija do góry. Clears the view and scrolls to top.
setOnLinkClick(listener) Callback kliknięcia linku. Link click callback.

Obsługiwane elementy HTML: h1h6, p, blockquote, hr, img, figure/figcaption, ul/ol, pre/code, table, iframe (YouTube z miniaturą).

Supported HTML elements: h1h6, p, blockquote, hr, img, figure/figcaption, ul/ol, pre/code, table, iframe (YouTube with thumbnail).

WPContent Compose

Funkcja @Composable renderująca HTML WordPress jako natywne elementy Jetpack Compose. Używa Jsoup do parsowania.

A @Composable function rendering WordPress HTML as native Jetpack Compose elements. Uses Jsoup for parsing.

@Composable
fun WPContent(html: String, modifier: Modifier = Modifier)
WPContent(
    html     = post.contentHtml,
    modifier = Modifier.padding(16.dp)
)

Obsługiwane tagi: h1h6, p, img (Coil AsyncImage), ul/ol, blockquote.

Supported tags: h1h6, p, img (Coil AsyncImage), ul/ol, blockquote.

CategoryChipsView UI

Poziomo przewijany widok chipów kategorii (Material ChipGroup). Obsługuje tryb pojedynczego i wielokrotnego wyboru.

Horizontally scrollable category chips (Material ChipGroup). Supports single and multi-selection modes.

<rip.nerd.wpkit.ui.CategoryChipsView
    android:id="@+id/chips"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />
chips.setData(
    items           = categories,
    singleSelection = true,
    preselected     = emptyList(),
    onChanged       = { selectedIds -> viewModel.setCategories(selectedIds.toSet()) }
)
chips.clearSelection()
val ids: List<Long> = chips.selectedIds()

Komponenty Compose Compose

Compose Components Compose

WPPostCard / WPPostCardCompact

WPPostCard(post = post, onClick = { openPost(it) })
// Posty wyróżnione (featured = true) pokazują odznakę ⭐
// Featured posts display a ⭐ badge on the image
WPPostCardCompact(post = post, onClick = { openPost(it) })

WPPostGrid

// Siatka 2-kolumnowa kart postów / 2-column grid of post cards
WPPostGrid(posts = posts, onClick = { openPost(it) }, columns = 2)

WPRelatedPostsRow

// Poziomo przewijany wiersz "Czytaj dalej" / Horizontal "Read next" row
WPRelatedPostsRow(
    posts     = relatedPosts,
    onClick   = { openPost(it) },
    title     = "Read next",
    cardWidth = 160.dp
)

WPSiteHeader / WPReadingTimeChip / WPAuthorRow

WPSiteHeader(siteInfo = siteInfo)        // logo + nazwa + tagline
WPReadingTimeChip(post = post)           // "5 min read"
WPAuthorRow(author = author, showBio = true)  // avatar + imię + bio

WPSearchBar

WPSearchBar(
    onSearch   = { query -> viewModel.setQuery(query) },
    debounceMs = 400L
)

WPTagChips / WPCategoryChips

Poziomo przewijalne chipy tagów/kategorii (LazyRow) — wydajne przy dużych listach.

Horizontally scrollable tag/category chips (LazyRow) — efficient for large lists.

WPTagChips(
    tags     = tags,
    selected = selectedTagIds,
    onToggle = { id -> viewModel.toggleTag(id) }
)
WPCategoryChips(
    categories = categories,
    selected   = selectedCatIds,
    onToggle   = { id -> viewModel.toggleCategory(id) }
)

WPPostFilterSheet

Dolna szuflada (Modal Bottom Sheet) łącząca wszystkie filtry: kategorie, tagi, porządek sortowania, preset zakresu dat.

Modal Bottom Sheet combining all filters: categories, tags, sort order, date range preset.

var showFilter by remember { mutableStateOf(false) }

if (showFilter) {
    WPPostFilterSheet(
        categories     = categories,
        tags           = tags,
        selectedCats   = selectedCatIds,
        selectedTags   = selectedTagIds,
        currentOrderBy = "date",
        currentOrder   = "desc",
        currentPreset  = null,
        onApply   = { cats, tags, orderBy, order, preset ->
            viewModel.setCategories(cats)
            viewModel.setTags(tags)
            viewModel.setOrderBy(orderBy, ascending = order == "asc")
            viewModel.setDatePreset(preset)
            showFilter = false
        },
        onDismiss = { showFilter = false }
    )
}

Plugin WordPress — przegląd plugin

WordPress Plugin — Overview plugin

WPKit Workspace to plugin WordPress dostarczający niestandardowe REST API (/wp-json/wpkit/v1/) oraz panel administracyjny do zarządzania workspace'ami. Workspace to izolowana kolekcja treści (posty, strony, menu, kategorie) przypisana do konkretnej aplikacji mobilnej i chroniona unikalnym tokenem.

WPKit Workspace is a WordPress plugin providing a custom REST API (/wp-json/wpkit/v1/) and an admin panel for managing workspaces. A workspace is an isolated content collection (posts, pages, menus, categories) assigned to a specific mobile app and protected by a unique token.

Instalacja

Installation

wp-content/plugins/
  wpkit-workspace/
    wpkit.php
    admin/
    assets/
    includes/

Po aktywacji w panelu WordPress pojawia się pozycja menu WPKit z zakładkami: General, Posts, Pages, Categories, Tags, Menus, Settings.

After activation in WordPress admin, a WPKit menu item appears with tabs: General, Posts, Pages, Categories, Tags, Menus, Settings.

Autoryzacja

Authorization

Wszystkie endpointy workspace wymagają nagłówka X-WPKit-Token z unikalnym 64-znakowym tokenem widocznym w panelu WPKit. Token jest wstrzykiwany automatycznie przez WPkitService.

All workspace endpoints require the X-WPKit-Token header with the unique 64-character token shown in the WPKit admin panel. The token is injected automatically by WPkitService.

Endpointy API pluginu

Plugin API Endpoints

Endpoint AuthAuth Opis / Description
GET /wpkit/v1/infopublicznypublicWersja pluginu.Plugin version.
GET /wpkit/v1/sitepublicznypublicNazwa, tagline, logo, strefa czasowa.Site name, tagline, logo, timezone.
GET /wpkit/v1/workspacetokenInformacje i możliwości workspace.Workspace info & capabilities.
GET /wpkit/v1/workspace/settingstokenNiestandardowe ustawienia klucz-wartość.Custom key-value settings.
GET /wpkit/v1/workspace/statstokenLiczniki postów/stron/menu + śledzenie żądań.Post/page/menu counts + request tracking.
GET /wpkit/v1/workspace/poststokenStronicowane posty.Paginated posts.
GET /wpkit/v1/workspace/posts/{id}tokenPost po ID.Post by ID.
GET /wpkit/v1/workspace/posts/slug/{slug}tokenPost po slug.Post by slug.
GET /wpkit/v1/workspace/posts/{id}/relatedtokenPowiązane posty.Related posts.
GET /wpkit/v1/workspace/featuredtokenPosty/strony wyróżnione. Param: type (post/page/any), limit (1–50).Featured posts/pages. Params: type (post/page/any), limit (1–50).
GET /wpkit/v1/workspace/pagestokenStronicowane strony.Paginated pages.
GET /wpkit/v1/workspace/pages/{id}tokenStrona po ID.Page by ID.
GET /wpkit/v1/workspace/pages/slug/{slug}tokenStrona po slug.Page by slug.
GET /wpkit/v1/workspace/categoriestokenKategorie workspace.Workspace categories.
GET /wpkit/v1/workspace/tagstokenTagi workspace.Workspace tags.
GET /wpkit/v1/workspace/authorstokenAutorzy postów w workspace.Workspace post authors.
GET /wpkit/v1/workspace/menustokenWszystkie menu.All menus.
GET /wpkit/v1/workspace/menus/{location}tokenMenu po lokalizacji.Menu by location.
GET /wpkit/v1/workspace/search?q=tokenPełnotekstowe wyszukiwanie.Full-text search.

Parametry zapytań (posty/strony)

Query parameters (posts/pages)

page, per_page, search, categories, tags, orderby (date/modified/title/id), order (asc/desc), after (ISO 8601), before (ISO 8601), author

page, per_page, search, categories, tags, orderby (date/modified/title/id), order (asc/desc), after (ISO 8601), before (ISO 8601), author

Pola elementu menu (MenuItem)

Menu item fields

Pole / FieldWartościValues
item_type "external" (URL), "post", "page", "category" "external" (URL), "post", "page", "category"
reference_id WordPress ID referencjonowanego wpisu/strony/kategorii (0 = brak). WordPress ID of referenced post/page/category (0 = none).

Dodatkowe pola w odpowiedzi dla postów

Extra post response fields

Pole / FieldOpis / Description
featured true jeśli oznaczony jako wyróżniony w panelu workspace. true if marked as featured in workspace admin.
reading_time Szacowany czas czytania w minutach (~200 słów/min). Estimated reading time in minutes (~200 wpm).
word_count Liczba słów treści posta. Word count of post content.

Nagłówki odpowiedzi

Response headers

Cache-Control: public, max-age=120  ·  X-WPKit-Version  ·  X-WP-Total  ·  X-WP-TotalPages

Panel administracyjny pluginu

Plugin Admin Panel

Publikacja wersji

Publishing

Konfiguracja wersji

Version configuration

// wpkit/build.gradle
group = 'rip.nerd.wpkit'
def baseVersion = '1.1.0'
def isDevBuild = true   // → dodaje sufiks -SNAPSHOT / adds -SNAPSHOT suffix

Komendy

Commands

# Zbuduj AAR / Build AAR
./gradlew clean :wpkit:assembleDebug

# Opublikuj do GitHub Packages / Publish to GitHub Packages
./gradlew :wpkit:publishAarPublicationToGitHubPackagesRepository

# Opublikuj lokalnie / Publish to local Maven (~/.m2)
./gradlew :wpkit:publishAarPublicationToMavenLocal
💡 W pliku gradle.properties lub local.properties ustaw gpr.user i gpr.token aby uwierzytelniać publikację do GitHub Packages. 💡 Set gpr.user and gpr.token in gradle.properties or local.properties to authenticate publishing to GitHub Packages.