Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions .opencode/skills/open-pr/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
---
name: open-pr
description: Open a Pull Request comparing current feature branch against master, generating a pt-br description
---

# open-pr

Open a Pull Request on GitHub comparing the current branch against `master`, generating a description in **pt-br**.

## Behavior

1. **Detect current branch** — run `git rev-parse --abbrev-ref HEAD` to get the feature branch name.

2. **Fetch latest `master`** — run `git fetch origin master`.

3. **Analyze changes** — compare HEAD against `master`:

```bash
git log master..HEAD --oneline --no-decorate
git diff master...HEAD --stat
```

4. **Categorize commits** by conventional commit type:

| Type | Label |
|---|---|
| `feat` | Nova funcionalidade |
| `fix` | Correção |
| `refactor` | Refatoração |
| `docs` | Documentação |
| `test` | Testes |
| `chore` | Manutenção |
| `build` | Build / CI |
| `BREAKING` | Mudança crítica |

5. **Identify affected modules** — from changed file paths, determine which modules were modified:
- `composeApp`
- `feature-*`
- `core-*`
- `build-logic`
- `androidApp`
- `gradle` / config

6. **Generate PR body in pt-br** with the following structure:

```markdown
## O que foi feito?

[Resumo das alterações baseado nos commits]

## Módulos afetados

- [lista de módulos]

## Tipo de mudança

- [ ] Nova funcionalidade
- [ ] Correção de bug
- [ ] Refatoração
- [ ] Documentação
- [ ] Testes
- [ ] Build / CI

## Como testar?

1. [passos para testar]
2. [passos adicionais]

## Checklist

- [ ] Build passa localmente
- [ ] Lint / Detekt não aponta erros novos
- [ ] Testes existentes continuam passando
- [ ] Testes novos foram adicionados (se aplicável)
```

7. **Determine PR title** — use the first commit subject line or a concise summary derived from the branch name (e.g. `feat: adiciona tela de perfil`).

8. **Open the PR** using `gh`:

```bash
gh pr create \
--base master \
--head <current-branch> \
--title "<title>" \
--body "<body>"
```

9. If `gh` is not authenticated or available, print the full PR content to stdout with instructions.

## Notes

- All PR text MUST be in **pt-br** (Portuguese — Brazil).
- Follow the project's existing commit style (conventional commits).
- If the branch has only one commit, use its message as the PR title.
- If the PR would be empty (no diff), warn the user.
121 changes: 121 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# StreamPlayerApp — KMP

## Project Overview

- **Purpose:** Open-source Netflix-like app built by the CodandoTV community for learning and mentoring.
- **Type:** Kotlin Multiplatform (KMP) Application
- **Primary Language:** Kotlin 2.3.20
- **Runtime/Platform:** Multiplatform — Android, iOS, Desktop (Compose Multiplatform)
- **Min SDK:** 28 (Android) | **Compile SDK:** 36 | **Target SDK:** 36

## Build System

| Item | Value |
|---|---|
| Gradle | 9.3.1 |
| Kotlin | 2.3.20 |
| AGP | 9.1.0 |
| JDK | 21 |
| Version Catalog | `gradle/libs.versions.toml` |
| Convention Plugins | `build-logic/` (precompiled script plugins) |
| Application ID | `com.codandotv.streamplayerapp` |

### Convention Plugins (build-logic)

- `com.streamplayer.kmp-library` — base KMP module setup (multiplatform, serialization, parcelize, dokka, detekt)
- `com.streamplayer.dokka` — documentation generation
- `com.streamplayer.detekt` — static analysis
- `com.streamplayer.koin-annotations-setup` — Koin annotation processing
- `popcorngp-setup-plugin` — Popcorn Guinea Pig test setup

### Key Dependencies

- Compose Multiplatform 1.10.0
- Koin 4.2.0 / Koin Annotations 2.3.1
- Ktor 3.0.1 / OkHttp 4.12.0
- Room 2.7.0-alpha13
- Coil 3.1.0
- Navigation Compose 2.9.2
- Kotlinx Datetime 0.8.0
- Firebase (BOM 33.14.0, Crashlytics, Analytics)
- Detekt 1.23.6
- Dokka 1.9.10

## Project Structure

### Main Application

| Module | Type | Description |
|---|---|---|
| `composeApp` | KMP + Compose | Main UI module, aggregates features |
| `androidApp` | Android App | Android entry point wrapper |

### Feature Modules

| Module | Description |
|---|---|
| `feature-list-streams` | Stream listing |
| `feature-detail` | Detail screen |
| `feature-search` | Search functionality |
| `feature-profile` | User profile |
| `feature-news` | News feed |

### Core Modules

| Module | Description |
|---|---|
| `core-shared` | Shared domain models and utilities |
| `core-networking` | Networking layer (Ktor + OkHttp) |
| `core-shared-ui` | Shared UI components and theming |
| `core-navigation` | Navigation infrastructure |
| `core-local-storage` | Local persistence (Room) |
| `core-camera-gallery` | Camera and gallery features |
| `core-permission` | Permission handling (MOKO Permissions) |
| `core-background-work` | Background task management (WorkManager, KMPNotifier) |
| `core-session` | Session management |

### Build Infrastructure

| Module | Description |
|---|---|
| `build-logic` | Convention plugins and shared build config |
| `Config.kt` | Centralized version and configuration constants |

### Test Infrastructure

- JUnit 4.13.2
- MockK 1.13.7
- Kotlinx Coroutines Test 1.8.1
- Koin Test
- Popcorn Guinea Pig (test fixtures)

### Documentation

- **Generator:** Dokka 1.9.10
- **Source:** API docs generated via `dokka` task
- **Project docs:** README.md, README_pt-br.md

## Version Management

Versions are managed in:

| File | Content |
|---|---|
| `gradle/libs.versions.toml` | Dependency and plugin versions |
| `build-logic/src/main/java/Config.kt` | `versionName = "3.2"`, `versionCode`, SDK versions, JDK target |
| `gradle.properties` | Gradle daemon config, AndroidX settings |

## Distribution

This is an application project and is **not** published as a library. No Maven Central, Gradle Plugin Portal, or pub.dev distribution.

## Git Workflow

| Item | Value |
|---|---|
| Default Branch | `main` |
| CI | GitHub Actions (`build.yml`, `linter.yaml`, `popcorn.yaml`) |
| Versioning | SemVer (current: 3.2) |
| Tags | Standard Git tags |
| PR Language | pt-br |
| Review | Via GitHub PRs, CODEOWNERS in `.github/CODEOWNERS` |
2 changes: 1 addition & 1 deletion build-logic/src/main/java/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ object Config {
const val compileSdkVersion = 36
const val minSdkVersion = 28
const val targetSdkVersion = 36
const val versionName = "1.5"
const val versionName = "3.2"
const val versionCode = 1
const val testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

Expand Down
1 change: 1 addition & 0 deletions composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ kotlin {
implementation(projects.featureProfile)
implementation(projects.featureNews)
implementation(projects.coreShared)
implementation(projects.coreSession)
api(projects.coreSharedUi)
api(projects.coreCameraGallery)
api(projects.coreBackgroundWork)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.codandotv.streamplayerapp.composeApp.presentation.di
import com.codandotv.streamplayerapp.core.background.work.di.SyncModule
import com.codandotv.streamplayerapp.core.local.storage.di.LocalStorageModule
import com.codandotv.streamplayerapp.core.networking.di.NetworkModule
import com.codandotv.streamplayerapp.core.session.di.coreSessionModule
import com.codandotv.streamplayerapp.core.shared.qualifier.QualifierDispatcherIO
import com.codandotv.streamplayerapp.feature.liststreams.list.di.ListStreamModule
import com.codandotv.streamplayerapp.feature.news.di.NewsScreenModule
Expand All @@ -28,6 +29,7 @@ fun streamPlayerApplication(platformBlock: KoinApplication.() -> Unit): KoinAppl
Dispatchers.IO
}
},
coreSessionModule,
NetworkModule().module,
LocalStorageModule.module,
SyncModule.module,
Expand Down
12 changes: 12 additions & 0 deletions core-session/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
plugins {
id("com.streamplayer.kmp-library")
id("com.streamplayer.koin-annotations-setup")
}

kotlin {
sourceSets {
commonMain.dependencies {
implementation(libs.kotlinx.datetime)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.codandotv.streamplayerapp.core.session.di

import com.codandotv.streamplayerapp.core.session.domain.SessionManager
import org.koin.dsl.module

val coreSessionModule = module {
factory { SessionManager() }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.codandotv.streamplayerapp.core.session.di

class SessionScope
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.codandotv.streamplayerapp.core.session.domain

import com.codandotv.streamplayerapp.core.session.di.SessionScope
import org.koin.mp.KoinPlatform
import kotlin.time.Clock

class SessionManager {
private val scopeId: String = "user_session"

fun openSession(imageUrl: String) {
closeSession()
val koin = KoinPlatform.getKoin()

val scope = koin.createScope<SessionScope>(
scopeId = scopeId,
)
scope.declare(
UserSessionInfo(
userTimestamp = Clock.System.now(),
profileImageUrl = imageUrl
)
)
}

fun userSessionInfo(): UserSessionInfo? {
return KoinPlatform.getKoin()
.getScopeOrNull(scopeId)
?.getOrNull<UserSessionInfo>()
}

fun closeSession() {
val koin = KoinPlatform.getKoin()
koin.getScopeOrNull(scopeId)?.close()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.codandotv.streamplayerapp.core.session.domain

import kotlin.time.Instant

data class UserSessionInfo(
val userTimestamp: Instant,
val profileImageUrl: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.codandotv.streamplayerapp.core.shared.ui.widget

import androidx.compose.foundation.layout.height
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
import streamplayerapp_kmp.core_shared_ui.generated.resources.Res
import streamplayerapp_kmp.core_shared_ui.generated.resources.icon_profile
import streamplayerapp_kmp.core_shared_ui.generated.resources.perfil_fake

@Composable
fun AvatarImageIcon(imageUrl: String?, modifier: Modifier = Modifier) {
AsyncImage(
modifier = modifier
.height(24.dp)
.clip(RoundedCornerShape(4.dp)),
model = imageUrl,
error = painterResource(Res.drawable.perfil_fake),
placeholder = painterResource(Res.drawable.perfil_fake),
contentDescription = stringResource(Res.string.icon_profile)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.ExperimentalMaterial3Api
Expand All @@ -22,19 +21,15 @@ import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
import com.codandotv.streamplayerapp.core.shared.ui.resources.Colors
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
import streamplayerapp_kmp.core_shared_ui.generated.resources.Res
import streamplayerapp_kmp.core_shared_ui.generated.resources.ic_netflix
import streamplayerapp_kmp.core_shared_ui.generated.resources.icon_netflix
import streamplayerapp_kmp.core_shared_ui.generated.resources.icon_profile
import streamplayerapp_kmp.core_shared_ui.generated.resources.icon_search
import streamplayerapp_kmp.core_shared_ui.generated.resources.perfil_fake
import streamplayerapp_kmp.core_shared_ui.generated.resources.topbar_categories
import streamplayerapp_kmp.core_shared_ui.generated.resources.topbar_movies
import streamplayerapp_kmp.core_shared_ui.generated.resources.topbar_shows
Expand Down Expand Up @@ -104,14 +99,8 @@ private fun StreamPlayerTopBar(
modifier = Modifier.fillMaxHeight(),
onClick = { onNavigateProfilePicker() }
) {
AsyncImage(
modifier = Modifier
.height(24.dp)
.clip(RoundedCornerShape(4.dp)),
model = profilePicture,
error = painterResource(Res.drawable.perfil_fake),
placeholder = painterResource(Res.drawable.perfil_fake),
contentDescription = stringResource(Res.string.icon_profile)
AvatarImageIcon(
imageUrl = profilePicture,
)
}
}
Expand Down
Loading
Loading