Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate to Moko Resources #101

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
Draft
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
79 changes: 50 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,38 @@ TvManiac
-------------------------
![Check](https://github.com/c0de-wizard/tv-maniac/actions/workflows/build.yml/badge.svg) ![android](http://img.shields.io/badge/platform-android-6EDB8D.svg?style=flat) ![ios](http://img.shields.io/badge/platform-ios-CDCDCD.svg?style=flat) [![TvManiac Debug](https://img.shields.io/badge/Debug--Apk-download-green?style=for-the-badge&logo=android)](https://github.com/c0de-wizard/tv-maniac/releases/latest/download/app-debug.apk)

**TvManiac** is a personalized entertainment tracking and recommendation Multiplatform app. By utilizing
[Trakt](https://trakt.tv/), we can view shows, create a watchlist get statistics, and much more. This project aims to demonstrate KMP development capabilities. This is currently running on:
**TvManiac** is a personalized entertainment tracking and recommendation Multiplatform app. By
utilizing
[Trakt](https://trakt.tv/), we can view shows, create a watchlist get statistics, and much more.
This project aims to demonstrate KMP development capabilities. This is currently running on:

- Android: Compose
- iOS: SwiftUI


## 🚧 Under Heavy Development 🚧
This is my playground for learning Kotlin Multiplatform. With that said, I'm sure it's filled with bugs crawling everywhere, and I'm probably doing a couple of things wrong. So a lot is changing, but that shouldn't stop you from checking it out.
This is my playground for learning Kotlin Multiplatform. With that said, I'm sure it's filled with bugs crawling everywhere,
and I'm probably doing a couple of things wrong. So a lot is changing, but that shouldn't stop you from checking it out.

| Android | iOS |
| -- | -- |
| Android | iOS |
|------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------|
| <video src="https://user-images.githubusercontent.com/841885/223576880-c7391d14-63b8-47cd-a7f9-97aee5e47892.mp4" width=350/> | <video src="https://github.com/c0de-wizard/tv-maniac/assets/841885/c26f3857-9b4e-409d-9bfb-6be32cd5111b" width=350/> |


## 🖥 Project Setup & Environment

#### Requirements

- [Zulu Java 18](https://www.azul.com/downloads/?package=jdk#zulu)
- You require the latest [Android Studio](https://developer.android.com/studio/preview) release to be able to build the app.
- Install KMP Plugin. Checkout [this setup guide](https://kotlinlang.org/docs/multiplatform-mobile-setup.html).

### API Keys
To use the Trakt API, you'll need to [create a new API app](https://trakt.tv/oauth/applications/new). & for [TMDb](https://www.themoviedb.org/settings/api), create an account and generate an API key if you don't have one.
Once you have your keys, add them to `config.yaml`. If the file is unavailable, navigate to the root dir and create a symlink.

To use the Trakt API, you'll need
to [create a new API app](https://trakt.tv/oauth/applications/new). &
for [TMDb](https://www.themoviedb.org/settings/api), create an account and generate an API key if
you don't have one.
Once you have your keys, add them to `config.yaml`. If the file is unavailable, navigate to the root
dir and create a symlink.

`ln -s core/util/src/commonMain/resources/config.yaml config.yaml`

Expand All @@ -37,6 +45,7 @@ traktRedirectUri: "PUT_CALLBACK_URI_HERE"
```

### Opening iOS Project

- Navigate to the ios directory & open `.xcodeproj`

### Android Screenshots
Expand Down Expand Up @@ -101,47 +110,58 @@ traktRedirectUri: "PUT_CALLBACK_URI_HERE"
</tr>
</table>



## Libraries Used

### Android

* [Accompanist](https://github.com/google/accompanist)
* [Insets](https://google.github.io/accompanist/insets/)
* [Pager Layouts](https://google.github.io/accompanist/pager/)
* [Android-youtube-player](https://github.com/PierfrancescoSoffritti/android-youtube-player) - Youtube Player
* [AppAuth](https://openid.github.io/AppAuth-Android/) - AppAuth for Android is a client SDK for communicating with OAuth 2.0 and OpenID Connect providers.
* [Compose Lints](https://slackhq.github.io/compose-lints/) - Custom lint checks for Jetpack Compose.
* [Insets](https://google.github.io/accompanist/insets/)
* [Pager Layouts](https://google.github.io/accompanist/pager/)
* [Android-youtube-player](https://github.com/PierfrancescoSoffritti/android-youtube-player) -
Youtube Player
* [AppAuth](https://openid.github.io/AppAuth-Android/) - AppAuth for Android is a client SDK for
communicating with OAuth 2.0 and OpenID Connect providers.
* [Compose Lints](https://slackhq.github.io/compose-lints/) - Custom lint checks for Jetpack
Compose.
* [Jetpack Compose](https://developer.android.com/jetpack/compose)
* [Coil](https://coil-kt.github.io/coil/compose/) - Image loading
* [Navigation](https://developer.android.com/jetpack/compose/navigation) - Navigation
* [KenBurnsView](https://github.com/flavioarfaria/KenBurnsView) - Immersive image.
* [Leakcanary](https://github.com/square/leakcanary) - Memory leak detection.
* [ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel) UI-related data holder, lifecycle
* [ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel) UI-related data
holder, lifecycle
aware.
* [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager?gclsrc=ds&gclsrc=ds) Handle persistent work
* [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager?gclsrc=ds&gclsrc=ds)
Handle persistent work

### Kmp - Common
* [Kotlinx Serialization](https://ktor.io/docs/kotlin-serialization.html) - De/Serializing JSON

* [Coroutines Extensions](https://cashapp.github.io/sqldelight/js_sqlite/coroutines/) Consume
queries as Flow
* [Coroutines](https://github.com/Kotlin/kotlinx.coroutines#multiplatform) - Concurrency & Threading
* [DataStore Preferences](https://android-developers.googleblog.com/2022/10/announcing-experimental-preview-of-jetpack-multiplatform-libraries.html) - Data storage
* [DataStore Preferences](https://android-developers.googleblog.com/2022/10/announcing-experimental-preview-of-jetpack-multiplatform-libraries.html) -
Data storage
* [DateTime](https://github.com/Kotlin/kotlinx-datetime) - Date & Time
* [Flow-Redux](https://github.com/freeletics/FlowRedux)
* [Kermit](https://kermit.touchlab.co/) - Logging
* [Kotest Assertions](https://kotest.io/docs/assertions/assertions.html) - Testing
* [kotlin-inject](https://github.com/evant/kotlin-inject) - Injection library.
* [Kotlinx Serialization](https://ktor.io/docs/kotlin-serialization.html) - De/Serializing JSON
* [Ktor](https://ktor.io/) - Networking
* [Kotest Assertions](https://kotest.io/docs/assertions/assertions.html) - Testing
* [Moko Resources](https://github.com/icerockdev/moko-resources) - Shared Resources
* [SQLDelight](https://github.com/cashapp/sqldelight/) - Local storage
- [Coroutines Extensions](https://cashapp.github.io/sqldelight/js_sqlite/coroutines/) Consume queries as Flow

### iOS

* [Kingfisher](https://github.com/onevcat/Kingfisher) - Image library.
* [OAuthSwift](https://github.com/OAuthSwift/OAuthSwift) Swift-based OAuth library for iOS and macOS.
* [OAuthSwift](https://github.com/OAuthSwift/OAuthSwift) Swift-based OAuth library for iOS and
macOS.
* [TvManiac](https://github.com/c0de-wizard/tvmaniac-swift-packages) - TvManiac SwiftPackage.



## Roadmap

Android

- [x] Implement Watchlist
- [x] Add `More` screen. Shows GridView
- [x] Recommended Shows
Expand All @@ -150,12 +170,13 @@ Android
- Dynamic theme change.
- [x] Add Seasons UI
- [x] Implement trakt auth & sign in
- [ ] Migrate to Material3
- [x] Migrate to Material3
- [ ] Add Episode detail screen
- [ ] Add Watchlist
- [ ] Implement Search

iOS

- [x] Add HomeScreen: Tabs & Empty UI
- [x] Implement Discover UI
- [x] Show Detail Screen
Expand All @@ -166,18 +187,18 @@ iOS
- [ ] Implement Watchlist UI

Shared

- [x] Use SQLDelight extensions to consume queries as Flow
- [x] Refactor interactor implementation.
- [x] Use koin for injection
- [x] Modularize `shared` module
- [x] Try out [Flow-Redux](https://github.com/freeletics/FlowRedux)
- [ ] Improve error handling.
- [x] Improve error handling.
- [ ] Fix paging
- [ ] Add test cases.



### References

- [Design Inspiration](https://dribbble.com/shots/7591814-HBO-Max-Companion-App-Animation)
- [Code Snippets](https://github.com/android/compose-samples)
- [Touchlab KaMPKit project](https://github.com/touchlab/KaMPKit)
Expand Down
18 changes: 9 additions & 9 deletions android-core/designsystem/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ android {
}

dependencies {
api(libs.coroutines.jvm)
api(libs.coil.coil)
api(libs.coil.compose)
api(libs.androidx.compose.ui.tooling)
api(libs.androidx.palette)
api(libs.androidx.compose.material.icons)
api(libs.androidx.compose.material3)
api(libs.androidx.compose.ui.tooling)
api(libs.androidx.compose.ui.ui)
api(libs.androidx.palette)
api(libs.coil.coil)
api(libs.coil.compose)
api(libs.coroutines.jvm)

implementation(projects.androidCore.resources)
implementation(projects.common.localization)

implementation(libs.kenburns)
implementation(libs.androidx.core)
implementation(libs.androidx.compose.foundation)
implementation(libs.androidx.compose.material.icons)
implementation(libs.androidx.core)
implementation(libs.kenburns)
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.LibraryAddCheck
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
Expand All @@ -23,11 +25,9 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.thomaskioko.tvmaniac.compose.theme.TvManiacTheme
import com.thomaskioko.tvmaniac.resources.R

@Composable
fun TvManiacTextButton(
Expand Down Expand Up @@ -116,7 +116,7 @@ fun TvManiacOutlinedButton(
},
),

)
)
}

@Composable
Expand Down Expand Up @@ -204,7 +204,7 @@ fun TvManiacOutlinedButtonPreview() {
enabled = true,
leadingIcon = {
Image(
painter = painterResource(id = R.drawable.ic_baseline_check_box_24),
imageVector = Icons.Filled.LibraryAddCheck,
contentDescription = null,
colorFilter = ColorFilter.tint(
MaterialTheme.colorScheme.secondary.copy(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.thomaskioko.tvmaniac.resources.R
import com.thomaskioko.tvmaniac.common.localization.MR

@Composable
fun TvPosterCard(
Expand Down Expand Up @@ -53,7 +53,7 @@ fun TvPosterCard(

AsyncImageComposable(
model = posterImageUrl,
contentDescription = stringResource(R.string.cd_show_poster, title),
contentDescription = stringResource(MR.strings.cd_show_poster.resourceId, title),
modifier = Modifier
.fillMaxSize()
.aspectRatio(2 / 3f),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,25 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Inbox
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.thomaskioko.tvmaniac.common.localization.MR
import com.thomaskioko.tvmaniac.compose.theme.TvManiacTheme
import com.thomaskioko.tvmaniac.resources.R

@Composable
fun EmptyContent(
painter: Painter,
imageVector: ImageVector,
message: String,
modifier: Modifier = Modifier,
) {
Expand All @@ -36,7 +37,7 @@ fun EmptyContent(
horizontalAlignment = Alignment.CenterHorizontally,
) {
Image(
painter = painter,
imageVector = imageVector,
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.secondary.copy(alpha = 0.8F)),
modifier = Modifier.size(120.dp),
contentDescription = null,
Expand All @@ -61,8 +62,8 @@ fun EmptyContentViewPreview() {
TvManiacTheme {
Surface {
EmptyContent(
painter = painterResource(id = R.drawable.ic_watchlist_empty),
message = stringResource(id = R.string.generic_empty_content),
imageVector = Icons.Outlined.Inbox,
message = stringResource(id = MR.strings.generic_empty_content.resourceId),
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.thomaskioko.tvmaniac.common.localization.MR
import com.thomaskioko.tvmaniac.compose.theme.TvManiacTheme
import com.thomaskioko.tvmaniac.compose.theme.green
import com.thomaskioko.tvmaniac.resources.R

@Composable
fun ConnectionStatus(
Expand All @@ -47,9 +47,9 @@ fun ConnectionStatus(
label = "",
)
val message = if (isConnected) {
stringResource(id = R.string.status_connected)
stringResource(id = MR.strings.status_connected.resourceId)
} else {
stringResource(id = R.string.status_no_connection)
stringResource(id = MR.strings.status_no_connection.resourceId)
}
val icon = if (isConnected) Icons.Outlined.SignalWifi4Bar else Icons.Outlined.SignalWifiOff

Expand Down Expand Up @@ -105,7 +105,7 @@ fun ErrorUi(
Spacer(modifier = Modifier.height(8.dp))

Text(
text = errorMessage ?: stringResource(id = R.string.unexpected_error_retry),
text = errorMessage ?: stringResource(MR.strings.unexpected_error_retry.resourceId),
style = MaterialTheme.typography.bodyLarge,
textAlign = TextAlign.Center,
)
Expand All @@ -126,7 +126,7 @@ fun ErrorUi(
fun RowError(
onRetry: () -> Unit,
modifier: Modifier = Modifier,
errorMessage: String = stringResource(id = R.string.unexpected_error_retry),
errorMessage: String = stringResource(id = MR.strings.unexpected_error_retry.resourceId),
) {
Column(
modifier = modifier,
Expand All @@ -151,7 +151,7 @@ fun RowError(
@Composable
fun EmptyUi(
modifier: Modifier = Modifier,
message: String = stringResource(R.string.generic_empty_content),
message: String = stringResource(MR.strings.generic_empty_content.resourceId),
) {
Box(modifier = modifier) {
Column(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,13 @@ import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.thomaskioko.tvmaniac.common.localization.MR
import com.thomaskioko.tvmaniac.compose.extensions.iconButtonBackgroundScrim
import com.thomaskioko.tvmaniac.compose.theme.TvManiacTheme
import com.thomaskioko.tvmaniac.resources.R

@OptIn(ExperimentalMaterial3Api::class)
@Composable
Expand Down Expand Up @@ -71,7 +70,7 @@ fun TvManiacTopBar(
navigationIcon = {
if (showNavigationIcon) {
Image(
painter = painterResource(R.drawable.ic_baseline_arrow_back_24),
imageVector = Icons.Filled.ArrowBack,
contentDescription = null,
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onSurface),
modifier = Modifier
Expand Down Expand Up @@ -148,7 +147,7 @@ fun CollapsableAppBar(
) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = stringResource(R.string.cd_navigate_back),
contentDescription = stringResource(MR.strings.cd_navigate_back.resourceId),
tint = MaterialTheme.colorScheme.onBackground,
)
}
Expand Down
Loading
Loading