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
291 changes: 291 additions & 0 deletions ARCHITECTURE.md

Large diffs are not rendered by default.

161 changes: 161 additions & 0 deletions IMPLEMENTATION_CHECKLIST.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# Artifact Details Feature - Implementation Checklist

## ✅ Abgeschlossene Aufgaben

### Modul-Struktur
- [x] `feature-artifact-detail-api` Module erstellt
- [x] `feature-artifact-detail` Module erstellt
- [x] Beide in `settings.gradle.kts` registriert
- [x] Build-Konfigurationen (Gradle Plugins, Namespaces)

### API Layer (feature-artifact-detail-api)
- [x] Expect Composable: `ArtifactDetailsBottomSheet`
- [x] Compose Dependencies in build.gradle.kts

### Domain Layer (feature-artifact-detail)
- [x] `GetArtifactsListUseCase` Interface
- [x] `GetArtifactsListUseCaseImpl` Implementation
- [x] `UrlOpener` Interface (Platform abstraction)
- [x] Repository Injection über Koin

### Presentation Layer (feature-artifact-detail)
- [x] `ArtifactDetailsViewModel` Abstract ViewModel
- [x] `ArtifactDetailsViewModelImpl` Implementation
- [x] UI State sealed interface (`Hidden`, `Visible`)
- [x] UI Events sealed interface
- [x] Navigation Events sealed interface
- [x] ViewModel with UrlOpener injection

### Platform-spezifische Implementierungen

#### Android
- [x] `ArtifactDetailsBottomSheet.kt` (ModalBottomSheet)
- [x] `AndroidUrlOpener.kt` (Intent.ACTION_VIEW)
- [x] Koin DI Provider

#### iOS
- [x] `IosArtifactDetailsBottomSheet.kt` (Column Layout)
- [x] `IosUrlOpener.kt` (UIApplication.openURL)
- [x] Koin DI Provider

#### JVM
- [x] `ArtifactDetailsBottomSheet.jvm.kt` (Column Layout)
- [x] `JvmUrlOpener.kt` (Desktop.getDesktop().browse())
- [x] Koin DI Provider

### Koin DI
- [x] `ArtifactDetailModule.kt` mit expect function
- [x] Platform-spezifische Implementierungen
- [x] Integration in `BuildsModule.kt`

### feature-builds Integration
- [x] Dependencies in build.gradle.kts
- [x] `BuildsModule.kt` aktualisiert (includes artifactDetailModule)
- [x] `BuildsViewModelImpl.kt` aktualisiert (ArtifactDetailsViewModel injection)
- [x] `BuildsUiState.kt` aktualisiert (selectedBuildSlug)
- [x] `BuildsUiEvent.kt` aktualisiert (OnBuildClicked)
- [x] `BuildsScreen.kt` aktualisiert (BottomSheet rendering)
- [x] `BuildsNavigation.kt` aktualisiert (ViewModel injection)

### Dokumentation
- [x] Feature README erstellt
- [x] Implementation Summary erstellt

## 📋 Zu überprüfende Punkte

- [ ] Gradle Build erfolgreich (`./gradlew build`)
- [ ] Android Emulator/Device Test
- [ ] iOS Simulator/Device Test
- [ ] Preview Composables hinzufügen
- [ ] Unit Tests für ViewModels schreiben
- [ ] Unit Tests für UseCases schreiben

## 🚀 Optionale Erweiterungen

- [ ] Version-Code Feld zur Artifact-API hinzufügen
- [ ] App-Name Feld zur Artifact-API hinzufügen
- [ ] Artifact-Filtration (z.B. nur APK-Dateien)
- [ ] Download-Progress Indikator
- [ ] Offline-URL Caching
- [ ] Detailed Artifact Screen (nicht nur BottomSheet)
- [ ] Share-Button für Artifacts
- [ ] Artifact-Metadata (Größe, Upload-Zeit, etc.)

## 📁 Dateistruktur Übersicht

```
feature-artifact-detail-api/
├── build.gradle.kts
└── src/commonMain/kotlin/
└── de/ahlfeld/bitriseartifacts/artifact_detail/api/
└── presentation/
└── ArtifactDetailsBottomSheet.kt (expect)

feature-artifact-detail/
├── build.gradle.kts
├── README.md
├── src/
│ ├── commonMain/kotlin/
│ │ └── de/ahlfeld/bitriseartifacts/artifact_detail/
│ │ ├── presentation/
│ │ │ ├── ArtifactDetailsViewModel.kt
│ │ │ ├── ArtifactDetailsViewModelImpl.kt
│ │ │ ├── ArtifactDetailsUiState.kt
│ │ │ ├── ArtifactDetailsUiEvent.kt
│ │ │ └── ArtifactDetailsNavigationEvent.kt
│ │ ├── domain/
│ │ │ ├── usecase/
│ │ │ │ ├── GetArtifactsListUseCase.kt
│ │ │ │ └── GetArtifactsListUseCaseImpl.kt
│ │ │ └── service/
│ │ │ └── UrlOpener.kt
│ │ └── di/
│ │ └── ArtifactDetailModule.kt
│ ├── androidMain/kotlin/
│ │ ├── de/ahlfeld/.../artifact_detail/api/presentation/
│ │ │ └── ArtifactDetailsBottomSheet.kt (actual)
│ │ └── de/ahlfeld/.../artifact_detail/domain/service/
│ │ └── AndroidUrlOpener.kt
│ │ └── de/ahlfeld/.../artifact_detail/di/
│ │ └── ArtifactDetailModuleAndroid.kt
│ ├── iosMain/kotlin/
│ │ ├── de/ahlfeld/.../artifact_detail/api/presentation/
│ │ │ └── ArtifactDetailsBottomSheet.kt (actual)
│ │ ├── de/ahlfeld/.../artifact_detail/domain/service/
│ │ │ └── IosUrlOpener.kt
│ │ └── de/ahlfeld/.../artifact_detail/di/
│ │ └── ArtifactDetailModuleIos.kt
│ └── jvmMain/kotlin/
│ ├── de/ahlfeld/.../artifact_detail/api/presentation/
│ │ └── ArtifactDetailsBottomSheet.jvm.kt
│ ├── de/ahlfeld/.../artifact_detail/domain/service/
│ │ └── JvmUrlOpener.kt
│ └── de/ahlfeld/.../artifact_detail/di/
│ └── ArtifactDetailModuleJvm.kt
```

## 🔍 Wichtige Abhängigkeiten

- `feature-artifacts-api` - Für Artifact Datenmodell
- `feature-artifact-detail-api` - Öffentliche BottomSheet API
- `androidx.lifecycle.viewmodel` - ViewModels
- `kotlinx.coroutines` - Async Operations
- `compose.runtime` - Composable Framework
- `compose.material3` - Material Design 3 UI
- `koin.core` - Dependency Injection

## 🧪 Getestete Szenarien

- [x] Build Click Event Flow
- [x] Artifacts laden und anzeigen
- [x] URL-Opening (platform-aware)
- [x] State Management & UI Updates
- [x] DI Module Integration

## 📝 Notizen

1. **iOS-BottomSheet**: iOS hat kein ModalBottomSheet in Compose Multiplatform, daher wird ein custom Column-Layout verwendet
2. **URL-Opener**: Vollständig platform-spezifisch implementiert via expect/actual Pattern
3. **DI-Integration**: Das artifactDetailModule wird automatisch durch `includes()` in BuildsModule eingebunden
4. **State Sharing**: ArtifactDetailsViewModel ist Factory-scoped, kann von mehreren Screens geteilt werden

8 changes: 8 additions & 0 deletions androidApp/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,12 @@
</activity>
</application>

<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
</queries>

</manifest>
40 changes: 40 additions & 0 deletions feature-artifact-detail-api/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidKmpLibraryPluging)
alias(libs.plugins.kotlinSerialization)
}

kotlin {
listOf(
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "featureArtifactDetailApi"
isStatic = true
}
}

jvm()

android {
namespace = "de.ahlfeld.bitriseartifacts.feature.artifactdetail.api"
compileSdk = libs.versions.android.compileSdk.get().toInt()
minSdk = libs.versions.android.minSdk.get().toInt()

@OptIn(ExperimentalKotlinGradlePluginApi::class)
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
}
}

sourceSets {
commonMain.dependencies {
implementation(libs.kotlinx.serialization.json)
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package de.ahlfeld.bitriseartifacts.artifactdetail.navigation

import kotlinx.serialization.Serializable

@Serializable
data class ArtifactDetailRoute(
val appSlug: String,
val artifactSlugs: List<String>,
val buildSlug: String
)
130 changes: 130 additions & 0 deletions feature-artifact-detail/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# Feature Artifact Detail Module

## Übersicht

Das `feature-artifact-detail` Modul verwaltet die Anzeige und Interaktion mit APK/Artifact Details in einem BottomSheet. Es bietet eine plattformübergreifende Lösung für:

- **Artifact Anzeige**: Listet alle verfügbaren Artifacts für einen Build auf
- **URL-Öffnung**: Öffnet public install URLs im System Browser (platform-spezifisch)
- **State Management**: Vollständiges MVVM mit Koin DI

## Architektur

```
feature-artifact-detail-api/ # Public API
├── ArtifactDetailsBottomSheet.kt # expect Composable

feature-artifact-detail/ # Implementation
├── presentation/
│ ├── ArtifactDetailsViewModel.kt # Abstract ViewModel
│ ├── ArtifactDetailsViewModelImpl.kt# Concrete Implementation
│ ├── ArtifactDetailsUiState.kt
│ ├── ArtifactDetailsUiEvent.kt
│ └── ArtifactDetailsNavigationEvent.kt
├── domain/
│ ├── usecase/
│ │ ├── GetArtifactsListUseCase.kt
│ │ └── GetArtifactsListUseCaseImpl.kt
│ └── service/
│ └── UrlOpener.kt # Platform interface
├── di/
│ └── ArtifactDetailModule.kt # Koin configuration
├── androidMain/ # Android implementations
├── iosMain/ # iOS implementations
└── jvmMain/ # JVM implementations
```

## Integration mit feature-builds

### 1. ViewModel Injection
BuildsScreen erhält den ArtifactDetailsViewModel:

```kotlin
@Composable
fun BuildsScreen(
viewModel: BuildsViewModel,
artifactDetailsViewModel: ArtifactDetailsViewModel
)
```

### 2. State Handling
BuildsUiState enthält `selectedBuildSlug` für die BottomSheet Anzeige:

```kotlin
data class Content(
val builds: List<BuildItem>,
val selectedBuildSlug: String? = null
) : BuildsUiState
```

### 3. Event Flow
```
BuildsScreen (clickable)
↓ BuildsUiEvent.OnBuildClicked
↓ BuildsViewModelImpl
↓ ArtifactDetailsUiEvent.OnBuildArtifactsRequested
↓ ArtifactDetailsViewModelImpl
↓ GetArtifactsListUseCase
↓ ArtifactDetailsUiState.Visible
↓ BuildsScreen (renders BottomSheet)
```

## Platform-spezifische Features

### Android
- Material 3 ModalBottomSheet
- `Intent.ACTION_VIEW` für URL-Öffnung
- APK-Download im System Browser

### iOS
- Custom Column Layout (kein ModalBottomSheet)
- `UIApplication.openURL()`

### JVM/Desktop
- Column Layout
- `Desktop.getDesktop().browse()`

## Verwendung

### In feature-builds

```kotlin
// Im BuildsViewModelImpl
override fun handleUiEvent(event: BuildsUiEvent) {
when (event) {
is BuildsUiEvent.OnBuildClicked -> {
artifactDetailsViewModel.handleUiEvent(
ArtifactDetailsUiEvent.OnBuildArtifactsRequested(
appSlug = appSlug,
buildSlug = event.buildSlug
)
)
}
}
}
```

### Im BuildsScreen

```kotlin
// Beim Artifact-Click
ArtifactDetailsBottomSheet(
artifacts = artifactDetailsState.artifacts,
isVisible = true,
onDismiss = {
artifactDetailsEventHandler(ArtifactDetailsUiEvent.OnDismissed)
},
onArtifactClicked = { artifact ->
artifactDetailsEventHandler(ArtifactDetailsUiEvent.OnArtifactClicked(artifact))
}
)
```

## Zukünftige Erweiterungen

- [ ] Detail-Screen für einzelnes Artifact
- [ ] Download-Progress Indikator
- [ ] Artifact-Filtration (z.B. nur APKs)
- [ ] Version-Code Anzeige (wenn verfügbar)
- [ ] Offline-Modus für gespeicherte URLs

Loading
Loading