├── .github
├── CODEOWNERS
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ └── android.yml
├── .gitignore
├── CONTRIBUTOR_PROJECT.md
├── CONTRIBUTOR_WIKI.md
├── Gemfile
├── LICENSE
├── README.md
├── README_pt-br.md
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── codandotv
│ │ │ └── streamplayerapp
│ │ │ ├── CustomApplication.kt
│ │ │ ├── MainActivity.kt
│ │ │ ├── di
│ │ │ └── AppModule.kt
│ │ │ ├── navigation
│ │ │ └── NavigationGraph.kt
│ │ │ └── splah
│ │ │ └── presentation
│ │ │ ├── navigation
│ │ │ └── SplashNavigation.kt
│ │ │ └── screens
│ │ │ └── SplashScreen.kt
│ └── res
│ │ └── xml
│ │ ├── backup_rules.xml
│ │ └── data_extraction_rules.xml
│ └── test
│ └── java
│ └── com
│ └── codandotv
│ └── streamplayerapp
│ └── ExampleUnitTest.kt
├── build-logic
├── build.gradle.kts
├── settings.gradle.kts
└── src
│ └── main
│ └── java
│ ├── Config.kt
│ ├── Keys.kt
│ ├── com.streamplayer.android-library.gradle.kts
│ ├── com.streamplayer.application.gradle.kts
│ ├── com.streamplayer.compose.gradle.kts
│ ├── com.streamplayer.detekt.gradle.kts
│ ├── com.streamplayer.dokka.gradle.kts
│ ├── com.streamplayer.kover.gradle.kts
│ └── extensions
│ ├── CommonExtensions.kt
│ ├── DependecyHandlerExtensions.kt
│ └── VersionCatalog.kt
├── build.gradle.kts
├── config
└── detekt
│ └── detekt.yml
├── core-local-storage
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ └── java
│ └── com
│ └── codandotv
│ └── streamplayerapp
│ └── core_local_storage
│ ├── data
│ ├── dao
│ │ └── FavoriteDao.kt
│ └── database
│ │ └── StreamPlayerAppDatabase.kt
│ ├── di
│ └── LocalStorageModule.kt
│ └── domain
│ └── model
│ └── MovieEntity.kt
├── core-navigation
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── java
│ └── com
│ │ └── codandotv
│ │ └── streamplayerapp
│ │ └── core_navigation
│ │ ├── bottomnavigation
│ │ ├── BottomNavItem.kt
│ │ └── StreamPlayerBottomNavigation.kt
│ │ ├── extensions
│ │ └── NavControllerExtension.kt
│ │ ├── helper
│ │ └── NavigationHelper.kt
│ │ └── routes
│ │ ├── BottomNavRoutes.kt
│ │ └── Routes.kt
│ └── res
│ ├── drawable
│ ├── ic_downloads_selected.xml
│ ├── ic_downloads_unselected.xml
│ ├── ic_games_selected.xml
│ ├── ic_games_unselected.xml
│ ├── ic_home_selected.xml
│ ├── ic_home_unselected.xml
│ ├── ic_news_selected.xml
│ └── ic_news_unselected.xml
│ └── values
│ └── strings.xml
├── core-networking
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── codandotv
│ │ └── streamplayerapp
│ │ └── core_networking
│ │ ├── Url.kt
│ │ ├── coroutines
│ │ ├── NetworkResponseAdapter.kt
│ │ ├── NetworkResponseAdapterFactory.kt
│ │ └── NetworkResponseCall.kt
│ │ ├── di
│ │ ├── NetworkModule.kt
│ │ └── QualifierNetworking.kt
│ │ └── handleError
│ │ ├── Failure.kt
│ │ ├── NetworkResponse.kt
│ │ └── ResultExtensions.kt
│ └── res
│ └── values
│ └── strings.xml
├── core-shared-ui
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── codandotv
│ │ └── streamplayerapp
│ │ └── core_shared_ui
│ │ ├── resources
│ │ └── Colors.kt
│ │ ├── theme
│ │ ├── StreamPlayerTheme.kt
│ │ └── ThemePreviews.kt
│ │ ├── utils
│ │ └── Sharing.kt
│ │ └── widget
│ │ ├── IconWithText.kt
│ │ ├── PlayerComponent.kt
│ │ ├── SharingStreamCustomView.kt
│ │ └── StreamPlayerTopBar.kt
│ └── res
│ ├── drawable
│ ├── ic_add.xml
│ ├── ic_close.xml
│ ├── ic_copy_content.xml
│ ├── ic_info.xml
│ ├── ic_instagram.xml
│ ├── ic_message.xml
│ ├── ic_netflix.png
│ ├── ic_netflix_background.xml
│ ├── ic_netflix_foreground.xml
│ ├── ic_play.xml
│ ├── ic_whatsapp.xml
│ ├── perfil_fake.png
│ └── transparent_image.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_netflix.xml
│ └── ic_netflix_round.xml
│ ├── mipmap-hdpi
│ ├── ic_netflix.png
│ └── ic_netflix_round.png
│ ├── mipmap-mdpi
│ ├── ic_netflix.png
│ └── ic_netflix_round.png
│ ├── mipmap-xhdpi
│ ├── ic_netflix.png
│ └── ic_netflix_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_netflix.png
│ └── ic_netflix_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_netflix.png
│ └── ic_netflix_round.png
│ ├── raw
│ └── logo.json
│ ├── values-night
│ └── themes.xml
│ ├── values-v31
│ └── themes.xml
│ └── values
│ ├── colors.xml
│ ├── content-description.xml
│ ├── strings.xml
│ └── themes.xml
├── core-shared
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── codandotv
│ └── streamplayerapp
│ └── core_shared
│ ├── extension
│ ├── ErrorExt.kt
│ ├── String.Ext.kt
│ └── UriExt.kt
│ └── qualifier
│ └── QualifierDispatcherIO.kt
├── fastlane
└── Fastfile
├── feature-favorites
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
└── proguard-rules.pro
├── feature-list-streams
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ ├── main
│ ├── java
│ │ └── com
│ │ │ └── codandotv
│ │ │ └── streamplayerapp
│ │ │ └── feature_list_streams
│ │ │ ├── core
│ │ │ └── ContentType.kt
│ │ │ ├── detail
│ │ │ ├── data
│ │ │ │ ├── DetailStreamRepository.kt
│ │ │ │ ├── DetailStreamService.kt
│ │ │ │ └── model
│ │ │ │ │ ├── DetailStreamResponse.kt
│ │ │ │ │ └── VideoStreamResponse.kt
│ │ │ ├── di
│ │ │ │ └── DetailStreamModule.kt
│ │ │ ├── domain
│ │ │ │ ├── DetailStream.kt
│ │ │ │ ├── DetailStreamMapper.kt
│ │ │ │ ├── DetailStreamUseCase.kt
│ │ │ │ ├── VideoStream.kt
│ │ │ │ └── VideoStreamsUseCase.kt
│ │ │ └── presentation
│ │ │ │ ├── navigation
│ │ │ │ └── DetailStreamNavigation.kt
│ │ │ │ ├── screens
│ │ │ │ ├── DetailStreamViewModel.kt
│ │ │ │ ├── DetailStreamsScreen.kt
│ │ │ │ └── DetailStreamsUIState.kt
│ │ │ │ └── widget
│ │ │ │ ├── DetailStreamActionOption.kt
│ │ │ │ ├── DetailStreamButtonAction.kt
│ │ │ │ ├── DetailStreamImagePreview.kt
│ │ │ │ ├── DetailStreamRowHeader.kt
│ │ │ │ └── DetailStreamToolbar.kt
│ │ │ ├── list
│ │ │ ├── data
│ │ │ │ ├── ListStreamRepository.kt
│ │ │ │ ├── ListStreamService.kt
│ │ │ │ ├── StreamDataSource.kt
│ │ │ │ └── model
│ │ │ │ │ ├── GenresResponse.kt
│ │ │ │ │ └── ListStreamResponse.kt
│ │ │ ├── di
│ │ │ │ └── ListStreamModule.kt
│ │ │ ├── domain
│ │ │ │ ├── GetGenresUseCase.kt
│ │ │ │ ├── GetLatestMovieUseCase.kt
│ │ │ │ ├── ListMovieUseCase.kt
│ │ │ │ ├── ListStreamAnalytics.kt
│ │ │ │ ├── ListStreamMapper.kt
│ │ │ │ └── model
│ │ │ │ │ ├── Genre.kt
│ │ │ │ │ ├── HighlightBanner.kt
│ │ │ │ │ └── ListStream.kt
│ │ │ └── presentation
│ │ │ │ ├── navigation
│ │ │ │ └── ListStreamsNavigation.kt
│ │ │ │ ├── screens
│ │ │ │ ├── ListStreamViewModel.kt
│ │ │ │ ├── ListStreamsScreen.kt
│ │ │ │ └── ListStreamsUIState.kt
│ │ │ │ └── widgets
│ │ │ │ ├── HighlightBanner.kt
│ │ │ │ ├── StreamsCard.kt
│ │ │ │ └── StreamsCarousel.kt
│ │ │ └── search
│ │ │ ├── data
│ │ │ ├── api
│ │ │ │ ├── MostPopularMoviesService.kt
│ │ │ │ └── SearchStreamService.kt
│ │ │ ├── datasource
│ │ │ │ ├── MostPopularMoviesDataSource.kt
│ │ │ │ └── SearchStreamDataSource.kt
│ │ │ ├── model
│ │ │ │ └── ListSearchStreamResponse.kt
│ │ │ └── repository
│ │ │ │ ├── MostPopularMoviesRepository.kt
│ │ │ │ └── SearchStreamRepository.kt
│ │ │ ├── di
│ │ │ └── SearchModule.kt
│ │ │ ├── domain
│ │ │ ├── MostPopularMoviesUseCase.kt
│ │ │ ├── SearchUseCase.kt
│ │ │ └── mapper
│ │ │ │ └── SearchMapper.kt
│ │ │ └── presentation
│ │ │ ├── navigation
│ │ │ └── SearchStreamNavigation.kt
│ │ │ ├── screens
│ │ │ ├── SearchScreen.kt
│ │ │ ├── SearchUIState.kt
│ │ │ └── SearchViewModel.kt
│ │ │ └── widgets
│ │ │ ├── SearchCarousel.kt
│ │ │ ├── SearchStreamCard.kt
│ │ │ └── SearchStreams.kt
│ └── res
│ │ ├── drawable
│ │ ├── ic_top_10.webp
│ │ ├── image_placeholder.xml
│ │ ├── netflix_detail.webp
│ │ ├── netflix_horizontal_logo.xml
│ │ └── play_circle.xml
│ │ ├── layout
│ │ └── activity_list_stream.xml
│ │ └── values
│ │ ├── content-description.xml
│ │ └── strings.xml
│ └── test
│ └── java
│ └── com
│ └── codandotv
│ └── streamplayerapp
│ └── feature_list_streams
│ └── detail
│ ├── DetailStreamRepositoryTest.kt
│ ├── DetailStreamUseCaseTest.kt
│ ├── DetailStreamViewModelTest.kt
│ ├── InstantTaskCoroutinesExecutorRule.kt
│ └── Shared.kt
├── feature-profile
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── java
│ └── com
│ │ └── codandotv
│ │ └── streamplayerapp
│ │ └── feature_profile
│ │ └── profile
│ │ ├── data
│ │ ├── ProfilePickerStreamRepository.kt
│ │ ├── ProfilePickerStreamService.kt
│ │ └── model
│ │ │ └── ProfileStreamResponse.kt
│ │ ├── di
│ │ └── ProfilePickerStreamModule.kt
│ │ ├── domain
│ │ ├── ProfilePickerStreamMapper.kt
│ │ ├── ProfilePickerStreamUseCase.kt
│ │ └── ProfileStream.kt
│ │ └── presentation
│ │ ├── navigation
│ │ └── ProfilePickerStreamNavigation.kt
│ │ ├── screens
│ │ ├── ProfilePickerStreamScreen.kt
│ │ ├── ProfilePickerStreamViewModel.kt
│ │ └── ProfilePickerStreamsUIState.kt
│ │ └── widget
│ │ ├── ComposeExtensions.kt
│ │ ├── ProfilePickerOpacityLayer.kt
│ │ ├── ProfilePickerProfilesGrid.kt
│ │ ├── ProfilePickerSelectedProfileContainer.kt
│ │ ├── ProfilePickerStreamLoad.kt
│ │ └── ProfilePickerStreamToolbar.kt
│ └── res
│ ├── drawable
│ ├── image_placeholder.xml
│ └── netflix_horizontal_logo.xml
│ └── values
│ └── strings.xml
├── file_readme
├── codandotv.png
└── splash_list_detail.gif
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle.kts
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @CodandoTV/CodMentor
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Descrição
2 |
3 | [Descrição das alterações feitas no código ou nas funcionalidades.]
4 |
5 | ## Testes Realizados
6 |
7 | [Descreva os testes que você realizou para verificar a validade das alterações.]
8 |
9 | ## Screenshots
10 |
11 | [Adicione screenshots relevantes, se aplicável.]
12 |
13 | ## Checklist
14 |
15 | - [ ] Os testes foram executados e passaram com sucesso.
16 | - [ ] As alterações de código seguem as diretrizes de estilo do projeto.
17 | - [ ] Foram adicionados testes, se aplicável.
18 | - [ ] Se inscreveu no canal?😛
19 |
20 | ## Issues Relacionadas
21 |
22 | [Adicione as issues relacionadas a esse PR, se houver.]
23 | - [link da issue 1]
--------------------------------------------------------------------------------
/.github/workflows/android.yml:
--------------------------------------------------------------------------------
1 | name: Android CI
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | pull_request:
7 | branches: [ "master" ]
8 |
9 | jobs:
10 | ci:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v3
14 | - name: set up JDK 17
15 | uses: actions/setup-java@v3
16 | with:
17 | java-version: '17'
18 | distribution: 'temurin'
19 | cache: gradle
20 | - uses: ruby/setup-ruby@v1
21 | with:
22 | ruby-version: '3.2'
23 | bundler-cache: true
24 | - name: install fastlane
25 | run: bundle install
26 | - name: Grant execute permission for gradlew
27 | run: chmod +x gradlew
28 | - name: Run Lane from fastlane
29 | run: |
30 | ls
31 | pwd
32 | bundle exec fastlane ci
33 |
34 | - name: Adding markdown
35 | if: failure()
36 | run: |
37 | echo '### Report' >> $GITHUB_STEP_SUMMARY
38 | echo ' Build falhando ' >> $GITHUB_STEP_SUMMARY
39 | echo ' > Execute `./gradlew build` local ' >> $GITHUB_STEP_SUMMARY
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | */build
12 | .idea
13 | /captures
14 | .externalNativeBuild
15 | .cxx
16 | local.properties
17 |
--------------------------------------------------------------------------------
/CONTRIBUTOR_PROJECT.md:
--------------------------------------------------------------------------------
1 | **1. Abra a parte de ISSUES do GitHub e escolha uma issue que você se identifica ou gostaria de explorá-la.**
2 | - 1.1. Caso você queira aprender algo ou quer explorar o processo de mentoria ou está na dúvida sobre o que pegar.
3 | - Fale com um dos membros apoiadores no grupo do Discord, no canal #projeto-netflix (Rods, Gabriel Moro ou Carlos Vacarri), para ajudar a instruí-l(a/o).
4 | - Essa parte da dinâmica da mentoria será moldada junto com você, então tente/tenha paciência e vamos aprender juntos!
5 | - 1.2. Caso você queira ajudar a galera no processo de mentoria.
6 | - Fale com um dos membros apoiadores no grupo do Discord, no canal #projeto-netflix, e participe ajudando o pessoal lá.
7 | - Entre em contato conosco no Discord com Rods, Gabriel Moro ou Carlos Vacarri.
8 | - 1.3. Caso você esteja apenas procurando um pretexto para codar, fazer alguma melhoria ou porque não pegar algo divertido para fazer!
9 | - Siga o resto dos passos e seja bem-vindo!
10 | - 1.4 Caso tenha sentido falta de algo que não está mapeando, crie uma issue, e fale com nossos membros apoiadores;
11 | - 1.5 Quer gravar video no CodandoTV do que você fez?
12 | - Fale com Rods! [Discord](https://discord.gg/fZMDmjKmju) / [LinkedIn](https://www.linkedin.com/in/rviannaoliveira/)
13 |
14 | **2. Associe seu nome, se não der, é porque você precisa participar do grupo `Codevs`, entre em contato com nossos membros apoiadores do discord para adicionarem você nesse grupo;**
15 | - 2.2. Se você quer fazer algo que ainda não tem uma issue fale com Rods para instrui-lo
16 |
17 | **3. Faça um fork deste repositório na sua máquina;**
18 |
19 | **4. Crie uma branch a partir da `master` para a sua feature;**
20 | ```git
21 | git checkout -b feature/issue
22 | ```
23 |
24 | **5. Desenvolva e teste sua feature;**
25 |
26 | **6. Faça commit das suas alterações:**
27 | ```git
28 | git commit -m 'Adicionando minha feature'
29 | ```
30 |
31 | **7. Faça push para o repositório remoto;**
32 | ```git
33 | git push origin minha-feature
34 | ```
35 |
36 | **8. Abra um Pull Request explicando suas alterações e aguarde a revisão e aprovação.**
37 |
--------------------------------------------------------------------------------
/CONTRIBUTOR_WIKI.md:
--------------------------------------------------------------------------------
1 | **1. Faça um fork do repositório**
2 |
3 | **2. Clone o repositório**
4 | ```git
5 | git clone https://github.com/CodandoTV/StreamPlayerApp.wiki.git
6 | ```
7 | **3. Acessa a pasta wiki**
8 | ```
9 | cd StreamPlayerApp.wiki
10 | ```
11 | **4. Faça as alterações desejadas nos arquivos da wiki;**
12 |
13 | **5. Commit e push das alterações para o seu repositório forked:**
14 | ```git
15 | git checkout -b "nome do seu branch"
16 | git add .
17 | git commit -m "Descrição das alterações"
18 | git push
19 | ```
20 | **6. Crie um novo Pull Request, escolhendo o repositório original como base e a branch com as suas alterações.**
21 |
22 | **7. Preencha os detalhes do PR, fornecendo um título e uma descrição clara.**
23 |
24 | **8. Crie o PR e aguarde a revisão e possível aceitação das suas alterações.**
25 |
26 | **9. Certifique-se de substituir "SEU_USUARIO" pelo seu nome de usuário do GitHub e "NOME_DO_REPOSITORIO" pelo nome correto do repositório da wiki em questão.**
27 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gem "fastlane"
4 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 |
3 | plugins {
4 | id("com.streamplayer.application")
5 | }
6 |
7 | dependencies {
8 | implementation(projects.featureFavorites)
9 | implementation(projects.featureListStreams)
10 | implementation(projects.featureProfile)
11 | implementation(projects.coreShared)
12 | implementation(projects.coreSharedUi)
13 | implementation(projects.coreNavigation)
14 | implementation(projects.coreNetworking)
15 | implementation(projects.coreLocalStorage)
16 |
17 | implementation(platform(libs.compose.bom))
18 | androidTestImplementation(platform(libs.compose.bom))
19 |
20 | implementation(libs.bundles.koin)
21 | implementation(libs.bundles.androidSupport)
22 | implementation(libs.bundles.compose)
23 | implementation(libs.bundles.kotlin)
24 |
25 | implementation(libs.lottie)
26 | implementation(libs.lottie)
27 | testImplementation(libs.bundles.test)
28 |
29 | // Kover - Combined report
30 | rootProject.subprojects.forEach { kover(it) }
31 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
14 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/codandotv/streamplayerapp/CustomApplication.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp
2 |
3 | import android.app.Application
4 | import com.codandotv.streamplayerapp.di.AppModule
5 | import org.koin.android.ext.koin.androidContext
6 | import org.koin.core.context.startKoin
7 |
8 | class CustomApplication : Application() {
9 |
10 | override fun onCreate() {
11 | super.onCreate()
12 | startKoin{
13 | androidContext(this@CustomApplication.applicationContext)
14 | modules(AppModule.list)
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/codandotv/streamplayerapp/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.runtime.Composable
7 | import androidx.navigation.compose.rememberNavController
8 | import com.codandotv.streamplayerapp.core_shared_ui.theme.StreamPlayerTheme
9 | import com.codandotv.streamplayerapp.navigation.NavigationGraph
10 |
11 | class MainActivity : ComponentActivity() {
12 | override fun onCreate(savedInstanceState: Bundle?) {
13 | super.onCreate(savedInstanceState)
14 | setContent {
15 | StreamPlayerApp()
16 | }
17 | }
18 | }
19 |
20 | @Composable
21 | fun StreamPlayerApp() {
22 | StreamPlayerTheme {
23 | val navController = rememberNavController()
24 | NavigationGraph(navController = navController)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/codandotv/streamplayerapp/di/AppModule.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.di
2 |
3 | import android.content.res.Resources
4 | import com.codandotv.streamplayerapp.core_local_storage.di.LocalStorageModule
5 | import com.codandotv.streamplayerapp.core_networking.di.NetworkModule
6 | import com.codandotv.streamplayerapp.core_shared.qualifier.QualifierDispatcherIO
7 | import kotlinx.coroutines.Dispatchers
8 | import org.koin.android.ext.koin.androidContext
9 | import org.koin.dsl.module
10 |
11 | object AppModule {
12 | private val module = module {
13 | single { androidContext().resources }
14 | single(QualifierDispatcherIO) { Dispatchers.IO }
15 | }
16 | val list = module + NetworkModule.module + LocalStorageModule.module
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/codandotv/streamplayerapp/navigation/NavigationGraph.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.navigation
2 |
3 | import android.annotation.SuppressLint
4 | import androidx.compose.material3.ExperimentalMaterial3Api
5 | import androidx.compose.material3.Scaffold
6 | import androidx.compose.material3.Text
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.graphics.Color
9 | import androidx.navigation.NavController
10 | import androidx.navigation.NavGraphBuilder
11 | import androidx.navigation.NavHostController
12 | import androidx.navigation.compose.NavHost
13 | import androidx.navigation.compose.composable
14 | import com.codandotv.streamplayerapp.core_navigation.bottomnavigation.StreamPlayerBottomNavigation
15 | import com.codandotv.streamplayerapp.core_navigation.routes.BottomNavRoutes
16 | import com.codandotv.streamplayerapp.core_navigation.routes.Routes
17 | import com.codandotv.streamplayerapp.feature_list_streams.detail.presentation.navigation.detailStreamNavGraph
18 | import com.codandotv.streamplayerapp.feature_list_streams.list.presentation.navigation.listStreamsNavGraph
19 | import com.codandotv.streamplayerapp.feature_list_streams.search.presentation.navigation.searchStreamsNavGraph
20 | import com.codandotv.streamplayerapp.feature_profile.profile.presentation.navigation.profilePickerStreamNavGraph
21 | import com.codandotv.streamplayerapp.splah.presentation.navigation.splashNavGraph
22 |
23 | @Composable
24 | fun NavigationGraph(navController: NavHostController) {
25 | NavHost(navController = navController, startDestination = Routes.Splash) {
26 | splashNavGraph(navController = navController)
27 | listStreamsNavGraph(navController = navController)
28 | searchStreamsNavGraph(navController = navController)
29 | detailStreamNavGraph(navController = navController)
30 | temporaryFun(BottomNavRoutes.GAMES, navController)
31 | temporaryFun(BottomNavRoutes.NEWS, navController)
32 | temporaryFun(BottomNavRoutes.SCENES, navController)
33 | temporaryFun(BottomNavRoutes.DOWNLOADS, navController)
34 | profilePickerStreamNavGraph(navController = navController)
35 | }
36 | }
37 |
38 | fun NavGraphBuilder.temporaryFun(route: String, navController: NavController) {
39 | composable(route = route) {
40 | example(navController = navController, route)
41 | }
42 | }
43 |
44 | @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
45 | @OptIn(ExperimentalMaterial3Api::class)
46 | @Composable
47 | fun example(navController: NavController, route: String) {
48 | Scaffold(
49 | bottomBar = {
50 | StreamPlayerBottomNavigation(navController = navController)
51 | }
52 | ) { _ ->
53 | Text(text = route, color = Color.White)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/com/codandotv/streamplayerapp/splah/presentation/navigation/SplashNavigation.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.splah.presentation.navigation
2 |
3 | import androidx.navigation.NavGraphBuilder
4 | import androidx.navigation.NavHostController
5 | import androidx.navigation.compose.composable
6 | import com.codandotv.streamplayerapp.core_navigation.routes.BottomNavRoutes
7 | import com.codandotv.streamplayerapp.core_navigation.routes.Routes
8 | import com.codandotv.streamplayerapp.splah.presentation.screens.SplashScreen
9 |
10 | fun NavGraphBuilder.splashNavGraph(navController: NavHostController) {
11 | composable(Routes.Splash) {
12 | SplashScreen(
13 | onAnimationFinished = { navController.navigate(BottomNavRoutes.HOME) }
14 | )
15 | }
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/codandotv/streamplayerapp/splah/presentation/screens/SplashScreen.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.splah.presentation.screens
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.runtime.getValue
9 | import androidx.compose.ui.Alignment
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.graphics.Color
12 | import androidx.compose.ui.tooling.preview.Preview
13 | import com.airbnb.lottie.compose.LottieAnimation
14 | import com.airbnb.lottie.compose.LottieCompositionSpec
15 | import com.airbnb.lottie.compose.animateLottieCompositionAsState
16 | import com.airbnb.lottie.compose.rememberLottieComposition
17 | import com.codandotv.streamplayerapp.core.shared.ui.R as SharedUiR
18 |
19 | @Composable
20 | fun SplashScreen(
21 | onAnimationFinished: () -> Unit
22 | ) {
23 | Column(
24 | modifier = Modifier.fillMaxSize()
25 | ) {
26 | Box(
27 | contentAlignment = Alignment.Center,
28 | modifier = Modifier
29 | .fillMaxSize()
30 | .background(Color.Black)
31 | ) {
32 | val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(SharedUiR.raw.logo))
33 | val logoAnimationState = animateLottieCompositionAsState(composition = composition)
34 | LottieAnimation(composition = composition, progress = { logoAnimationState.progress })
35 | if (logoAnimationState.isAtEnd && logoAnimationState.isPlaying) {
36 | onAnimationFinished()
37 | }
38 | }
39 | }
40 | }
41 |
42 | @Composable
43 | @Preview
44 | fun SplashScreenPreview() {
45 | SplashScreen(onAnimationFinished = {})
46 | }
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/test/java/com/codandotv/streamplayerapp/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.assertEquals
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/build-logic/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.gradle.kotlin.dsl.`kotlin-dsl`
2 |
3 | plugins {
4 | `kotlin-dsl`
5 | `kotlin-dsl-precompiled-script-plugins`
6 | }
7 |
8 | repositories {
9 | mavenCentral()
10 | google()
11 | }
12 |
13 | dependencies {
14 | implementation(libs.android.gradle.plugin)
15 | implementation(libs.kotlin.gradle.plugin)
16 | implementation(libs.kover.gradle.plugin)
17 | implementation(libs.detekt.gradle.plugin)
18 | }
--------------------------------------------------------------------------------
/build-logic/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | dependencyResolutionManagement {
2 | repositories {
3 | mavenCentral()
4 | }
5 | versionCatalogs {
6 | create("libs") {
7 | from(files("../gradle/libs.versions.toml"))
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/build-logic/src/main/java/Config.kt:
--------------------------------------------------------------------------------
1 | object Config {
2 | const val applicationId = "com.codandotv.streamplayerapp"
3 | const val compileSdkVersion = 34
4 | const val minSdkVersion = 24
5 | const val targetSdkVersion = 34
6 | const val versionName = "1.0"
7 | const val versionCode = 1
8 | const val testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
9 |
10 | object BuildField {
11 | const val host_debug = "\"https://api.themoviedb.org/3/\""
12 | const val host_release = "\"https://api.themoviedb.org/3/\""
13 | const val api_profile_debug = "\"https://demo3364084.mockable.io/\""
14 | const val api_profile_release = "\"https://demo3364084.mockable.io/\""
15 |
16 | private const val tmdb_token_name_debug = "TMDB_BEARER_TOKEN_DEBUG"
17 | private const val tmdb_token_name_release = "TMDB_BEARER_TOKEN_RELEASE"
18 |
19 | private const val bearear_without_environment = "eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJiNDg2NWM4YTAzNzhmM2I4NjI0OWU1ZjNiYWFiMjU2NyIsInN1YiI6IjY0Mjk4YTg5YTNlNGJhMWM0NDgzM2U4OCIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.9cIxv29vkaZ2yW88DIFRUFK_nXbK2b6KS8t96kA8WAE"
20 |
21 | val api_bearer_debug = "\"Bearer ${System.getenv(tmdb_token_name_debug) ?: bearear_without_environment}\""
22 | val api_bearer_release = "\"Bearer ${System.getenv(tmdb_token_name_release) ?: bearear_without_environment}\""
23 | }
24 | }
--------------------------------------------------------------------------------
/build-logic/src/main/java/Keys.kt:
--------------------------------------------------------------------------------
1 | object Keys {
2 | private const val default_tmdb_token = "eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJiNDg2NWM4YTAzNzhmM2I4NjI0OWU1ZjNiYWFiMjU2NyIsInN1YiI6IjY0Mjk4YTg5YTNlNGJhMWM0NDgzM2U4OCIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.9cIxv29vkaZ2yW88DIFRUFK_nXbK2b6KS8t96kA8WAE"
3 | private const val tmdb_token_name_debug = "TMDB_BEARER_TOKEN_DEBUG"
4 | private const val tmdb_token_name_release = "TMDB_BEARER_TOKEN_RELEASE"
5 |
6 | object BuildField {
7 | val api_bearer_debug =
8 | "\"Bearer ${System.getenv(tmdb_token_name_debug) ?: default_tmdb_token}\""
9 | val api_bearer_release =
10 | "\"Bearer ${System.getenv(tmdb_token_name_release) ?: default_tmdb_token}\""
11 | }
12 | }
--------------------------------------------------------------------------------
/build-logic/src/main/java/com.streamplayer.android-library.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 |
3 | import extensions.dokkaPlugin
4 | import extensions.getLibrary
5 | import extensions.setupAndroidDefaultConfig
6 | import extensions.setupCompileOptions
7 | import extensions.setupNameSpace
8 | import extensions.setupPackingOptions
9 |
10 | val libs: VersionCatalog = extensions.getByType().named("libs")
11 |
12 | plugins {
13 | id("com.android.library")
14 | id("kotlin-android")
15 | id("kotlin-kapt")
16 | id("kotlin-parcelize")
17 | id("com.streamplayer.dokka")
18 | id("org.jetbrains.kotlinx.kover")
19 | id("com.streamplayer.detekt")
20 | }
21 |
22 | android {
23 | setupNameSpace(project)
24 |
25 | setupCompileOptions()
26 |
27 | setupPackingOptions()
28 |
29 | setupAndroidDefaultConfig()
30 | defaultConfig.targetSdk = Config.targetSdkVersion
31 |
32 | buildTypes {
33 | getByName("release") {
34 | isMinifyEnabled = true
35 | proguardFiles("proguard-android.txt", "proguard-rules.pro")
36 | consumerProguardFiles("proguard-rules.pro")
37 | }
38 |
39 | getByName("debug") {
40 | isMinifyEnabled = false
41 | }
42 | }
43 | }
44 |
45 | dependencies {
46 | dokkaPlugin(libs.getLibrary("dokka"))
47 | }
--------------------------------------------------------------------------------
/build-logic/src/main/java/com.streamplayer.application.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 |
3 | import extensions.dokkaPlugin
4 | import extensions.getLibrary
5 | import extensions.setupAndroidDefaultConfig
6 | import extensions.setupCompileOptions
7 | import extensions.setupCompose
8 | import extensions.setupPackingOptions
9 |
10 | val libs: VersionCatalog = extensions.getByType().named("libs")
11 |
12 | plugins {
13 | id("com.android.application")
14 | id("kotlin-android")
15 | id("kotlin-kapt")
16 | id("kotlin-parcelize")
17 | id("com.streamplayer.dokka")
18 | id("com.streamplayer.kover")
19 | id("com.streamplayer.detekt")
20 | }
21 | val catalog: VersionCatalog = extensions.getByType().named("libs")
22 |
23 |
24 | android {
25 | namespace = Config.applicationId
26 |
27 | setupCompileOptions()
28 | setupPackingOptions()
29 | setupAndroidDefaultConfig()
30 | setupCompose(catalog)
31 |
32 | defaultConfig {
33 | applicationId = Config.applicationId
34 | minSdk = Config.minSdkVersion
35 | targetSdk = Config.targetSdkVersion
36 | versionCode = Config.versionCode
37 | versionName = Config.versionName
38 | multiDexEnabled = true
39 | }
40 | }
41 |
42 | dependencies {
43 | dokkaPlugin(libs.getLibrary("dokka"))
44 | }
45 |
46 | tasks.register("coverageReport") {
47 | dependsOn(":app:koverHtmlReportDebug")
48 | }
49 |
--------------------------------------------------------------------------------
/build-logic/src/main/java/com.streamplayer.compose.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 | import extensions.getBundle
3 | import extensions.getLibrary
4 | import extensions.setupCompose
5 |
6 | plugins {
7 | id("com.streamplayer.android-library")
8 | }
9 |
10 | val libs: VersionCatalog = extensions.getByType().named("libs")
11 |
12 | android {
13 | setupCompose(libs)
14 | }
15 |
16 | dependencies {
17 | implementation(platform(libs.getLibrary("compose.bom")))
18 | androidTestImplementation(platform(libs.getLibrary("compose.bom")))
19 |
20 | implementation(libs.getBundle("compose"))
21 | debugImplementation(libs.getLibrary("compose.ui.tooling"))
22 | }
--------------------------------------------------------------------------------
/build-logic/src/main/java/com.streamplayer.detekt.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("io.gitlab.arturbosch.detekt")
3 | }
4 |
5 | detekt {
6 | config.setFrom(file(project.rootDir.path.plus("/config/detekt/detekt.yml")))
7 | buildUponDefaultConfig = true
8 |
9 | source.from(
10 | "src/main/java",
11 | "src/test/java",
12 | "src/main/kotlin",
13 | "src/test/kotlin"
14 | )
15 |
16 | }
--------------------------------------------------------------------------------
/build-logic/src/main/java/com.streamplayer.dokka.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 |
3 | allprojects {
4 | apply(plugin = "org.jetbrains.dokka")
5 | }
--------------------------------------------------------------------------------
/build-logic/src/main/java/com.streamplayer.kover.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("org.jetbrains.kotlinx.kover")
3 | }
4 |
5 | koverReport {
6 | filters {
7 | excludes {
8 | packages(
9 | "*.di",
10 | )
11 |
12 | classes(
13 | "*.BuildConfig",
14 | "*.ComposableSingletons",
15 | "*ScreenKt*",
16 | )
17 | annotatedBy("Generated")
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/build-logic/src/main/java/extensions/CommonExtensions.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 |
3 | package extensions
4 |
5 | import Config
6 | import com.android.build.api.dsl.CommonExtension
7 | import org.gradle.api.JavaVersion
8 | import org.gradle.api.Project
9 | import org.gradle.api.artifacts.VersionCatalog
10 | import org.gradle.api.plugins.ExtensionAware
11 | import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
12 |
13 | internal fun CommonExtension<*, *, *, *, *>.setupPackingOptions() {
14 | packaging {
15 | resources {
16 | with(pickFirsts) {
17 | add("META-INF/library_release.kotlin_module")
18 | add("META-INF/LICENSE.md")
19 | add("META-INF/LICENSE-notice.md")
20 | }
21 | with(excludes) {
22 | add("META-INF/AL2.0")
23 | add("META-INF/LGPL2.1")
24 | }
25 | }
26 | }
27 | }
28 |
29 | internal fun CommonExtension<*, *, *, *, *>.setupAndroidDefaultConfig() {
30 | defaultConfig {
31 | compileSdk = Config.compileSdkVersion
32 | minSdk = Config.minSdkVersion
33 | vectorDrawables.useSupportLibrary = true
34 |
35 | testInstrumentationRunner = Config.testInstrumentationRunner
36 | }
37 | }
38 |
39 | internal fun CommonExtension<*, *, *, *, *>.setupCompileOptions() {
40 | compileOptions {
41 | sourceCompatibility = JavaVersion.VERSION_17
42 | targetCompatibility = JavaVersion.VERSION_17
43 | }
44 |
45 | kotlinOptions {
46 | jvmTarget = "17"
47 | }
48 | }
49 |
50 | fun CommonExtension<*, *, *, *, *>.setupCompose(catalog: VersionCatalog) {
51 | buildFeatures {
52 | compose = true
53 | buildConfig = true
54 | }
55 |
56 | composeOptions {
57 | kotlinCompilerExtensionVersion = "${catalog.getVersion("compose")}"
58 | }
59 |
60 | packaging {
61 | resources {
62 | excludes.apply {
63 | add("META-INF/AL2.0")
64 | add("META-INF/LGPL2.1")
65 | }
66 | }
67 | }
68 | }
69 |
70 |
71 | internal fun CommonExtension<*, *, *, *, *>.setupNameSpace(project: Project) {
72 | val moduleName = project.displayName
73 | .removePrefix("project ")
74 | .replace(":", ".")
75 | .replace("'", "")
76 | .replace("-", ".")
77 |
78 | namespace = "${Config.applicationId}$moduleName"
79 | }
80 |
81 | private fun CommonExtension<*, *, *, *, *>.kotlinOptions(block: KotlinJvmOptions.() -> Unit) {
82 | (this as ExtensionAware).extensions.configure("kotlinOptions", block)
83 | }
84 |
--------------------------------------------------------------------------------
/build-logic/src/main/java/extensions/DependecyHandlerExtensions.kt:
--------------------------------------------------------------------------------
1 | package extensions
2 |
3 | import org.gradle.api.artifacts.Dependency
4 | import org.gradle.api.artifacts.dsl.DependencyHandler
5 |
6 | fun DependencyHandler.`dokkaPlugin`(dependencyNotation: Any): Dependency? =
7 | add("dokkaPlugin", dependencyNotation)
--------------------------------------------------------------------------------
/build-logic/src/main/java/extensions/VersionCatalog.kt:
--------------------------------------------------------------------------------
1 | package extensions
2 |
3 | import org.gradle.api.artifacts.VersionCatalog
4 |
5 | internal fun VersionCatalog.getLibrary(library: String) = findLibrary(library).get()
6 | internal fun VersionCatalog.getVersion(library: String) = findVersion(library).get()
7 | internal fun VersionCatalog.getBundle(bundle: String) = findBundle(bundle).get()
8 |
9 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import java.net.URI
2 |
3 | @Suppress("DSL_SCOPE_VIOLATION")
4 | plugins {
5 | alias(libs.plugins.android.application) apply false
6 | alias(libs.plugins.android.library) apply false
7 | alias(libs.plugins.kotlin.android) apply false
8 | alias(libs.plugins.ksp) apply false
9 | alias(libs.plugins.dokka) apply false
10 | alias(libs.plugins.kover) apply false
11 | }
12 |
13 | tasks.register("clean", Delete::class) {
14 | delete(rootProject.buildDir)
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | google()
20 | mavenCentral()
21 | maven {
22 | url = URI.create("https://jitpack.io")
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/core-local-storage/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core-local-storage/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.streamplayer.android-library")
3 | id("com.google.devtools.ksp")
4 | }
5 |
6 | dependencies {
7 |
8 | ksp(libs.roomCompiler)
9 | implementation(libs.bundles.room)
10 | implementation(libs.bundles.kotlin)
11 | implementation(libs.bundles.koin)
12 | testImplementation(libs.bundles.test)
13 | }
--------------------------------------------------------------------------------
/core-local-storage/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodandoTV/Netflix-Android/7dc3eee526a58135ae2001293c50a42cf648a308/core-local-storage/consumer-rules.pro
--------------------------------------------------------------------------------
/core-local-storage/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/core-local-storage/src/main/java/com/codandotv/streamplayerapp/core_local_storage/data/dao/FavoriteDao.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.core_local_storage.data.dao
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Insert
5 | import androidx.room.OnConflictStrategy
6 | import androidx.room.Query
7 | import com.codandotv.streamplayerapp.core_local_storage.domain.model.MovieEntity
8 |
9 | @Dao
10 | interface FavoriteDao {
11 |
12 | @Query("SELECT * FROM movie")
13 | suspend fun fetchAll(): List
14 |
15 | @Insert(onConflict = OnConflictStrategy.REPLACE)
16 | suspend fun insert(favoriteMovie: MovieEntity)
17 |
18 | @Query("""DELETE FROM movie WHERE id = :favoriteMovie""")
19 | suspend fun delete(favoriteMovie: String)
20 | }
--------------------------------------------------------------------------------
/core-local-storage/src/main/java/com/codandotv/streamplayerapp/core_local_storage/data/database/StreamPlayerAppDatabase.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.core_local_storage.data.database
2 |
3 | import android.content.Context
4 | import androidx.room.Database
5 | import androidx.room.Room
6 | import androidx.room.RoomDatabase
7 | import com.codandotv.streamplayerapp.core_local_storage.data.dao.FavoriteDao
8 | import com.codandotv.streamplayerapp.core_local_storage.domain.model.MovieEntity
9 |
10 | @Database(entities = [MovieEntity::class], version = 1)
11 | abstract class StreamPlayerAppDatabase : RoomDatabase() {
12 |
13 | abstract fun favoriteDao(): FavoriteDao
14 |
15 | companion object {
16 |
17 | private var instance: StreamPlayerAppDatabase? = null
18 |
19 | fun getInstance(context: Context): StreamPlayerAppDatabase {
20 | if (instance == null) {
21 | synchronized(this) {
22 | instance = Room.databaseBuilder(
23 | context.applicationContext,
24 | StreamPlayerAppDatabase::class.java,
25 | DATABASE_NAME
26 | ).build()
27 | }
28 | }
29 | return instance!!
30 | }
31 |
32 | const val DATABASE_NAME = "app-database.db"
33 | }
34 | }
--------------------------------------------------------------------------------
/core-local-storage/src/main/java/com/codandotv/streamplayerapp/core_local_storage/di/LocalStorageModule.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.core_local_storage.di
2 |
3 | import androidx.room.Room
4 | import com.codandotv.streamplayerapp.core_local_storage.data.database.StreamPlayerAppDatabase
5 | import com.codandotv.streamplayerapp.core_local_storage.data.database.StreamPlayerAppDatabase.Companion.DATABASE_NAME
6 | import org.koin.android.ext.koin.androidContext
7 | import org.koin.dsl.module
8 |
9 | object LocalStorageModule {
10 | val module = module {
11 | single { Room.databaseBuilder(androidContext(), StreamPlayerAppDatabase::class.java, DATABASE_NAME).build() }
12 | single { StreamPlayerAppDatabase.getInstance(get()) }
13 | single { get().favoriteDao() }
14 | }
15 | }
--------------------------------------------------------------------------------
/core-local-storage/src/main/java/com/codandotv/streamplayerapp/core_local_storage/domain/model/MovieEntity.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.core_local_storage.domain.model
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Entity
5 | import androidx.room.PrimaryKey
6 |
7 | @Entity(tableName = "movie")
8 | data class MovieEntity(
9 | @PrimaryKey val id: String,
10 | @ColumnInfo("title") val title: String,
11 | @ColumnInfo("overview") val overview: String,
12 | @ColumnInfo("tagline") val tagline: String,
13 | @ColumnInfo("url") val url: String,
14 | @ColumnInfo("release_year")val releaseYear: String
15 | )
--------------------------------------------------------------------------------
/core-navigation/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core-navigation/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 | plugins {
3 | id("com.streamplayer.android-library")
4 | id("com.streamplayer.compose")
5 | }
6 |
7 | dependencies {
8 | implementation(libs.bundles.kotlin)
9 | }
--------------------------------------------------------------------------------
/core-navigation/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodandoTV/Netflix-Android/7dc3eee526a58135ae2001293c50a42cf648a308/core-navigation/consumer-rules.pro
--------------------------------------------------------------------------------
/core-navigation/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/core-navigation/src/main/java/com/codandotv/streamplayerapp/core_navigation/bottomnavigation/BottomNavItem.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.core_navigation.bottomnavigation
2 |
3 | import androidx.annotation.DrawableRes
4 | import androidx.annotation.StringRes
5 | import com.codandotv.streamplayerapp.core.navigation.R
6 | import com.codandotv.streamplayerapp.core_navigation.routes.BottomNavRoutes
7 |
8 | sealed class BottomNavItem(
9 | @StringRes var title: Int,
10 | @DrawableRes var iconUnselected: Int,
11 | @DrawableRes var iconSelected: Int,
12 | var screenRoute: String
13 | ) {
14 | object Home :
15 | BottomNavItem(
16 | R.string.bottom_nav_home,
17 | R.drawable.ic_home_unselected,
18 | R.drawable.ic_home_selected,
19 | BottomNavRoutes.HOME
20 | )
21 |
22 | object Games :
23 | BottomNavItem(
24 | R.string.bottom_nav_games,
25 | R.drawable.ic_games_unselected,
26 | R.drawable.ic_games_selected,
27 | BottomNavRoutes.GAMES
28 | )
29 |
30 | object News :
31 | BottomNavItem(
32 | R.string.bottom_nav_news,
33 | R.drawable.ic_news_unselected,
34 | R.drawable.ic_news_selected,
35 | BottomNavRoutes.NEWS
36 | )
37 |
38 | object Downloads : BottomNavItem(
39 | R.string.bottom_nav_downloads,
40 | R.drawable.ic_downloads_unselected,
41 | R.drawable.ic_downloads_selected,
42 | BottomNavRoutes.DOWNLOADS
43 | )
44 | }
--------------------------------------------------------------------------------
/core-navigation/src/main/java/com/codandotv/streamplayerapp/core_navigation/extensions/NavControllerExtension.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.core_navigation.extensions
2 |
3 | import androidx.navigation.NavController
4 |
5 | fun NavController.goBack() = this.navigateUp()
6 |
--------------------------------------------------------------------------------
/core-navigation/src/main/java/com/codandotv/streamplayerapp/core_navigation/helper/NavigationHelper.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.core_navigation.helper
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.getValue
5 | import androidx.navigation.NavController
6 | import androidx.navigation.compose.currentBackStackEntryAsState
7 |
8 | @Composable
9 | fun currentRoute(navController: NavController): String? {
10 | val navBackStackEntry by navController.currentBackStackEntryAsState()
11 | return navBackStackEntry?.destination?.route
12 | }
--------------------------------------------------------------------------------
/core-navigation/src/main/java/com/codandotv/streamplayerapp/core_navigation/routes/BottomNavRoutes.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.core_navigation.routes
2 |
3 | import com.codandotv.streamplayerapp.core_navigation.routes.BottomNavRoutes.PARAM.PROFILE_ID
4 |
5 | object BottomNavRoutes {
6 | const val HOME = "bottomHome"
7 | const val HOME_COMPLETE = "$HOME?$PROFILE_ID={$PROFILE_ID}"
8 | const val GAMES = "bottomGames"
9 | const val NEWS = "bottomNews"
10 | const val SCENES = "bottomScenes"
11 | const val DOWNLOADS = "bottomDownloads"
12 |
13 | object PARAM {
14 | const val PROFILE_ID = "userId"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/core-navigation/src/main/java/com/codandotv/streamplayerapp/core_navigation/routes/Routes.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.core_navigation.routes
2 |
3 | import com.codandotv.streamplayerapp.core_navigation.routes.Routes.PARAM.ID
4 |
5 | object Routes {
6 | const val DETAIL = "DetailList/"
7 | const val DETAIL_COMPLETE = "${DETAIL}{${ID}}"
8 | const val Splash = "splash"
9 | const val SEARCH = "Search"
10 | const val PROFILE_PICKER = "profilePicker"
11 |
12 | object PARAM {
13 | const val ID = "id"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/core-navigation/src/main/res/drawable/ic_downloads_selected.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/core-navigation/src/main/res/drawable/ic_downloads_unselected.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/core-navigation/src/main/res/drawable/ic_games_selected.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/core-navigation/src/main/res/drawable/ic_games_unselected.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/core-navigation/src/main/res/drawable/ic_home_selected.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/core-navigation/src/main/res/drawable/ic_home_unselected.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/core-navigation/src/main/res/drawable/ic_news_selected.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/core-navigation/src/main/res/drawable/ic_news_unselected.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/core-navigation/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Início
5 | Jogos
6 | Novidades
7 | Downloads
8 |
9 |
--------------------------------------------------------------------------------
/core-networking/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core-networking/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.streamplayer.android-library")
3 | }
4 | android {
5 | buildFeatures {
6 | buildConfig = true
7 | }
8 | buildTypes {
9 | debug {
10 | buildConfigField("String", "HOST", Config.BuildField.host_debug)
11 | buildConfigField("String", "API_BEARER_AUTH", Config.BuildField.api_bearer_debug)
12 | buildConfigField("String", "PROFILE", Config.BuildField.api_profile_debug)
13 |
14 | }
15 | getByName("release") {
16 | buildConfigField("String", "HOST", Config.BuildField.host_release)
17 | buildConfigField("String", "API_BEARER_AUTH", Config.BuildField.api_bearer_release)
18 | buildConfigField("String", "PROFILE", Config.BuildField.api_profile_release)
19 | }
20 | }
21 | }
22 | dependencies {
23 | implementation(libs.bundles.kotlin)
24 | implementation(libs.bundles.networking)
25 | implementation(libs.bundles.koin)
26 | testImplementation(libs.bundles.test)
27 | }
28 |
--------------------------------------------------------------------------------
/core-networking/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodandoTV/Netflix-Android/7dc3eee526a58135ae2001293c50a42cf648a308/core-networking/consumer-rules.pro
--------------------------------------------------------------------------------
/core-networking/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/core-networking/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/core-networking/src/main/java/com/codandotv/streamplayerapp/core_networking/Url.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.core_networking
2 |
3 | object Url {
4 | const val IMAGE_URL_SIZE_200 = "https://image.tmdb.org/t/p/w200/"
5 | const val IMAGE_URL_SIZE_300 = "https://image.tmdb.org/t/p/w300/"
6 | const val IMAGE_URL_SIZE_500 = "https://image.tmdb.org/t/p/w500/"
7 | }
8 |
--------------------------------------------------------------------------------
/core-networking/src/main/java/com/codandotv/streamplayerapp/core_networking/coroutines/NetworkResponseAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.core_networking.coroutines
2 |
3 | import com.squareup.moshi.Moshi
4 | import retrofit2.Call
5 | import retrofit2.CallAdapter
6 | import java.lang.reflect.Type
7 |
8 | class NetworkResponseAdapter(
9 | private val responseType: Type,
10 | private val moshi: Moshi
11 | ): CallAdapter {
12 |
13 | override fun responseType(): Type = responseType
14 | override fun adapt(call: Call) = NetworkResponseCall(call,moshi)
15 | }
--------------------------------------------------------------------------------
/core-networking/src/main/java/com/codandotv/streamplayerapp/core_networking/coroutines/NetworkResponseAdapterFactory.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.core_networking.coroutines
2 |
3 | import com.codandotv.streamplayerapp.core_networking.handleError.NetworkResponse
4 | import com.squareup.moshi.Moshi
5 | import retrofit2.Call
6 | import retrofit2.CallAdapter
7 | import retrofit2.Retrofit
8 | import java.lang.reflect.ParameterizedType
9 | import java.lang.reflect.Type
10 |
11 | @Suppress("ReturnCount", "SwallowedException")
12 | class NetworkResponseAdapterFactory(private val moshi: Moshi) : CallAdapter.Factory() {
13 | override fun get(
14 | returnType: Type,
15 | annotations: Array,
16 | retrofit: Retrofit
17 | ): CallAdapter<*, *>? {
18 | return try {
19 | // suspend functions wrap the response type in `Call`
20 | if (Call::class.java != getRawType(returnType)) {
21 | return null
22 | }
23 |
24 | // check first that the return type is `ParameterizedType`
25 | check(returnType is ParameterizedType) {
26 | "return type must be parameterized as Call> or Call>"
27 | }
28 |
29 | // get the response type inside the `Call` type
30 | val responseType = getParameterUpperBound(0, returnType)
31 |
32 | // if the response type is not ApiResponse then we can't handle this type, so we return null
33 | if (getRawType(responseType) != NetworkResponse::class.java) {
34 | return null
35 | }
36 |
37 | // the response type is ApiResponse and should be parameterized
38 | check(responseType is ParameterizedType) {
39 | "Response must be parameterized as NetworkResponse " +
40 | "or NetworkResponse"
41 | }
42 |
43 | val successBodyType = getParameterUpperBound(0, responseType)
44 |
45 | return NetworkResponseAdapter(successBodyType, moshi)
46 | } catch (ex: ClassCastException) {
47 | null
48 | }
49 | }
50 | }
--------------------------------------------------------------------------------
/core-networking/src/main/java/com/codandotv/streamplayerapp/core_networking/di/QualifierNetworking.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.core_networking.di
2 |
3 | import org.koin.core.qualifier.Qualifier
4 | import org.koin.core.qualifier.QualifierValue
5 |
6 | object QualifierHost : Qualifier {
7 | override val value: QualifierValue
8 | get() = "QualifierHost"
9 | }
10 |
11 | object QualifierProfile : Qualifier {
12 | override val value: QualifierValue
13 | get() = "QualifierProfile"
14 | }
15 |
16 | object QualifierProfileRetrofit : Qualifier {
17 | override val value: QualifierValue
18 | get() = "QualifierProfileRetrofit"
19 | }
20 |
21 |
22 | object QualifierLoggerInterceptor : Qualifier {
23 | override val value: QualifierValue
24 | get() = "QualifierLoggerInterceptor"
25 | }
26 |
27 | object QualifierAuthInterceptor : Qualifier {
28 | override val value: QualifierValue
29 | get() = "QualifierAuthInterceptor"
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/core-networking/src/main/java/com/codandotv/streamplayerapp/core_networking/handleError/Failure.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.core_networking.handleError
2 |
3 | import com.codandotv.streamplayerapp.core.networking.R
4 | import org.koin.core.component.KoinComponent
5 |
6 | /**
7 | * Base Class for handling errors/failures/exceptions.
8 | */
9 | @Suppress(
10 | "ThrowingExceptionsWithoutMessageOrCause",
11 | "TooGenericExceptionCaught",
12 | "MagicNumber"
13 | )
14 | sealed class Failure(
15 | val code: Int? = -1,
16 | val errorMessage: String? = null,
17 | val errorMessageRes: Int = R.string.core_networking_msg_default_error
18 | ) : Exception(), KoinComponent {
19 | data class NoDataContent(val codeStatus: Int? = null) :
20 | Failure(codeStatus, errorMessageRes = R.string.core_networking_no_data_content)
21 |
22 | data class ServerError(val codeStatus: Int? = null) :
23 | Failure(codeStatus, errorMessageRes = R.string.core_networking_no_server_error)
24 |
25 | data class GenericError(
26 | val codeStatus: Int? = -12, private val msg: String? = null
27 | ) : Failure(
28 | codeStatus
29 | )
30 |
31 | data class NetworkError(
32 | val codeStatus: Int? = -13, private val throwable: Throwable
33 | ) : Failure(
34 | codeStatus, errorMessageRes = R.string.core_networking_networking_error
35 | )
36 |
37 | data class UnknownError(
38 | val codeStatus: Int? = null, private val throwable: Throwable? = Exception()
39 | ) : Failure(
40 | codeStatus, throwable?.message
41 | )
42 |
43 | data class UnexpectedApiException(
44 | val codeStatus: Int? = -14, private val throwable: Throwable? = Exception()
45 | ) : Failure(
46 | codeStatus, throwable?.message
47 | )
48 |
49 | data class ClientException(
50 | val codeStatus: Int? = -15, private val throwable: Throwable? = Exception()
51 | ) : Failure(
52 | codeStatus, throwable?.message
53 | )
54 |
55 | data class UnparsableResponseException(
56 | val codeStatus: Int? = -16, private val throwable: Throwable? = Exception()
57 | ) : Failure(
58 | codeStatus, throwable?.message
59 | )
60 | }
--------------------------------------------------------------------------------
/core-networking/src/main/java/com/codandotv/streamplayerapp/core_networking/handleError/NetworkResponse.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.core_networking.handleError
2 |
3 | import kotlinx.coroutines.flow.Flow
4 | import kotlinx.coroutines.flow.flow
5 |
6 | sealed class NetworkResponse {
7 | data class Success(
8 | val value: T
9 | ) : NetworkResponse()
10 |
11 | data class Error(
12 | val body: Any? = null,
13 | @Transient
14 | val exception: Failure? = null
15 | ) : NetworkResponse()
16 | }
17 |
18 | fun NetworkResponse.toResult(): Result =
19 | when (this) {
20 | is NetworkResponse.Success -> {
21 | Result.success(this.value)
22 | }
23 | is NetworkResponse.Error -> {
24 | Result.failure(this.exception ?: Failure.GenericError())
25 | }
26 | }
27 |
28 | fun NetworkResponse.toFlow(): Flow {
29 | val networkResponse = this
30 | return flow {
31 | when (networkResponse) {
32 | is NetworkResponse.Success -> {
33 | emit(networkResponse.value)
34 | }
35 | is NetworkResponse.Error -> {
36 | throw networkResponse.exception ?: Failure.GenericError()
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/core-networking/src/main/java/com/codandotv/streamplayerapp/core_networking/handleError/ResultExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.core_networking.handleError
2 |
3 | import kotlinx.coroutines.flow.Flow
4 | import kotlinx.coroutines.flow.catch
5 |
6 | inline fun Result.onError(action: (exception: Failure) -> Unit): Result {
7 | if(isFailure && exceptionOrNull() is Failure){
8 | val error = exceptionOrNull() as Failure
9 | action(error)
10 | }
11 | return this
12 | }
13 |
14 | fun Flow.catchFailure(action: suspend kotlinx.coroutines.flow.FlowCollector.(Failure) -> Unit): Flow =
15 | catch {
16 | if(it is Failure){
17 | action(it)
18 | }else{
19 | action(Failure.GenericError())
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/core-networking/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Não foi possível concluir. Estamos trabalhando para resolver. Tente novamente em alguns instantes.
3 | Sem conteúdo de dados
4 | Erro no servidor
5 | Sem conexão. Verifique o wifi ou dados móveis e tente novamente em alguns instantes.
6 |
--------------------------------------------------------------------------------
/core-shared-ui/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core-shared-ui/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 | plugins {
3 | id("com.streamplayer.android-library")
4 | id("com.streamplayer.compose")
5 | }
6 |
7 | dependencies {
8 | implementation(projects.coreShared)
9 | implementation(libs.bundles.koin)
10 | implementation(libs.bundles.kotlin)
11 | implementation(libs.bundles.androidSupport)
12 | implementation(libs.android.youtube.player)
13 | testImplementation(libs.bundles.test)
14 | implementation(libs.coil)
15 | }
--------------------------------------------------------------------------------
/core-shared-ui/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodandoTV/Netflix-Android/7dc3eee526a58135ae2001293c50a42cf648a308/core-shared-ui/consumer-rules.pro
--------------------------------------------------------------------------------
/core-shared-ui/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/core-shared-ui/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/core-shared-ui/src/main/java/com/codandotv/streamplayerapp/core_shared_ui/resources/Colors.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.core_shared_ui.resources
2 |
3 | import androidx.compose.material3.darkColorScheme
4 | import androidx.compose.material3.lightColorScheme
5 | import androidx.compose.ui.graphics.Color
6 |
7 | @Suppress("MagicNumber")
8 | object Colors {
9 | val Dark10 = Color(0x1A000000)
10 | val AlphaBlack = Color(0xEB000000)
11 |
12 | val Gray100 = Color(0xFF2C2C2C)
13 |
14 | val LightColors = lightColorScheme(
15 | primary = Color(0xFFE50914),
16 | secondary = Color(0xFFF5F5F1),
17 | background = Color(0xFF000000),
18 | onBackground = Color(0xFFFFFFFF),
19 | surface = Color(0xFF121212),
20 | onSurface = Color(0xFFF5F5F1),
21 | onSurfaceVariant = Color(0XFF7b7b7b)
22 | )
23 |
24 | val DarkColors = darkColorScheme(
25 | primary = Color(0xFFE50914),
26 | secondary = Color(0xFFF5F5F1),
27 | background = Color(0xFF000000),
28 | onBackground = Color(0xFFFFFFFF),
29 | surface = Color(0xFF121212),
30 | onSurface = Color(0xFFF5F5F1),
31 | onSurfaceVariant = Color(0XFF7b7b7b)
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/core-shared-ui/src/main/java/com/codandotv/streamplayerapp/core_shared_ui/theme/StreamPlayerTheme.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.core_shared_ui.theme
2 |
3 | import androidx.compose.foundation.isSystemInDarkTheme
4 | import androidx.compose.material3.MaterialTheme
5 | import androidx.compose.runtime.Composable
6 | import com.codandotv.streamplayerapp.core_shared_ui.resources.Colors
7 |
8 | @Composable
9 | fun StreamPlayerTheme(
10 | isDarkTheme: Boolean = isSystemInDarkTheme(),
11 | content: @Composable () -> Unit
12 | ) {
13 |
14 | MaterialTheme(
15 | colorScheme = getColorScheme(isDarkTheme),
16 | content = content,
17 | )
18 | }
19 |
20 | private fun getColorScheme(isDarkTheme: Boolean) =
21 | if (isDarkTheme) {
22 | Colors.DarkColors
23 | } else {
24 | Colors.LightColors
25 | }
26 |
--------------------------------------------------------------------------------
/core-shared-ui/src/main/java/com/codandotv/streamplayerapp/core_shared_ui/theme/ThemePreviews.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.core_shared_ui.theme
2 |
3 | import android.content.res.Configuration
4 | import androidx.compose.material3.Surface
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.tooling.preview.Preview
7 |
8 | @Preview(
9 | name = "dark mode",
10 | group = "themes",
11 | uiMode = Configuration.UI_MODE_NIGHT_YES
12 | )
13 | @Preview(
14 | name = "light mode",
15 | group = "themes",
16 | uiMode = Configuration.UI_MODE_NIGHT_NO
17 | )
18 | annotation class ThemePreviews
19 |
20 | @Composable
21 | fun ThemePreview(
22 | content: @Composable () -> Unit
23 | ) {
24 | StreamPlayerTheme {
25 | Surface { content() }
26 | }
27 | }
--------------------------------------------------------------------------------
/core-shared-ui/src/main/java/com/codandotv/streamplayerapp/core_shared_ui/utils/Sharing.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.core_shared_ui.utils
2 |
3 | import android.content.Context
4 | import android.content.pm.PackageInfo
5 | import android.content.pm.PackageManager
6 | import android.os.Build
7 |
8 | object Sharing {
9 |
10 | const val SHARING_DATA_TYPE_TEXT = "text/plain"
11 | const val SHARING_DATA_TYPE_IMAGE = "image/*"
12 | const val COPY_CONTENT_TYPE_TEXT = "text"
13 | const val WHATSAPP_PACKAGE_SHARING = "com.whatsapp"
14 | const val INSTAGRAM_PACKAGE_SHARING = "com.instagram.android"
15 | const val INSTAGRAM_STORY_DESTINATION = "com.instagram.share.ADD_TO_STORY"
16 | const val SMS_CONTENT_TYPE = "sms:"
17 | const val SMS_CONTENT_BODY = "sms_body"
18 | const val OPTIONS_TITLE_MESSAGE = "Compartilhar usando"
19 | const val ANIMATION_EXECUTION_DELAY = 100L
20 | const val ANIMATION_DURATION = 300
21 | }
22 |
23 | @Suppress("SwallowedException")
24 | fun isPackageInstalled(packageName: String, context: Context): Boolean {
25 | val pm = context.packageManager
26 | return try {
27 | pm.getPackageInfoCompat(packageName)
28 | true
29 | } catch (e: PackageManager.NameNotFoundException) {
30 | false
31 | }
32 | }
33 |
34 | fun PackageManager.getPackageInfoCompat(packageName: String, flags: Int = 0): PackageInfo =
35 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
36 | getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(flags.toLong()))
37 | } else {
38 | @Suppress("DEPRECATION") getPackageInfo(packageName, flags)
39 | }
--------------------------------------------------------------------------------
/core-shared-ui/src/main/java/com/codandotv/streamplayerapp/core_shared_ui/widget/IconWithText.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.core_shared_ui.widget
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.Spacer
6 | import androidx.compose.foundation.layout.height
7 | import androidx.compose.material3.Icon
8 | import androidx.compose.material3.MaterialTheme
9 | import androidx.compose.material3.Text
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.graphics.Color
14 | import androidx.compose.ui.graphics.vector.ImageVector
15 | import androidx.compose.ui.text.font.FontWeight
16 | import androidx.compose.ui.unit.dp
17 | import androidx.compose.ui.unit.sp
18 |
19 | @Suppress("LongParameterList")
20 | @Composable
21 | fun IconWithText(
22 | onClick: () -> Unit,
23 | imageVector: ImageVector,
24 | imageColor: Color,
25 | text: String,
26 | textColor: Color,
27 | modifier: Modifier = Modifier
28 | ) {
29 |
30 | Column(
31 | horizontalAlignment = Alignment.CenterHorizontally,
32 | modifier = modifier.clickable { onClick() } ) {
33 | Icon(
34 | imageVector = imageVector,
35 | contentDescription = null,
36 | tint = imageColor
37 | )
38 | Spacer(modifier = Modifier.height(4.dp))
39 | Text(
40 | text = text,
41 | style = MaterialTheme.typography.headlineMedium.copy(
42 | fontSize = 12.sp,
43 | fontWeight = FontWeight.Bold,
44 | ),
45 | color = textColor
46 | )
47 | }
48 | }
--------------------------------------------------------------------------------
/core-shared-ui/src/main/res/drawable/ic_add.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/core-shared-ui/src/main/res/drawable/ic_close.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/core-shared-ui/src/main/res/drawable/ic_copy_content.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/core-shared-ui/src/main/res/drawable/ic_info.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/core-shared-ui/src/main/res/drawable/ic_instagram.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
19 |
20 |
21 |
22 |
23 |
24 |
27 |
30 |
33 |
34 |
--------------------------------------------------------------------------------
/core-shared-ui/src/main/res/drawable/ic_message.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/core-shared-ui/src/main/res/drawable/ic_netflix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodandoTV/Netflix-Android/7dc3eee526a58135ae2001293c50a42cf648a308/core-shared-ui/src/main/res/drawable/ic_netflix.png
--------------------------------------------------------------------------------
/core-shared-ui/src/main/res/drawable/ic_play.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/core-shared-ui/src/main/res/drawable/ic_whatsapp.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/core-shared-ui/src/main/res/drawable/perfil_fake.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodandoTV/Netflix-Android/7dc3eee526a58135ae2001293c50a42cf648a308/core-shared-ui/src/main/res/drawable/perfil_fake.png
--------------------------------------------------------------------------------
/core-shared-ui/src/main/res/drawable/transparent_image.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/core-shared-ui/src/main/res/mipmap-anydpi-v26/ic_netflix.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/core-shared-ui/src/main/res/mipmap-anydpi-v26/ic_netflix_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/core-shared-ui/src/main/res/mipmap-hdpi/ic_netflix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodandoTV/Netflix-Android/7dc3eee526a58135ae2001293c50a42cf648a308/core-shared-ui/src/main/res/mipmap-hdpi/ic_netflix.png
--------------------------------------------------------------------------------
/core-shared-ui/src/main/res/mipmap-hdpi/ic_netflix_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodandoTV/Netflix-Android/7dc3eee526a58135ae2001293c50a42cf648a308/core-shared-ui/src/main/res/mipmap-hdpi/ic_netflix_round.png
--------------------------------------------------------------------------------
/core-shared-ui/src/main/res/mipmap-mdpi/ic_netflix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodandoTV/Netflix-Android/7dc3eee526a58135ae2001293c50a42cf648a308/core-shared-ui/src/main/res/mipmap-mdpi/ic_netflix.png
--------------------------------------------------------------------------------
/core-shared-ui/src/main/res/mipmap-mdpi/ic_netflix_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodandoTV/Netflix-Android/7dc3eee526a58135ae2001293c50a42cf648a308/core-shared-ui/src/main/res/mipmap-mdpi/ic_netflix_round.png
--------------------------------------------------------------------------------
/core-shared-ui/src/main/res/mipmap-xhdpi/ic_netflix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodandoTV/Netflix-Android/7dc3eee526a58135ae2001293c50a42cf648a308/core-shared-ui/src/main/res/mipmap-xhdpi/ic_netflix.png
--------------------------------------------------------------------------------
/core-shared-ui/src/main/res/mipmap-xhdpi/ic_netflix_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodandoTV/Netflix-Android/7dc3eee526a58135ae2001293c50a42cf648a308/core-shared-ui/src/main/res/mipmap-xhdpi/ic_netflix_round.png
--------------------------------------------------------------------------------
/core-shared-ui/src/main/res/mipmap-xxhdpi/ic_netflix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodandoTV/Netflix-Android/7dc3eee526a58135ae2001293c50a42cf648a308/core-shared-ui/src/main/res/mipmap-xxhdpi/ic_netflix.png
--------------------------------------------------------------------------------
/core-shared-ui/src/main/res/mipmap-xxhdpi/ic_netflix_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodandoTV/Netflix-Android/7dc3eee526a58135ae2001293c50a42cf648a308/core-shared-ui/src/main/res/mipmap-xxhdpi/ic_netflix_round.png
--------------------------------------------------------------------------------
/core-shared-ui/src/main/res/mipmap-xxxhdpi/ic_netflix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodandoTV/Netflix-Android/7dc3eee526a58135ae2001293c50a42cf648a308/core-shared-ui/src/main/res/mipmap-xxxhdpi/ic_netflix.png
--------------------------------------------------------------------------------
/core-shared-ui/src/main/res/mipmap-xxxhdpi/ic_netflix_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodandoTV/Netflix-Android/7dc3eee526a58135ae2001293c50a42cf648a308/core-shared-ui/src/main/res/mipmap-xxxhdpi/ic_netflix_round.png
--------------------------------------------------------------------------------
/core-shared-ui/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
--------------------------------------------------------------------------------
/core-shared-ui/src/main/res/values-v31/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
--------------------------------------------------------------------------------
/core-shared-ui/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/core-shared-ui/src/main/res/values/content-description.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Ícone Netflix
4 | Ícone Perfil
5 | Ícone Buscar
6 | Ícone Fechar
7 | Ícone Microfone
8 | Ícone Voltar
9 | Ícone Projetar
10 |
--------------------------------------------------------------------------------
/core-shared-ui/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | StreamPlayerApp
3 |
4 |
5 | Séries
6 | Filmes
7 | Categorias
8 |
9 |
10 |
11 | Compartilhar em...
12 | WhatsApp
13 | Mensagens
14 | Stories do Instagram
15 | Copiar Link
16 | Link copiado
17 | Mais Opções
18 | Já assistiu a \"%1s\" na Netflix?\n\n%2s
19 | Por favor, instale o aplicativo WhatsApp
20 | Por favor, instale o aplicativo Instagram
21 | Erro ao abrir o app Mensagens
22 |
23 |
24 |
25 | Preview
26 |
27 |
--------------------------------------------------------------------------------
/core-shared-ui/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
21 |
22 |
--------------------------------------------------------------------------------
/core-shared/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core-shared/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.streamplayer.android-library")
3 | }
4 |
5 | dependencies {
6 | implementation(libs.bundles.koin)
7 | }
--------------------------------------------------------------------------------
/core-shared/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodandoTV/Netflix-Android/7dc3eee526a58135ae2001293c50a42cf648a308/core-shared/consumer-rules.pro
--------------------------------------------------------------------------------
/core-shared/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/core-shared/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/core-shared/src/main/java/com/codandotv/streamplayerapp/core_shared/extension/ErrorExt.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.core_shared.extension
2 |
3 | import android.content.Context
4 | import android.widget.Toast
5 |
6 | fun showErrorMessage(context: Context, errorMessage: String, duration: Int = Toast.LENGTH_LONG) {
7 | Toast.makeText(context, errorMessage, duration).show()
8 | }
--------------------------------------------------------------------------------
/core-shared/src/main/java/com/codandotv/streamplayerapp/core_shared/extension/String.Ext.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.core_shared.extension
2 |
3 | fun String.Companion.empty() = ""
--------------------------------------------------------------------------------
/core-shared/src/main/java/com/codandotv/streamplayerapp/core_shared/extension/UriExt.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.core_shared.extension
2 |
3 | import android.content.Context
4 | import android.graphics.BitmapFactory
5 | import android.net.Uri
6 | import android.provider.MediaStore
7 | import java.io.IOException
8 | import java.io.InputStream
9 | import java.net.HttpURLConnection
10 | import java.net.MalformedURLException
11 | import java.net.URL
12 | import java.util.*
13 |
14 | @Suppress("MagicNumber", "SwallowedException")
15 | fun getUriFromUrlImage(
16 | contentUrl: String,
17 | context: Context
18 | ): Uri? {
19 | var url: URL? = null
20 | try {
21 | url = URL(contentUrl)
22 | } catch (e: MalformedURLException) {
23 | e.printStackTrace()
24 | }
25 | var connection: HttpURLConnection? = null
26 | try {
27 | assert(url != null)
28 | connection = url!!.openConnection() as HttpURLConnection
29 | } catch (e: IOException) {
30 | showErrorMessage(context, "Erro ao buscar imagem")
31 | }
32 | assert(connection != null)
33 | connection!!.doInput = true
34 | try {
35 | connection.connect()
36 | } catch (e: IOException) {
37 | showErrorMessage(context, "Erro ao buscar imagem")
38 | }
39 | var input: InputStream? = null
40 | try {
41 | input = connection.inputStream
42 | } catch (e: IOException) {
43 | showErrorMessage(context, "Erro ao buscar imagem")
44 | }
45 | val imgBitmap = BitmapFactory.decodeStream(input)
46 | val rand = Random()
47 | val randNo = rand.nextInt(100000)
48 | val imgBitmapPath = MediaStore.Images.Media.insertImage(
49 | context.contentResolver, imgBitmap,
50 | "IMG:$randNo", null
51 | )
52 | return Uri.parse(imgBitmapPath)
53 | }
--------------------------------------------------------------------------------
/core-shared/src/main/java/com/codandotv/streamplayerapp/core_shared/qualifier/QualifierDispatcherIO.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.core_shared.qualifier
2 |
3 | import org.koin.core.qualifier.Qualifier
4 | import org.koin.core.qualifier.QualifierValue
5 |
6 | object QualifierDispatcherIO : Qualifier {
7 | override val value: QualifierValue
8 | get() = "dispatcherIO"
9 | }
10 |
--------------------------------------------------------------------------------
/fastlane/Fastfile:
--------------------------------------------------------------------------------
1 |
2 | default_platform(:android)
3 |
4 | platform :android do
5 |
6 | desc "Run all CI lanes"
7 | lane :ci do
8 | lint
9 | test
10 | debug
11 | end
12 |
13 | desc "Runs all the tests"
14 | lane :test do
15 | gradle(task: "test")
16 | end
17 |
18 | desc "Lint check"
19 | lane :lint do
20 | gradle(task: "detekt")
21 | end
22 |
23 | desc "Create Release build"
24 | lane :release do
25 | gradle(task: "clean assembleRelease")
26 | end
27 |
28 | desc "Deploy a new version to the Google Play"
29 | lane :debug do
30 | gradle(task: "clean assembleDebug")
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/feature-favorites/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature-favorites/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 | plugins {
3 | id("com.streamplayer.android-library")
4 | id("com.streamplayer.compose")
5 | }
6 |
7 | dependencies {
8 | implementation(projects.coreNetworking)
9 | implementation(projects.coreNavigation)
10 | implementation(projects.coreShared)
11 | implementation(projects.coreSharedUi)
12 |
13 | implementation(libs.bundles.koin)
14 | implementation(libs.bundles.networking)
15 | implementation(libs.bundles.androidSupport)
16 | implementation(libs.coil)
17 |
18 | testImplementation(libs.bundles.test)
19 | }
--------------------------------------------------------------------------------
/feature-favorites/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodandoTV/Netflix-Android/7dc3eee526a58135ae2001293c50a42cf648a308/feature-favorites/consumer-rules.pro
--------------------------------------------------------------------------------
/feature-favorites/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/feature-list-streams/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature-list-streams/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 |
3 | plugins {
4 | id("com.streamplayer.android-library")
5 | id("com.streamplayer.compose")
6 | alias(libs.plugins.ksp)
7 | }
8 |
9 | dependencies {
10 | implementation(projects.coreNetworking)
11 | implementation(projects.coreNavigation)
12 | implementation(projects.coreShared)
13 | implementation(projects.coreSharedUi)
14 | implementation(projects.coreLocalStorage)
15 |
16 | implementation(libs.bundles.koin)
17 | implementation(libs.koin.annotations)
18 | ksp(libs.koin.compiler)
19 |
20 | implementation(libs.bundles.networking)
21 | implementation(libs.roomRuntime)
22 | implementation(libs.bundles.androidSupport)
23 | implementation(libs.coil)
24 |
25 | testImplementation(libs.bundles.test)
26 | }
27 |
28 | ksp {
29 | arg("KOIN_CONFIG_CHECK","true")
30 | }
--------------------------------------------------------------------------------
/feature-list-streams/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodandoTV/Netflix-Android/7dc3eee526a58135ae2001293c50a42cf648a308/feature-list-streams/consumer-rules.pro
--------------------------------------------------------------------------------
/feature-list-streams/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/core/ContentType.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.core
2 |
3 | import androidx.annotation.StringRes
4 | import com.codandotv.streamplayerapp.feature.list.streams.R
5 |
6 | enum class ContentType(@StringRes val contentName: Int, @StringRes val contentNameAsPlural: Int) {
7 | SHOW(R.string.list_content_type_show, R.string.list_content_type_show_plural),
8 | FILM(R.string.list_content_type_film, R.string.list_content_type_film_plural);
9 |
10 | companion object {
11 | fun getContentName(contentType: ContentType) =
12 | values().first { contentType == it }.contentName
13 |
14 | fun getContentNameAsPlural(contentType: ContentType) =
15 | values().first { contentType == it }.contentNameAsPlural
16 | }
17 | }
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/detail/data/DetailStreamRepository.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.detail.data
2 |
3 | import com.codandotv.streamplayerapp.core_local_storage.data.dao.FavoriteDao
4 | import com.codandotv.streamplayerapp.core_networking.handleError.toFlow
5 | import com.codandotv.streamplayerapp.feature_list_streams.detail.domain.DetailStream
6 | import com.codandotv.streamplayerapp.feature_list_streams.detail.domain.VideoStream
7 | import com.codandotv.streamplayerapp.feature_list_streams.detail.domain.toDetailStream
8 | import com.codandotv.streamplayerapp.feature_list_streams.detail.domain.toDetailStreamLocal
9 | import com.codandotv.streamplayerapp.feature_list_streams.detail.domain.toVideoStreams
10 | import kotlinx.coroutines.flow.Flow
11 | import kotlinx.coroutines.flow.map
12 |
13 | interface DetailStreamRepository {
14 | suspend fun getMovie(): Flow
15 | suspend fun deleteFromMyList(movie: String)
16 | suspend fun insertToMyList(movie: DetailStream)
17 | suspend fun isFavorite(movieId:String) : Boolean
18 | suspend fun getVideoStreams(): Flow>
19 | }
20 |
21 | class DetailStreamRepositoryImpl(
22 | private val movieId: String,
23 | private val service: DetailStreamService,
24 | private val favoriteDao: FavoriteDao,
25 | ) : DetailStreamRepository {
26 |
27 | override suspend fun getMovie(): Flow =
28 | service.getMovie(movieId)
29 | .toFlow()
30 | .map {
31 | it.toDetailStream(isFavorite(movieId))
32 | }
33 |
34 |
35 | override suspend fun deleteFromMyList(movie: String) = favoriteDao.delete(movie)
36 |
37 | override suspend fun insertToMyList(movie: DetailStream) = favoriteDao.insert(movie.toDetailStreamLocal())
38 |
39 | /**
40 | * Verify if movieId was saved as favorite
41 | * @param movieId
42 | * @return Boolean
43 | */
44 | override suspend fun isFavorite(movieId: String) : Boolean = favoriteDao.fetchAll().any {
45 | movie -> movie.id == movieId
46 | }
47 |
48 | override suspend fun getVideoStreams(): Flow> =
49 | service.getVideoStreams(movieId)
50 | .toFlow()
51 | .map {
52 | it.toVideoStreams()
53 | }
54 | }
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/detail/data/DetailStreamService.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.detail.data
2 |
3 | import com.codandotv.streamplayerapp.core_networking.handleError.NetworkResponse
4 | import com.codandotv.streamplayerapp.feature_list_streams.detail.data.model.DetailStreamResponse
5 | import com.codandotv.streamplayerapp.feature_list_streams.detail.data.model.VideoStreamsResponse
6 | import retrofit2.http.GET
7 | import retrofit2.http.Path
8 |
9 | interface DetailStreamService {
10 | @GET("movie/{movie_id}")
11 | suspend fun getMovie(@Path("movie_id") movieId: String): NetworkResponse
12 |
13 | @GET("movie/{movie_id}/videos")
14 | suspend fun getVideoStreams(@Path("movie_id") movieId: String): NetworkResponse
15 | }
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/detail/data/model/DetailStreamResponse.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.detail.data.model
2 |
3 | @Suppress("ConstructorParameterNaming")
4 | data class DetailStreamResponse(
5 | val id : String,
6 | val title : String,
7 | val overview : String,
8 | val tagline : String,
9 | val backdrop_path : String,
10 | val release_date : String
11 | )
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/detail/data/model/VideoStreamResponse.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.detail.data.model
2 |
3 | data class VideoStreamResponse(
4 | val id: String,
5 | val name: String,
6 | val key: String,
7 | val site: String,
8 | val size: Int,
9 | val official: Boolean,
10 | val type: String,
11 | )
12 |
13 | data class VideoStreamsResponse(
14 | val id: Long,
15 | val results: List
16 | )
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/detail/di/DetailStreamModule.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.detail.di
2 |
3 | import com.codandotv.streamplayerapp.feature_list_streams.detail.data.DetailStreamRepository
4 | import com.codandotv.streamplayerapp.feature_list_streams.detail.data.DetailStreamRepositoryImpl
5 | import com.codandotv.streamplayerapp.feature_list_streams.detail.data.DetailStreamService
6 | import com.codandotv.streamplayerapp.feature_list_streams.detail.domain.DetailStreamUseCase
7 | import com.codandotv.streamplayerapp.feature_list_streams.detail.domain.DetailStreamUseCaseImpl
8 | import com.codandotv.streamplayerapp.feature_list_streams.detail.domain.VideoStreamsUseCase
9 | import com.codandotv.streamplayerapp.feature_list_streams.detail.domain.VideoStreamsUseCaseImpl
10 | import com.codandotv.streamplayerapp.feature_list_streams.detail.presentation.screens.DetailStreamViewModel
11 | import kotlinx.coroutines.Dispatchers
12 | import org.koin.androidx.viewmodel.dsl.viewModel
13 | import org.koin.core.parameter.parametersOf
14 | import org.koin.dsl.module
15 | import retrofit2.Retrofit
16 |
17 | object DetailStreamModule {
18 | val module = module {
19 | viewModel { (id: String) ->
20 | DetailStreamViewModel(
21 | detailStreamUseCase = get {
22 | parametersOf(id)
23 | },
24 | videoStreamsUseCase = get {
25 | parametersOf(id)
26 | },
27 | dispatcher = Dispatchers.IO
28 | )
29 | }
30 | factory { (id: String) ->
31 | DetailStreamUseCaseImpl(
32 | detailStreamRepository = get {
33 | parametersOf(id)
34 | }
35 | )
36 | }
37 | factory { (id: String) ->
38 | VideoStreamsUseCaseImpl(
39 | detailStreamRepository = get {
40 | parametersOf(id)
41 | }
42 | )
43 | }
44 | factory { (id: String) ->
45 | DetailStreamRepositoryImpl(
46 | favoriteDao = get(),
47 | service = get(),
48 | movieId = id,
49 | )
50 | }
51 |
52 | factory { get().create(DetailStreamService::class.java) }
53 | }
54 | }
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/detail/domain/DetailStream.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.detail.domain
2 |
3 | data class DetailStream(
4 | val id : String,
5 | val title : String,
6 | val overview : String,
7 | val tagline : String,
8 | val url : String,
9 | val releaseYear : String,
10 | val isFavorite: Boolean
11 | )
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/detail/domain/DetailStreamMapper.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.detail.domain
2 |
3 | import com.codandotv.streamplayerapp.core_local_storage.domain.model.MovieEntity
4 | import com.codandotv.streamplayerapp.core_networking.Url.IMAGE_URL_SIZE_500
5 | import com.codandotv.streamplayerapp.feature_list_streams.detail.data.model.DetailStreamResponse
6 | import com.codandotv.streamplayerapp.feature_list_streams.detail.data.model.VideoStreamsResponse
7 |
8 | @Suppress("MagicNumber")
9 | fun DetailStreamResponse.toDetailStream(isFavorite: Boolean = false): DetailStream =
10 | DetailStream(
11 | id = this.id,
12 | title = this.title,
13 | overview = this.overview,
14 | tagline = this.tagline,
15 | url = "$IMAGE_URL_SIZE_500${this.backdrop_path}",
16 | releaseYear = this.release_date.substring(0, 4),
17 | isFavorite = isFavorite
18 | )
19 |
20 | fun DetailStream.toDetailStreamLocal(): MovieEntity =
21 | MovieEntity(
22 | id = this.id,
23 | title = this.title,
24 | overview = this.overview,
25 | tagline = this.tagline,
26 | url = this.url,
27 | releaseYear = this.releaseYear,
28 | )
29 |
30 | fun VideoStreamsResponse.toVideoStreams(): List =
31 | results.map {
32 | VideoStream(
33 | videoId = it.key,
34 | movieId = this.id
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/detail/domain/DetailStreamUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.detail.domain
2 |
3 | import com.codandotv.streamplayerapp.feature_list_streams.detail.data.DetailStreamRepository
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface DetailStreamUseCase {
7 | suspend fun getMovie(): Flow
8 |
9 | suspend fun toggleItemInFavorites(movie: DetailStream)
10 | }
11 |
12 | class DetailStreamUseCaseImpl(
13 | private val detailStreamRepository: DetailStreamRepository
14 | ) : DetailStreamUseCase {
15 |
16 | override suspend fun getMovie(): Flow =
17 | detailStreamRepository.getMovie()
18 |
19 | override suspend fun toggleItemInFavorites(movie: DetailStream) {
20 | if (detailStreamRepository.isFavorite(movie.id)) {
21 | detailStreamRepository.deleteFromMyList(movie.id)
22 | } else {
23 | detailStreamRepository.insertToMyList(movie)
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/detail/domain/VideoStream.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.detail.domain
2 |
3 | data class VideoStream(
4 | val movieId: Long,
5 | val videoId: String,
6 | )
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/detail/domain/VideoStreamsUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.detail.domain
2 |
3 | import com.codandotv.streamplayerapp.feature_list_streams.detail.data.DetailStreamRepository
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface VideoStreamsUseCase {
7 | suspend fun getVideoStreams(): Flow>
8 | }
9 |
10 | class VideoStreamsUseCaseImpl(
11 | private val detailStreamRepository: DetailStreamRepository
12 | ) : VideoStreamsUseCase {
13 | override suspend fun getVideoStreams(): Flow> {
14 | return detailStreamRepository.getVideoStreams()
15 | }
16 | }
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/detail/presentation/navigation/DetailStreamNavigation.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(KoinExperimentalAPI::class)
2 |
3 | package com.codandotv.streamplayerapp.feature_list_streams.detail.presentation.navigation
4 |
5 | import androidx.navigation.NavGraphBuilder
6 | import androidx.navigation.NavHostController
7 | import androidx.navigation.compose.composable
8 | import com.codandotv.streamplayerapp.core_navigation.routes.Routes
9 | import com.codandotv.streamplayerapp.core_navigation.routes.Routes.DETAIL_COMPLETE
10 | import com.codandotv.streamplayerapp.core_navigation.routes.Routes.PARAM.ID
11 | import com.codandotv.streamplayerapp.feature_list_streams.detail.di.DetailStreamModule
12 | import com.codandotv.streamplayerapp.feature_list_streams.detail.presentation.screens.DetailStreamScreen
13 | import org.koin.androidx.compose.koinViewModel
14 | import org.koin.compose.module.rememberKoinModules
15 | import org.koin.core.annotation.KoinExperimentalAPI
16 | import org.koin.core.parameter.parametersOf
17 |
18 | internal const val DEFAULT_ID = "0"
19 |
20 | fun NavGraphBuilder.detailStreamNavGraph(navController: NavHostController) {
21 | composable(DETAIL_COMPLETE) { nav ->
22 | rememberKoinModules {
23 | listOf(DetailStreamModule.module)
24 | }
25 | DetailStreamScreen(
26 | viewModel = koinViewModel {
27 | parametersOf(nav.arguments?.getString(ID) ?: DEFAULT_ID)
28 | },
29 | navController = navController,
30 | onNavigateSearchScreen = {
31 | navController.navigate(Routes.SEARCH)
32 | },
33 | )
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/detail/presentation/screens/DetailStreamViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.detail.presentation.screens
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.viewModelScope
5 | import com.codandotv.streamplayerapp.core_networking.handleError.catchFailure
6 | import com.codandotv.streamplayerapp.feature_list_streams.detail.domain.DetailStream
7 | import com.codandotv.streamplayerapp.feature_list_streams.detail.domain.DetailStreamUseCase
8 | import com.codandotv.streamplayerapp.feature_list_streams.detail.domain.VideoStreamsUseCase
9 | import com.codandotv.streamplayerapp.feature_list_streams.detail.presentation.screens.DetailStreamsUIState.DetailStreamsLoadedUIState
10 | import com.codandotv.streamplayerapp.feature_list_streams.detail.presentation.screens.DetailStreamsUIState.LoadingStreamUIState
11 | import kotlinx.coroutines.CoroutineDispatcher
12 | import kotlinx.coroutines.flow.MutableStateFlow
13 | import kotlinx.coroutines.flow.SharingStarted
14 | import kotlinx.coroutines.flow.StateFlow
15 | import kotlinx.coroutines.flow.flowOn
16 | import kotlinx.coroutines.flow.onStart
17 | import kotlinx.coroutines.flow.stateIn
18 | import kotlinx.coroutines.flow.update
19 | import kotlinx.coroutines.flow.zip
20 | import kotlinx.coroutines.launch
21 |
22 | class DetailStreamViewModel(
23 | private val detailStreamUseCase: DetailStreamUseCase,
24 | private val videoStreamsUseCase: VideoStreamsUseCase,
25 | private val dispatcher: CoroutineDispatcher
26 | ) : ViewModel() {
27 |
28 | private val _uiState = MutableStateFlow(LoadingStreamUIState)
29 | val uiState: StateFlow = _uiState.stateIn(
30 | viewModelScope,
31 | SharingStarted.Eagerly,
32 | initialValue = _uiState.value
33 | )
34 |
35 | fun loadDetail() {
36 | viewModelScope.launch {
37 | detailStreamUseCase.getMovie()
38 | .zip(videoStreamsUseCase.getVideoStreams()) { detailStream, videoUrl ->
39 | DetailStreamsLoadedUIState(
40 | detailStream = detailStream,
41 | videoId = videoUrl.firstOrNull()?.videoId
42 | )
43 | }
44 | .flowOn(dispatcher)
45 | .onStart { onLoading() }
46 | .catchFailure {
47 | println(">>>> ${it.errorMessage}")
48 | }
49 | .collect { result ->
50 | _uiState.update {
51 | result
52 | }
53 | }
54 | }
55 | }
56 |
57 | private fun onLoading() {
58 | _uiState.update { LoadingStreamUIState }
59 | }
60 |
61 | fun toggleItemInFavorites(detailStream: DetailStream) {
62 | viewModelScope.launch {
63 | detailStreamUseCase.toggleItemInFavorites(detailStream)
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/detail/presentation/screens/DetailStreamsUIState.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.detail.presentation.screens
2 |
3 | import com.codandotv.streamplayerapp.feature_list_streams.detail.domain.DetailStream
4 |
5 | sealed class DetailStreamsUIState {
6 | data class DetailStreamsLoadedUIState(
7 | val detailStream: DetailStream,
8 | val videoId: String?,
9 | ) : DetailStreamsUIState()
10 |
11 | object LoadingStreamUIState : DetailStreamsUIState()
12 | }
13 |
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/detail/presentation/widget/DetailStreamActionOption.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.detail.presentation.widget
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.material.icons.Icons
7 | import androidx.compose.material.icons.filled.Add
8 | import androidx.compose.material.icons.filled.Check
9 | import androidx.compose.material.icons.filled.Download
10 | import androidx.compose.material.icons.filled.Share
11 | import androidx.compose.material.icons.filled.ThumbUp
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.runtime.LaunchedEffect
14 | import androidx.compose.runtime.getValue
15 | import androidx.compose.runtime.mutableStateOf
16 | import androidx.compose.runtime.remember
17 | import androidx.compose.runtime.saveable.rememberSaveable
18 | import androidx.compose.runtime.setValue
19 | import androidx.compose.ui.Modifier
20 | import androidx.compose.ui.graphics.Color
21 | import androidx.compose.ui.res.stringResource
22 | import com.codandotv.streamplayerapp.core_shared_ui.widget.IconWithText
23 | import com.codandotv.streamplayerapp.feature.list.streams.R
24 | import com.codandotv.streamplayerapp.feature_list_streams.detail.domain.DetailStream
25 |
26 | @Composable
27 | fun DetailStreamActionOption(
28 | detailStream: DetailStream,
29 | onToggleToMyList: (DetailStream) -> Unit,
30 | onShowSharingOptions: () -> Unit,
31 | modifier: Modifier = Modifier.fillMaxWidth()
32 | ) {
33 | var checked by rememberSaveable { mutableStateOf(detailStream.isFavorite) }
34 | var iconCheckList by remember { mutableStateOf(Icons.Filled.Add) }
35 |
36 | LaunchedEffect(checked) {
37 | iconCheckList =
38 | if (checked) Icons.Filled.Check else Icons.Filled.Add
39 | }
40 |
41 | Row(
42 | modifier = modifier,
43 | horizontalArrangement = Arrangement.SpaceEvenly
44 | ) {
45 | IconWithText(
46 | onClick = {
47 | checked = !checked
48 | onToggleToMyList(detailStream)
49 | },
50 | imageVector = iconCheckList,
51 | imageColor = Color.White,
52 | text = stringResource(id = R.string.detail_my_list),
53 | textColor = Color.Gray,
54 | )
55 | IconWithText(
56 | onClick = { TODO("Implementar mecanismo de classificação.") },
57 | imageVector = Icons.Filled.ThumbUp,
58 | imageColor = Color.White,
59 | text = stringResource(id = R.string.detail_classification),
60 | textColor = Color.Gray,
61 | )
62 | IconWithText(
63 | onClick = { onShowSharingOptions.invoke() },
64 | imageVector = Icons.Filled.Share,
65 | imageColor = Color.White,
66 | text = stringResource(id = R.string.detail_share),
67 | textColor = Color.Gray,
68 | )
69 | IconWithText(
70 | onClick = { TODO("Implementar mecanismo de download.") },
71 | imageVector = Icons.Filled.Download,
72 | imageColor = Color.White,
73 | text = stringResource(id = R.string.detail_download),
74 | textColor = Color.Gray,
75 | )
76 | }
77 | }
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/detail/presentation/widget/DetailStreamButtonAction.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.detail.presentation.widget
2 |
3 | import androidx.compose.foundation.layout.Row
4 | import androidx.compose.foundation.layout.Spacer
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.foundation.layout.size
7 | import androidx.compose.foundation.layout.width
8 | import androidx.compose.foundation.shape.RoundedCornerShape
9 | import androidx.compose.material3.Button
10 | import androidx.compose.material3.ButtonColors
11 | import androidx.compose.material3.Icon
12 | import androidx.compose.material3.MaterialTheme
13 | import androidx.compose.material3.Text
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.graphics.Color
18 | import androidx.compose.ui.graphics.vector.ImageVector
19 | import androidx.compose.ui.text.font.FontWeight
20 | import androidx.compose.ui.unit.dp
21 | import androidx.compose.ui.unit.sp
22 |
23 | @Suppress("LongParameterList")
24 | @Composable
25 | fun DetailStreamButtonAction(
26 | buttonsColors: ButtonColors,
27 | imageVector: ImageVector,
28 | imageVectorColor: Color,
29 | text: String,
30 | textColor: Color,
31 | modifier: Modifier = Modifier.fillMaxWidth(),
32 | ) {
33 | Button(
34 | onClick = { },
35 | shape = RoundedCornerShape(4.dp),
36 | modifier = modifier,
37 | colors = buttonsColors,
38 | ) {
39 | Row(
40 | verticalAlignment = Alignment.CenterVertically
41 | ) {
42 | Icon(
43 | imageVector,
44 | contentDescription = null,
45 | tint = imageVectorColor,
46 | modifier = Modifier.size(28.dp)
47 | )
48 | Spacer(modifier = Modifier.width(8.dp))
49 | Text(
50 | text = text,
51 | style = MaterialTheme.typography.headlineMedium.copy(
52 | color = textColor,
53 | fontWeight = FontWeight.Bold,
54 | fontSize = 16.sp
55 | )
56 | )
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/detail/presentation/widget/DetailStreamImagePreview.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.detail.presentation.widget
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.clickable
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.aspectRatio
7 | import androidx.compose.foundation.layout.fillMaxHeight
8 | import androidx.compose.foundation.layout.fillMaxWidth
9 | import androidx.compose.foundation.layout.size
10 | import androidx.compose.foundation.shape.CircleShape
11 | import androidx.compose.material3.Icon
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.ui.Alignment
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.graphics.Color
16 | import androidx.compose.ui.layout.ContentScale
17 | import androidx.compose.ui.res.painterResource
18 | import androidx.compose.ui.unit.dp
19 | import coil.compose.AsyncImage
20 | import com.codandotv.streamplayerapp.core_shared_ui.widget.PlayerComponent
21 | import com.codandotv.streamplayerapp.feature.list.streams.R
22 | import com.codandotv.streamplayerapp.feature_list_streams.detail.presentation.screens.DetailStreamsUIState.DetailStreamsLoadedUIState
23 |
24 | @Suppress("MagicNumber")
25 | @Composable
26 | fun DetailStreamImagePreview(
27 | uiState: DetailStreamsLoadedUIState,
28 | modifier: Modifier = Modifier,
29 | showPlayer: Boolean = false,
30 | onPlayEvent: (() -> Unit)
31 | ) {
32 | Box(
33 | modifier = modifier
34 | .fillMaxWidth()
35 | .aspectRatio(16f / 9f),
36 | contentAlignment = Alignment.Center
37 | ) {
38 | if (showPlayer) {
39 | PlayerComponent(
40 | videoId = uiState.videoId ?: ""
41 | )
42 | } else {
43 | AsyncImage(
44 | model = uiState.detailStream.url,
45 | contentScale = ContentScale.FillBounds,
46 | contentDescription = uiState.detailStream.tagline,
47 | modifier = Modifier
48 | .fillMaxWidth()
49 | .fillMaxHeight()
50 | )
51 |
52 | Box(
53 | modifier = Modifier
54 | .background(Color.Black.copy(alpha = 0.5f), CircleShape)
55 | .size(50.dp)
56 | .align(Alignment.Center),
57 | )
58 | Icon(
59 | painter = painterResource(id = R.drawable.play_circle),
60 | tint = Color.White,
61 | contentDescription = null,
62 | modifier = Modifier
63 | .size(64.dp)
64 | .align(Alignment.Center)
65 | .clickable {
66 | onPlayEvent()
67 | }
68 | )
69 | }
70 | }
71 | }
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/detail/presentation/widget/DetailStreamRowHeader.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.detail.presentation.widget
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.foundation.layout.Row
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.foundation.layout.offset
7 | import androidx.compose.foundation.layout.size
8 | import androidx.compose.material3.MaterialTheme
9 | import androidx.compose.material3.Text
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.graphics.Color
14 | import androidx.compose.ui.res.painterResource
15 | import androidx.compose.ui.res.stringResource
16 | import androidx.compose.ui.text.font.FontWeight
17 | import androidx.compose.ui.unit.dp
18 | import androidx.compose.ui.unit.em
19 | import androidx.compose.ui.unit.sp
20 | import com.codandotv.streamplayerapp.feature.list.streams.R
21 |
22 | @Composable
23 | fun DetailStreamRowHeader(
24 | modifier: Modifier = Modifier.fillMaxWidth()
25 | ) {
26 | Row(
27 | verticalAlignment = Alignment.CenterVertically,
28 | modifier = modifier
29 | ) {
30 | Image(
31 | painter = painterResource(id = R.drawable.netflix_detail),
32 | contentDescription = null,
33 | modifier = Modifier
34 | .size(26.dp)
35 | .offset(x = (-6).dp)
36 |
37 | )
38 | Text(
39 | text = stringResource(id = R.string.detail_movie),
40 | modifier = Modifier.offset(x = (-6).dp),
41 | style = MaterialTheme.typography.headlineMedium.copy(
42 | color = Color.Gray,
43 | fontWeight = FontWeight.Bold,
44 | fontSize = 14.sp,
45 | letterSpacing = 0.3.em
46 | )
47 | )
48 | }
49 | }
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/detail/presentation/widget/DetailStreamToolbar.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.detail.presentation.widget
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.foundation.layout.height
5 | import androidx.compose.material.icons.Icons
6 | import androidx.compose.material.icons.filled.ArrowBack
7 | import androidx.compose.material.icons.filled.Search
8 | import androidx.compose.material3.ExperimentalMaterial3Api
9 | import androidx.compose.material3.Icon
10 | import androidx.compose.material3.IconButton
11 | import androidx.compose.material3.Text
12 | import androidx.compose.material3.TopAppBar
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.graphics.Color
16 | import androidx.compose.ui.res.painterResource
17 | import androidx.compose.ui.res.stringResource
18 | import androidx.compose.ui.unit.dp
19 | import androidx.navigation.NavController
20 | import com.codandotv.streamplayerapp.feature.list.streams.R
21 |
22 | @OptIn(ExperimentalMaterial3Api::class)
23 | @Composable
24 | fun DetailStreamToolbar(
25 | navController: NavController,
26 | onNavigateSearchScreen: () -> Unit = {}
27 | ) {
28 | TopAppBar(
29 | title = { Text(text = "") },
30 | modifier = Modifier.height(56.dp),
31 | navigationIcon = {
32 | IconButton(onClick = { navController.navigateUp() }) {
33 | Icon(
34 | imageVector = Icons.Filled.ArrowBack,
35 | contentDescription = stringResource(id = R.string.detail_back)
36 | )
37 | }
38 | }, actions = {
39 | IconButton(onClick = {
40 | onNavigateSearchScreen.invoke()
41 | }) {
42 | Icon(
43 | imageVector = Icons.Default.Search,
44 | tint = Color.White,
45 | contentDescription = stringResource(id = R.string.detail_search)
46 | )
47 | }
48 | IconButton(onClick = { }) {
49 | Image(
50 | painter = painterResource(id = com.codandotv.streamplayerapp.core.shared.ui.R.drawable.perfil_fake),
51 | contentDescription = null
52 | )
53 | }
54 | })
55 | }
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/list/data/ListStreamRepository.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.list.data
2 |
3 | import androidx.paging.Pager
4 | import androidx.paging.PagingConfig
5 | import androidx.paging.PagingData
6 | import com.codandotv.streamplayerapp.core_networking.handleError.toFlow
7 | import com.codandotv.streamplayerapp.feature_list_streams.list.domain.model.Genre
8 | import com.codandotv.streamplayerapp.feature_list_streams.list.domain.model.Stream
9 | import com.codandotv.streamplayerapp.feature_list_streams.list.domain.toGenres
10 | import com.codandotv.streamplayerapp.feature_list_streams.list.domain.toStream
11 | import kotlinx.coroutines.flow.Flow
12 | import kotlinx.coroutines.flow.map
13 | import org.koin.core.annotation.Factory
14 |
15 | interface ListStreamRepository {
16 | suspend fun getGenres(): Flow>
17 |
18 | suspend fun topRatedStream(): Flow
19 |
20 | fun loadMovies(genre: Genre): Flow>
21 | }
22 |
23 | @Factory
24 | class ListStreamRepositoryImpl(
25 | private val service: ListStreamService,
26 | ) : ListStreamRepository {
27 |
28 | override suspend fun getGenres(): Flow> {
29 | return service.getGenres().toFlow().map { it.toGenres() }
30 | }
31 |
32 | override suspend fun topRatedStream() = service.getTopRatedMovies().toFlow().map {
33 | it.results.first { it.poster_path != null }.toStream()
34 | }
35 |
36 | override fun loadMovies(genre: Genre): Flow> {
37 | return Pager(
38 | config = PagingConfig(
39 | pageSize = PAGE_SIZE,
40 | maxSize = MAX_SIZE,
41 | ),
42 | pagingSourceFactory = {
43 | StreamDataSource(service, genreName = genre.name, genreId = genre.id)
44 | },
45 | initialKey = 1
46 | ).flow
47 | }
48 |
49 | companion object {
50 | private const val PAGE_SIZE = 20
51 | private const val MAX_SIZE = 500
52 | }
53 | }
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/list/data/ListStreamService.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.list.data
2 |
3 | import com.codandotv.streamplayerapp.core_networking.handleError.NetworkResponse
4 | import com.codandotv.streamplayerapp.feature_list_streams.list.data.model.GenresResponse
5 | import com.codandotv.streamplayerapp.feature_list_streams.list.data.model.ListStreamResponse
6 | import com.codandotv.streamplayerapp.feature_list_streams.list.data.model.StreamResponse
7 | import retrofit2.http.GET
8 | import retrofit2.http.Query
9 |
10 | interface ListStreamService {
11 | @GET("discover/movie")
12 | suspend fun getMovies(@Query("with_genres") genres: String) : NetworkResponse
13 |
14 | @GET("discover/movie")
15 | suspend fun getPaginatedMovies(
16 | @Query("with_genres") genres: String,
17 | @Query("page") page: Int
18 | ) : NetworkResponse
19 |
20 | @GET("genre/movie/list")
21 | suspend fun getGenres(): NetworkResponse
22 |
23 | @GET("discover/movie")
24 | suspend fun getTopRatedMovies(
25 | @Query("sort_by") sortBy: String = "vote_average.desc",
26 | @Query("page") page: Int = 1
27 | ): NetworkResponse
28 | }
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/list/data/StreamDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.list.data
2 |
3 | import androidx.paging.PagingSource
4 | import androidx.paging.PagingState
5 | import com.codandotv.streamplayerapp.core_networking.handleError.NetworkResponse
6 | import com.codandotv.streamplayerapp.feature_list_streams.list.domain.model.Stream
7 | import com.codandotv.streamplayerapp.feature_list_streams.list.domain.toListStream
8 |
9 | @Suppress("TooGenericExceptionCaught", "UseCheckOrError")
10 | class StreamDataSource(
11 | private val service: ListStreamService,
12 | private val genreId: Long,
13 | private val genreName: String,
14 | ) : PagingSource() {
15 |
16 | override suspend fun load(params: LoadParams): LoadResult {
17 | val nextPageNumber = params.key ?: START_PAGE_INDEX
18 |
19 | return try {
20 | val response = service.getPaginatedMovies(
21 | genres = genreId.toString(),
22 | page = nextPageNumber
23 | )
24 |
25 | if (response is NetworkResponse.Success) {
26 | LoadResult.Page(
27 | data = response.value.toListStream(genreName).streams,
28 | prevKey = if (nextPageNumber > 1) nextPageNumber - 1 else null,
29 | nextKey = nextPageNumber.plus(1)
30 | )
31 | } else {
32 | throw IllegalStateException("Something wrong")
33 | }
34 | } catch (exception: Exception) {
35 | LoadResult.Error(exception)
36 | }
37 | }
38 |
39 | override fun getRefreshKey(state: PagingState): Int? = null
40 |
41 | companion object {
42 | private const val START_PAGE_INDEX = 1
43 | }
44 | }
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/list/data/model/GenresResponse.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.list.data.model
2 |
3 | data class GenreResponse(
4 | val id: Long,
5 | val name: String
6 | )
7 |
8 | data class GenresResponse(
9 | val genres: List
10 | )
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/list/data/model/ListStreamResponse.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.list.data.model
2 |
3 | @Suppress("ConstructorParameterNaming")
4 | data class StreamResponse(
5 | val id : String,
6 | val title : String,
7 | val overview : String,
8 | val poster_path: String? = null,
9 | )
10 | data class ListStreamResponse(
11 | val results: List
12 | )
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/list/di/ListStreamModule.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.list.di
2 |
3 | import com.codandotv.streamplayerapp.feature_list_streams.list.data.ListStreamService
4 | import org.koin.core.annotation.ComponentScan
5 | import org.koin.core.annotation.Factory
6 | import org.koin.core.annotation.Module
7 | import org.koin.core.context.GlobalContext
8 | import retrofit2.Retrofit
9 |
10 | @Module
11 | @ComponentScan("com.codandotv.streamplayerapp.feature_list_streams.list")
12 | class ListStreamModule {
13 |
14 | @Factory
15 | fun service(): ListStreamService {
16 | val koin = GlobalContext.get()
17 | val retrofit = koin.get()
18 | return retrofit.create(ListStreamService::class.java)
19 | }
20 | }
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/list/domain/GetGenresUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.list.domain
2 |
3 | import com.codandotv.streamplayerapp.feature_list_streams.list.data.ListStreamRepository
4 | import com.codandotv.streamplayerapp.feature_list_streams.list.domain.model.Genre
5 | import kotlinx.coroutines.flow.Flow
6 | import org.koin.core.annotation.Factory
7 |
8 | interface GetGenresUseCase {
9 | suspend operator fun invoke(): Flow>
10 | }
11 |
12 | @Factory
13 | class GetGenresUseCaseImpl(
14 | private val repository: ListStreamRepository
15 | ) : GetGenresUseCase {
16 | override suspend fun invoke(): Flow> {
17 | return repository.getGenres()
18 | }
19 | }
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/list/domain/GetLatestMovieUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.list.domain
2 |
3 | import com.codandotv.streamplayerapp.feature_list_streams.list.data.ListStreamRepository
4 | import com.codandotv.streamplayerapp.feature_list_streams.list.domain.model.Stream
5 | import kotlinx.coroutines.flow.Flow
6 | import org.koin.core.annotation.Factory
7 |
8 | interface GetTopRatedStream {
9 | suspend operator fun invoke(): Flow
10 | }
11 |
12 | @Factory
13 | class GetTopRatedStreamImpl(
14 | private val repository: ListStreamRepository
15 | ) : GetTopRatedStream {
16 | override suspend operator fun invoke(): Flow {
17 | return repository.topRatedStream()
18 | }
19 | }
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/list/domain/ListMovieUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.list.domain
2 |
3 | import androidx.paging.PagingData
4 | import com.codandotv.streamplayerapp.feature_list_streams.list.data.ListStreamRepository
5 | import com.codandotv.streamplayerapp.feature_list_streams.list.domain.model.Genre
6 | import com.codandotv.streamplayerapp.feature_list_streams.list.domain.model.Stream
7 | import kotlinx.coroutines.flow.Flow
8 | import org.koin.core.annotation.Factory
9 |
10 | interface ListStreamUseCase {
11 | operator fun invoke(genre: Genre): Flow>
12 | }
13 |
14 | @Factory
15 | class ListStreamUseCaseImpl(
16 | private val repository: ListStreamRepository
17 | ) : ListStreamUseCase {
18 | override operator fun invoke(genre: Genre): Flow> {
19 | return repository.loadMovies(genre)
20 | }
21 | }
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/list/domain/ListStreamAnalytics.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.list.domain
2 |
3 | import org.koin.core.annotation.Factory
4 |
5 | interface ListStreamAnalytics
6 |
7 | @Factory
8 | class ListStreamAnalyticsImpl : ListStreamAnalytics
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/list/domain/ListStreamMapper.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.list.domain
2 |
3 | import com.codandotv.streamplayerapp.core_networking.Url
4 | import com.codandotv.streamplayerapp.feature_list_streams.list.data.model.GenresResponse
5 | import com.codandotv.streamplayerapp.feature_list_streams.list.data.model.ListStreamResponse
6 | import com.codandotv.streamplayerapp.feature_list_streams.list.data.model.StreamResponse
7 | import com.codandotv.streamplayerapp.feature_list_streams.list.domain.model.Genre
8 | import com.codandotv.streamplayerapp.feature_list_streams.list.domain.model.ListStream
9 | import com.codandotv.streamplayerapp.feature_list_streams.list.domain.model.Stream
10 |
11 | fun ListStreamResponse.toListStream(genre: String): ListStream =
12 | ListStream(
13 | categoryName = genre,
14 | streams = this.results.map { streamResponse ->
15 | streamResponse.toStream()
16 | }
17 | )
18 |
19 | fun GenresResponse.toGenres(): List = this.genres.map { genreResponse ->
20 | Genre(id = genreResponse.id, name = genreResponse.name)
21 | }
22 |
23 | fun StreamResponse.toStream(): Stream = Stream(
24 | description = overview,
25 | name = title,
26 | posterPathUrl = "${Url.IMAGE_URL_SIZE_300}${poster_path}",
27 | id = id
28 | )
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/list/domain/model/Genre.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.list.domain.model
2 |
3 | data class Genre(
4 | val id: Long,
5 | val name: String
6 | )
7 |
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/list/domain/model/HighlightBanner.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.list.domain.model
2 |
3 | import android.os.Parcelable
4 | import androidx.annotation.DrawableRes
5 | import androidx.annotation.StringRes
6 | import kotlinx.parcelize.Parcelize
7 |
8 | @Parcelize
9 | data class HighlightBanner(
10 | val name: String,
11 | val imageUrl: String,
12 | val contentType: Int,
13 | val contentTypeAsPlural: Int,
14 | val extraInfo: IconAndTextInfo,
15 | val leftButton: IconAndTextInfo,
16 | val centralButton: IconAndTextInfo,
17 | val rightButton: IconAndTextInfo
18 | ) : Parcelable
19 |
20 | @Parcelize
21 | data class IconAndTextInfo(
22 | @DrawableRes val icon: Int,
23 | @StringRes val text: Int
24 | ) : Parcelable
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/list/domain/model/ListStream.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.list.domain.model
2 |
3 | data class Stream(
4 | val id : String,
5 | val name : String,
6 | val description : String,
7 | val posterPathUrl: String,
8 | )
9 | data class ListStream(
10 | val categoryName: String,
11 | val streams: List
12 | )
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/list/presentation/navigation/ListStreamsNavigation.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(KoinExperimentalAPI::class)
2 |
3 | package com.codandotv.streamplayerapp.feature_list_streams.list.presentation.navigation
4 |
5 | import androidx.activity.compose.BackHandler
6 | import androidx.navigation.NavGraphBuilder
7 | import androidx.navigation.NavHostController
8 | import androidx.navigation.compose.composable
9 | import com.codandotv.streamplayerapp.core_navigation.routes.BottomNavRoutes.HOME_COMPLETE
10 | import com.codandotv.streamplayerapp.core_navigation.routes.BottomNavRoutes.PARAM.PROFILE_ID
11 | import com.codandotv.streamplayerapp.core_navigation.routes.Routes
12 | import com.codandotv.streamplayerapp.core_navigation.routes.Routes.DETAIL
13 | import com.codandotv.streamplayerapp.core_navigation.routes.Routes.PROFILE_PICKER
14 | import com.codandotv.streamplayerapp.feature_list_streams.list.di.ListStreamModule
15 | import com.codandotv.streamplayerapp.feature_list_streams.list.presentation.screens.ListStreamsScreen
16 | import org.koin.compose.module.rememberKoinModules
17 | import org.koin.core.annotation.KoinExperimentalAPI
18 | import org.koin.ksp.generated.module
19 |
20 | internal const val DEFAULT_ID = ""
21 |
22 | fun NavGraphBuilder.listStreamsNavGraph(navController: NavHostController) {
23 | composable(HOME_COMPLETE) { nav ->
24 | BackHandler(true) {}
25 | rememberKoinModules {
26 | listOf(ListStreamModule().module)
27 | }
28 | ListStreamsScreen(navController = navController,
29 | onNavigateDetailList = { id ->
30 | navController.navigate("${DETAIL}${id}")
31 | },
32 | onNavigateProfilePicker = {
33 | navController.navigate(PROFILE_PICKER)
34 | },
35 | onNavigateSearchScreen = {
36 | navController.navigate(Routes.SEARCH)
37 | },
38 | profilePicture = nav.arguments?.getString(PROFILE_ID) ?: DEFAULT_ID
39 | )
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/list/presentation/screens/ListStreamsUIState.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.list.presentation.screens
2 |
3 | import com.codandotv.streamplayerapp.feature_list_streams.list.domain.model.HighlightBanner
4 | import com.codandotv.streamplayerapp.feature_list_streams.list.presentation.widgets.StreamsCarouselContent
5 |
6 | data class ListStreamsUIState(
7 | val highlightBanner: HighlightBanner? = null,
8 | val streamsCarouselContent: List,
9 | val isLoading: Boolean
10 | )
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/list/presentation/widgets/StreamsCard.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.list.presentation.widgets
2 |
3 | import android.os.Parcelable
4 | import androidx.compose.foundation.clickable
5 | import androidx.compose.foundation.layout.fillMaxSize
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.foundation.layout.size
8 | import androidx.compose.foundation.shape.RoundedCornerShape
9 | import androidx.compose.material3.Card
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.layout.ContentScale
13 | import androidx.compose.ui.tooling.preview.Preview
14 | import androidx.compose.ui.unit.dp
15 | import coil.compose.AsyncImage
16 | import com.codandotv.streamplayerapp.core_networking.Url.IMAGE_URL_SIZE_300
17 | import kotlinx.parcelize.Parcelize
18 |
19 | @Composable
20 | fun StreamsCard(
21 | content: StreamsCardContent,
22 | onNavigateDetailList: (String) -> Unit = {},
23 | ) {
24 | Card(
25 | shape = RoundedCornerShape(6.dp),
26 | modifier = Modifier
27 | .size(
28 | width = 100.dp,
29 | height = 140.dp
30 | )
31 | .padding(
32 | horizontal = 4.dp
33 | )
34 | .clickable {
35 | onNavigateDetailList.invoke(content.id)
36 | }
37 | ) {
38 | AsyncImage(
39 | model = content.url,
40 | modifier = Modifier.fillMaxSize(),
41 | contentScale = ContentScale.FillBounds,
42 | contentDescription = content.contentDescription
43 | )
44 | }
45 | }
46 |
47 | @Parcelize
48 | data class StreamsCardContent(
49 | val id: String,
50 | val url: String,
51 | val contentDescription: String,
52 | ) : Parcelable
53 |
54 | @Preview
55 | @Composable
56 | fun StreamsCardPreview() {
57 | StreamsCard(
58 | StreamsCardContent(
59 | url = "${IMAGE_URL_SIZE_300}evgwd37VHBJhXvSr88Mrx5riFil.jpg",
60 | contentDescription = "Test 1",
61 | id = "",
62 | )
63 | )
64 | }
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/list/presentation/widgets/StreamsCarousel.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.list.presentation.widgets
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.Spacer
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.foundation.layout.height
7 | import androidx.compose.foundation.layout.size
8 | import androidx.compose.foundation.lazy.LazyRow
9 | import androidx.compose.foundation.lazy.rememberLazyListState
10 | import androidx.compose.material3.MaterialTheme
11 | import androidx.compose.material3.Text
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.text.font.FontWeight
15 | import androidx.compose.ui.tooling.preview.Preview
16 | import androidx.compose.ui.unit.dp
17 | import androidx.compose.ui.unit.sp
18 | import androidx.paging.PagingData
19 | import androidx.paging.compose.collectAsLazyPagingItems
20 | import androidx.paging.compose.itemContentType
21 | import androidx.paging.compose.itemKey
22 | import kotlinx.coroutines.flow.Flow
23 | import kotlinx.coroutines.flow.emptyFlow
24 |
25 | @Composable
26 | fun StreamsCarousel(
27 | content: StreamsCarouselContent,
28 | modifier: Modifier = Modifier,
29 | onNavigateDetailList: (String) -> Unit = {},
30 | ) {
31 | val lazyPagingItems = content.contentList.collectAsLazyPagingItems()
32 | val lazyListState = rememberLazyListState()
33 |
34 | Column(modifier = modifier) {
35 | Text(
36 | content.genreTitle,
37 | style = MaterialTheme.typography.headlineMedium.copy(
38 | fontWeight = FontWeight.Bold,
39 | fontSize = 20.sp
40 | )
41 | )
42 |
43 | Spacer(modifier = Modifier.size(8.dp))
44 |
45 | LazyRow(
46 | state = lazyListState,
47 | modifier = Modifier
48 | .fillMaxWidth()
49 | .height(140.dp)
50 | ) {
51 | items(
52 | count = lazyPagingItems.itemCount,
53 | key = lazyPagingItems.itemKey(),
54 | contentType = lazyPagingItems.itemContentType()
55 | ) { index ->
56 | val item = lazyPagingItems[index]
57 | item?.let {
58 | StreamsCard(
59 | content = it,
60 | onNavigateDetailList
61 | )
62 | }
63 | }
64 | }
65 | }
66 | }
67 |
68 | data class StreamsCarouselContent(
69 | val genreTitle: String,
70 | val contentList: Flow>
71 | )
72 |
73 | @Composable
74 | @Preview
75 | fun StreamsCarouselPreview() {
76 | StreamsCarousel(
77 | content = StreamsCarouselContent(
78 | genreTitle = "Ação",
79 | contentList = emptyFlow()
80 | )
81 | )
82 | }
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/search/data/api/MostPopularMoviesService.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.search.data.api
2 |
3 | import com.codandotv.streamplayerapp.core_networking.handleError.NetworkResponse
4 | import com.codandotv.streamplayerapp.feature_list_streams.search.data.model.ListSearchStreamResponse
5 | import retrofit2.http.GET
6 |
7 | interface MostPopularMoviesService {
8 | @GET("movie/popular")
9 | suspend fun getPopular(): NetworkResponse
10 | }
11 |
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/search/data/api/SearchStreamService.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.search.data.api
2 |
3 | import com.codandotv.streamplayerapp.core_networking.handleError.NetworkResponse
4 | import com.codandotv.streamplayerapp.feature_list_streams.search.data.model.ListSearchStreamResponse
5 | import retrofit2.http.GET
6 | import retrofit2.http.Query
7 |
8 | interface SearchStreamService {
9 | @GET("search/movie")
10 | suspend fun getSearch(@Query("query") query: String) : NetworkResponse
11 | }
12 |
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/search/data/datasource/MostPopularMoviesDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.search.data.datasource
2 |
3 | import com.codandotv.streamplayerapp.core_networking.handleError.toFlow
4 | import com.codandotv.streamplayerapp.feature_list_streams.search.data.model.ListSearchStreamResponse
5 | import com.codandotv.streamplayerapp.feature_list_streams.search.data.api.MostPopularMoviesService
6 | import kotlinx.coroutines.flow.Flow
7 |
8 | interface MostPopularMoviesDataSource {
9 | suspend fun getMostPopularMovies(): Flow
10 | }
11 |
12 | class MostPopularMoviesDataSourceImpl(
13 | private val service: MostPopularMoviesService
14 | ) : MostPopularMoviesDataSource {
15 |
16 | override suspend fun getMostPopularMovies(): Flow =
17 | service.getPopular().toFlow()
18 | }
19 |
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/search/data/datasource/SearchStreamDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.search.data.datasource
2 |
3 | import com.codandotv.streamplayerapp.core_networking.handleError.toFlow
4 | import com.codandotv.streamplayerapp.feature_list_streams.search.data.model.ListSearchStreamResponse
5 | import com.codandotv.streamplayerapp.feature_list_streams.search.data.api.SearchStreamService
6 | import kotlinx.coroutines.flow.Flow
7 |
8 | interface SearchStreamDataSource {
9 | suspend fun getMovieSearch(query: String): Flow
10 | }
11 | class SearchStreamDataSourceImpl(
12 | private val service: SearchStreamService
13 | ): SearchStreamDataSource {
14 |
15 | override suspend fun getMovieSearch(query:String): Flow =
16 | service.getSearch(query = query).toFlow()
17 | }
18 |
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/search/data/model/ListSearchStreamResponse.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.search.data.model
2 |
3 | import com.squareup.moshi.Json
4 |
5 | data class ListSearchStreamResponse(
6 | @Json(name = "results")
7 | val results: List
8 | ) {
9 | data class SearchStreamResponse(
10 | @Json(name = "id")
11 | val id: String,
12 | @Json(name = "title")
13 | val title: String,
14 | @Json(name="overview")
15 | val overview: String,
16 | @Json(name = "poster_path")
17 | val posterPath: String,
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/search/data/repository/MostPopularMoviesRepository.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.search.data.repository
2 |
3 | import com.codandotv.streamplayerapp.feature_list_streams.search.data.model.ListSearchStreamResponse
4 | import com.codandotv.streamplayerapp.feature_list_streams.search.data.datasource.MostPopularMoviesDataSource
5 | import kotlinx.coroutines.flow.Flow
6 |
7 | interface MostPopularMoviesRepository {
8 | suspend fun getMostPopularMovies(): Flow
9 | }
10 |
11 | class MostPopularMoviesRepositoryImpl(
12 | private val dataSource: MostPopularMoviesDataSource
13 | ) : MostPopularMoviesRepository {
14 | override suspend fun getMostPopularMovies(): Flow =
15 | dataSource.getMostPopularMovies()
16 | }
17 |
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/search/data/repository/SearchStreamRepository.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.search.data.repository
2 |
3 | import com.codandotv.streamplayerapp.feature_list_streams.search.data.datasource.SearchStreamDataSource
4 | import com.codandotv.streamplayerapp.feature_list_streams.search.data.model.ListSearchStreamResponse
5 | import kotlinx.coroutines.flow.Flow
6 |
7 | interface SearchStreamRepository {
8 | suspend fun getMovieSearch(query: String) : Flow
9 |
10 | }
11 | class SearchStreamRepositoryImp(
12 | private val dataSource: SearchStreamDataSource
13 | ) : SearchStreamRepository {
14 | override suspend fun getMovieSearch(query: String): Flow =
15 | dataSource.getMovieSearch(query)
16 | }
17 |
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/search/di/SearchModule.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.search.di
2 |
3 | import com.codandotv.streamplayerapp.feature_list_streams.search.data.api.SearchStreamService
4 | import com.codandotv.streamplayerapp.feature_list_streams.search.data.datasource.SearchStreamDataSourceImpl
5 | import com.codandotv.streamplayerapp.feature_list_streams.search.data.datasource.MostPopularMoviesDataSource
6 | import com.codandotv.streamplayerapp.feature_list_streams.search.data.datasource.MostPopularMoviesDataSourceImpl
7 | import com.codandotv.streamplayerapp.feature_list_streams.search.data.repository.MostPopularMoviesRepository
8 | import com.codandotv.streamplayerapp.feature_list_streams.search.data.repository.MostPopularMoviesRepositoryImpl
9 | import com.codandotv.streamplayerapp.feature_list_streams.search.data.api.MostPopularMoviesService
10 | import com.codandotv.streamplayerapp.feature_list_streams.search.data.datasource.SearchStreamDataSource
11 | import com.codandotv.streamplayerapp.feature_list_streams.search.data.repository.SearchStreamRepository
12 | import com.codandotv.streamplayerapp.feature_list_streams.search.data.repository.SearchStreamRepositoryImp
13 | import com.codandotv.streamplayerapp.feature_list_streams.search.domain.MostPopularMoviesUseCase
14 | import com.codandotv.streamplayerapp.feature_list_streams.search.domain.MostPopularMoviesUseCaseImpl
15 | import com.codandotv.streamplayerapp.feature_list_streams.search.domain.SearchUseCase
16 | import com.codandotv.streamplayerapp.feature_list_streams.search.domain.SearchUseCaseImpl
17 | import com.codandotv.streamplayerapp.feature_list_streams.search.presentation.screens.SearchViewModel
18 | import org.koin.androidx.viewmodel.dsl.viewModel
19 | import org.koin.dsl.module
20 | import retrofit2.Retrofit
21 |
22 | object SearchModule {
23 | val module = module {
24 | viewModel {
25 | SearchViewModel(
26 | searchUseCase = get(),
27 | mostPopularMoviesUseCase = get()
28 | )
29 | }
30 |
31 | factory { get().create(SearchStreamService::class.java) }
32 | factory { get().create(MostPopularMoviesService::class.java) }
33 |
34 | factory { MostPopularMoviesUseCaseImpl(repository = get()) }
35 | factory { MostPopularMoviesDataSourceImpl(service = get()) }
36 | factory { MostPopularMoviesRepositoryImpl(dataSource = get()) }
37 |
38 | factory { SearchUseCaseImpl(repository = get()) }
39 | factory { SearchStreamDataSourceImpl(service = get()) }
40 | factory { SearchStreamRepositoryImp(dataSource = get()) }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/search/domain/MostPopularMoviesUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.search.domain
2 |
3 | import com.codandotv.streamplayerapp.feature_list_streams.search.data.model.ListSearchStreamResponse
4 | import com.codandotv.streamplayerapp.feature_list_streams.search.data.repository.MostPopularMoviesRepository
5 | import kotlinx.coroutines.flow.Flow
6 |
7 | interface MostPopularMoviesUseCase {
8 | suspend operator fun invoke(): Flow
9 | }
10 |
11 | class MostPopularMoviesUseCaseImpl(
12 | val repository: MostPopularMoviesRepository
13 | ) : MostPopularMoviesUseCase {
14 | override suspend operator fun invoke(): Flow {
15 | return repository.getMostPopularMovies()
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/search/domain/SearchUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.search.domain
2 |
3 | import com.codandotv.streamplayerapp.feature_list_streams.search.data.model.ListSearchStreamResponse
4 | import com.codandotv.streamplayerapp.feature_list_streams.search.data.repository.SearchStreamRepository
5 | import kotlinx.coroutines.flow.Flow
6 |
7 | interface SearchUseCase {
8 | suspend operator fun invoke(query:String): Flow
9 | }
10 |
11 | class SearchUseCaseImpl(val repository: SearchStreamRepository) : SearchUseCase {
12 | override suspend operator fun invoke(query:String): Flow {
13 | return repository.getMovieSearch(query = query)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/search/domain/mapper/SearchMapper.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.search.domain.mapper
2 |
3 | import com.codandotv.streamplayerapp.core_networking.Url
4 | import com.codandotv.streamplayerapp.feature_list_streams.search.data.model.ListSearchStreamResponse.SearchStreamResponse
5 | import com.codandotv.streamplayerapp.feature_list_streams.search.presentation.widgets.SearchStreamCardModel
6 |
7 | fun SearchStreamResponse.toSearchStreamCardModel() = SearchStreamCardModel(
8 | id = id,
9 | title = title,
10 | url = "${Url.IMAGE_URL_SIZE_200}${posterPath}"
11 | )
12 |
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/search/presentation/navigation/SearchStreamNavigation.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(KoinExperimentalAPI::class)
2 |
3 | package com.codandotv.streamplayerapp.feature_list_streams.search.presentation.navigation
4 |
5 | import androidx.activity.compose.BackHandler
6 | import androidx.navigation.NavGraphBuilder
7 | import androidx.navigation.NavHostController
8 | import androidx.navigation.compose.composable
9 | import com.codandotv.streamplayerapp.core_navigation.routes.Routes
10 | import com.codandotv.streamplayerapp.feature_list_streams.search.di.SearchModule
11 | import com.codandotv.streamplayerapp.feature_list_streams.search.presentation.screens.SearchScreen
12 | import org.koin.compose.module.rememberKoinModules
13 | import org.koin.core.annotation.KoinExperimentalAPI
14 |
15 | fun NavGraphBuilder.searchStreamsNavGraph(navController: NavHostController) {
16 | composable(Routes.SEARCH) { nav ->
17 | BackHandler(true) {}
18 | rememberKoinModules{
19 | listOf(SearchModule.module)
20 | }
21 | SearchScreen(
22 | navController = navController,
23 | onNavigateDetailList = { id ->
24 | navController.navigate("${Routes.DETAIL}${id}")
25 | },
26 | )
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/feature-list-streams/src/main/java/com/codandotv/streamplayerapp/feature_list_streams/search/presentation/screens/SearchUIState.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.search.presentation.screens
2 |
3 | import com.codandotv.streamplayerapp.feature_list_streams.search.data.model.ListSearchStreamResponse
4 |
5 | sealed class SearchUIState {
6 | data class Success(val listCharacters: ListSearchStreamResponse) : SearchUIState()
7 | data class Error(val messageError: String = String()) : SearchUIState()
8 | object Loading : SearchUIState()
9 | object Empty : SearchUIState()
10 | }
11 |
--------------------------------------------------------------------------------
/feature-list-streams/src/main/res/drawable/ic_top_10.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodandoTV/Netflix-Android/7dc3eee526a58135ae2001293c50a42cf648a308/feature-list-streams/src/main/res/drawable/ic_top_10.webp
--------------------------------------------------------------------------------
/feature-list-streams/src/main/res/drawable/image_placeholder.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
--------------------------------------------------------------------------------
/feature-list-streams/src/main/res/drawable/netflix_detail.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodandoTV/Netflix-Android/7dc3eee526a58135ae2001293c50a42cf648a308/feature-list-streams/src/main/res/drawable/netflix_detail.webp
--------------------------------------------------------------------------------
/feature-list-streams/src/main/res/drawable/netflix_horizontal_logo.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/feature-list-streams/src/main/res/drawable/play_circle.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/feature-list-streams/src/main/res/layout/activity_list_stream.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
--------------------------------------------------------------------------------
/feature-list-streams/src/main/res/values/content-description.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Ícone Reprodução
4 | Ícone adicionar
5 | Ícone informações
6 | Poster de conteúdo em destaque
7 | Ícone top 10
8 |
--------------------------------------------------------------------------------
/feature-list-streams/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Assistir
4 | Baixar E1
5 | Filme
6 | Minha Lista
7 | Classificar
8 | Compartilhar
9 | Baixar completo
10 | Pesquisar
11 | Voltar
12 |
13 |
14 | Série
15 | Séries
16 | Filme
17 | Filmes
18 |
19 | Minha lista
20 | Saiba mais
21 | Assistir
22 | Top 1 em %s hoje
23 |
24 |
25 |
26 | Principais buscas
27 | Principais buscas
28 |
29 |
30 | Houve um problema ao conectar à Netflix. Tente novamente mais tarde.
31 | Tente novamente
32 | Conteúdo não encontrado
33 |
34 |
--------------------------------------------------------------------------------
/feature-list-streams/src/test/java/com/codandotv/streamplayerapp/feature_list_streams/detail/DetailStreamRepositoryTest.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.detail
2 |
3 | import com.codandotv.streamplayerapp.core_local_storage.data.dao.FavoriteDao
4 | import com.codandotv.streamplayerapp.core_networking.handleError.NetworkResponse
5 | import com.codandotv.streamplayerapp.feature_list_streams.detail.data.DetailStreamRepository
6 | import com.codandotv.streamplayerapp.feature_list_streams.detail.data.DetailStreamRepositoryImpl
7 | import com.codandotv.streamplayerapp.feature_list_streams.detail.data.DetailStreamService
8 | import io.mockk.coEvery
9 | import io.mockk.coVerifyOrder
10 | import io.mockk.mockk
11 | import kotlinx.coroutines.flow.collectLatest
12 | import kotlinx.coroutines.test.runTest
13 | import org.junit.Before
14 | import org.junit.Test
15 |
16 | class DetailStreamRepositoryTest {
17 | private lateinit var repository: DetailStreamRepository
18 | private val movieId = MOVIE_ID_STRING
19 | private lateinit var service: DetailStreamService
20 | private lateinit var favoriteDao: FavoriteDao
21 |
22 | @Before
23 | fun setUp() {
24 | service = mockk()
25 | favoriteDao = mockk()
26 | repository = DetailStreamRepositoryImpl(
27 | movieId = movieId,
28 | service = service,
29 | favoriteDao = favoriteDao
30 | )
31 | }
32 |
33 | @Test
34 | fun `getmovie should load the movies when passed a movieId`() {
35 | runTest {
36 | coEvery { service.getMovie(movieId) } returns NetworkResponse.Success(
37 | detailStreamResponse
38 | )
39 | coEvery { favoriteDao.fetchAll() } returns emptyList()
40 |
41 | repository.getMovie()
42 | .collectLatest {
43 | it == detailStream
44 | }
45 |
46 | coVerifyOrder {
47 | service.getMovie(movieId)
48 | favoriteDao.fetchAll()
49 | }
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/feature-list-streams/src/test/java/com/codandotv/streamplayerapp/feature_list_streams/detail/DetailStreamUseCaseTest.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.detail
2 |
3 | import com.codandotv.streamplayerapp.feature_list_streams.detail.data.DetailStreamRepository
4 | import com.codandotv.streamplayerapp.feature_list_streams.detail.domain.DetailStream
5 | import com.codandotv.streamplayerapp.feature_list_streams.detail.domain.DetailStreamUseCase
6 | import com.codandotv.streamplayerapp.feature_list_streams.detail.domain.DetailStreamUseCaseImpl
7 | import io.mockk.coEvery
8 | import io.mockk.coVerify
9 | import io.mockk.mockk
10 | import kotlinx.coroutines.flow.collect
11 | import kotlinx.coroutines.flow.flowOf
12 | import kotlinx.coroutines.runBlocking
13 | import kotlinx.coroutines.test.runTest
14 | import org.junit.Before
15 | import org.junit.Test
16 | import kotlin.test.assertTrue
17 |
18 | class DetailStreamUseCaseTest {
19 | private lateinit var detailStreamUseCase: DetailStreamUseCase
20 | private lateinit var detailStreamRepository: DetailStreamRepository
21 |
22 | @Before
23 | fun setUp() {
24 | detailStreamRepository = mockk()
25 | detailStreamUseCase = DetailStreamUseCaseImpl(
26 | detailStreamRepository = detailStreamRepository
27 | )
28 | }
29 |
30 | @Test
31 | fun `load movies`() {
32 | runTest {
33 | coEvery { detailStreamRepository.getMovie() } returns flowOf(detailStream)
34 |
35 | detailStreamUseCase.getMovie()
36 | .collect{
37 | assertTrue {
38 | it == detailStream
39 | }
40 | }
41 |
42 | coVerify{
43 | detailStreamRepository.getMovie()
44 | }
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/feature-list-streams/src/test/java/com/codandotv/streamplayerapp/feature_list_streams/detail/DetailStreamViewModelTest.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.detail
2 |
3 | import androidx.arch.core.executor.testing.InstantTaskExecutorRule
4 | import androidx.lifecycle.LifecycleOwner
5 | import com.codandotv.streamplayerapp.feature_list_streams.detail.domain.DetailStreamUseCase
6 | import com.codandotv.streamplayerapp.feature_list_streams.detail.domain.VideoStreamsUseCase
7 | import com.codandotv.streamplayerapp.feature_list_streams.detail.presentation.screens.DetailStreamViewModel
8 | import com.codandotv.streamplayerapp.feature_list_streams.detail.presentation.screens.DetailStreamsUIState
9 | import io.mockk.coEvery
10 | import io.mockk.coVerify
11 | import io.mockk.mockk
12 | import kotlinx.coroutines.flow.flowOf
13 | import kotlinx.coroutines.test.runTest
14 | import org.junit.Before
15 | import org.junit.Rule
16 | import org.junit.Test
17 | import kotlin.test.assertTrue
18 |
19 | class DetailStreamViewModelTest {
20 | private lateinit var detailStreamViewModel: DetailStreamViewModel
21 | private lateinit var detailUseCase: DetailStreamUseCase
22 | private lateinit var videoUseCase: VideoStreamsUseCase
23 |
24 | @get:Rule
25 | val rule = InstantTaskExecutorRule()
26 |
27 | @get:Rule
28 | var executorRule = InstantTaskCoroutinesExecutorRule()
29 |
30 | @Before
31 | fun setUp() {
32 | detailUseCase = mockk()
33 | videoUseCase = mockk()
34 |
35 | detailStreamViewModel = DetailStreamViewModel(
36 | detailStreamUseCase = detailUseCase,
37 | videoStreamsUseCase = videoUseCase,
38 | dispatcher = executorRule.dispatcher
39 | )
40 | }
41 |
42 | @Test
43 | fun `should load the movies with videoId`() {
44 | runTest {
45 | coEvery { detailUseCase.getMovie() } returns flowOf(detailStream)
46 | coEvery { videoUseCase.getVideoStreams() } returns flowOf(videosStreamsList)
47 |
48 | detailStreamViewModel.loadDetail()
49 |
50 | coVerify {
51 | detailStreamViewModel.uiState.value.let {
52 | DetailStreamsUIState.LoadingStreamUIState
53 | }
54 | detailUseCase.getMovie()
55 | detailStreamViewModel.uiState.value.let {
56 | assertTrue {
57 | it == DetailStreamsUIState.DetailStreamsLoadedUIState(
58 | detailStream = detailStream, videoId = videosStreamsList.first().videoId
59 | )
60 | }
61 | }
62 | }
63 | }
64 | }
65 |
66 | }
--------------------------------------------------------------------------------
/feature-list-streams/src/test/java/com/codandotv/streamplayerapp/feature_list_streams/detail/InstantTaskCoroutinesExecutorRule.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.detail
2 |
3 | import kotlinx.coroutines.Dispatchers
4 | import kotlinx.coroutines.ExperimentalCoroutinesApi
5 | import kotlinx.coroutines.test.UnconfinedTestDispatcher
6 | import kotlinx.coroutines.test.resetMain
7 | import kotlinx.coroutines.test.setMain
8 | import org.junit.rules.TestWatcher
9 | import org.junit.runner.Description
10 |
11 | @OptIn(ExperimentalCoroutinesApi::class)
12 | class InstantTaskCoroutinesExecutorRule : TestWatcher() {
13 | val dispatcher = UnconfinedTestDispatcher()
14 |
15 | override fun starting(description: Description) {
16 | super.starting(description)
17 | Dispatchers.setMain(dispatcher)
18 | }
19 |
20 | override fun finished(description: Description) {
21 | super.finished(description)
22 | Dispatchers.resetMain()
23 | }
24 | }
--------------------------------------------------------------------------------
/feature-list-streams/src/test/java/com/codandotv/streamplayerapp/feature_list_streams/detail/Shared.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_list_streams.detail
2 |
3 | import com.codandotv.streamplayerapp.feature_list_streams.detail.data.model.DetailStreamResponse
4 | import com.codandotv.streamplayerapp.feature_list_streams.detail.domain.DetailStream
5 | import com.codandotv.streamplayerapp.feature_list_streams.detail.domain.VideoStream
6 |
7 | val videoStream = VideoStream(
8 | movieId = 123,
9 | videoId = "123"
10 | )
11 |
12 | const val MOVIE_ID_STRING = "123"
13 |
14 | val videoStream1 = VideoStream(
15 | movieId = 1234565,
16 | videoId = "123565"
17 | )
18 |
19 | val videosStreamsList = listOf(
20 | videoStream,
21 | videoStream1
22 | )
23 |
24 | val detailStreamResponse = DetailStreamResponse(
25 | id = "id",
26 | title = "title",
27 | overview = "overview",
28 | tagline = "tagline",
29 | backdrop_path = "backdrop",
30 | release_date = "release"
31 | )
32 |
33 | val detailStream = DetailStream(
34 | id = "id",
35 | title = "title",
36 | overview = "overview",
37 | tagline = "tagline",
38 | isFavorite = false,
39 | releaseYear = "backdrop",
40 | url = "palmeiras"
41 | )
--------------------------------------------------------------------------------
/feature-profile/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/feature-profile/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 |
3 | plugins {
4 | id("com.streamplayer.android-library")
5 | id("com.streamplayer.compose")
6 | alias(libs.plugins.ksp)
7 | }
8 |
9 | dependencies {
10 | implementation(projects.coreNetworking)
11 | implementation(projects.coreNavigation)
12 | implementation(projects.coreShared)
13 | implementation(projects.coreSharedUi)
14 |
15 | implementation(libs.bundles.koin)
16 | implementation(libs.koin.annotations)
17 | ksp(libs.koin.compiler)
18 |
19 | implementation(libs.bundles.networking)
20 | implementation(libs.bundles.androidSupport)
21 | implementation(libs.coil)
22 |
23 | testImplementation(libs.bundles.test)
24 | }
25 |
26 | ksp {
27 | arg("KOIN_CONFIG_CHECK","true")
28 | }
29 |
--------------------------------------------------------------------------------
/feature-profile/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodandoTV/Netflix-Android/7dc3eee526a58135ae2001293c50a42cf648a308/feature-profile/consumer-rules.pro
--------------------------------------------------------------------------------
/feature-profile/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/feature-profile/src/main/java/com/codandotv/streamplayerapp/feature_profile/profile/data/ProfilePickerStreamRepository.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_profile.profile.data
2 |
3 | import android.util.Log
4 | import com.codandotv.streamplayerapp.core_networking.handleError.toFlow
5 | import com.codandotv.streamplayerapp.core_networking.handleError.toResult
6 | import com.codandotv.streamplayerapp.feature_profile.profile.domain.ProfileStream
7 | import com.codandotv.streamplayerapp.feature_profile.profile.domain.toProfiles
8 | import kotlinx.coroutines.flow.Flow
9 | import kotlinx.coroutines.flow.flowOf
10 | import kotlinx.coroutines.flow.map
11 | import org.koin.core.annotation.Factory
12 |
13 | interface ProfilePickerStreamRepository {
14 | suspend fun getProfiles(): Flow>
15 | }
16 |
17 | @Factory
18 | class ProfilePickerStreamRepositoryImpl(
19 | private val service: ProfilePickerStreamService
20 | ) : ProfilePickerStreamRepository {
21 |
22 | override suspend fun getProfiles(): Flow> {
23 |
24 | with(service.getProfiles()) {
25 | if (this.toResult().isFailure || this.toResult().getOrNull() == null) {
26 | Log.i("ProfilePickerStreamRepositoryImpl", "versão off carregada")
27 | return flowOf(mockProfiles)
28 | } else {
29 | return this.toFlow().map { it.toProfiles() }
30 | }
31 | }
32 | }
33 | }
34 |
35 |
36 | private val mockProfiles = listOf(
37 | ProfileStream(
38 | id = "1",
39 | name = "Chapei de Palha",
40 | imageUrl = "https://raw.githubusercontent.com/git-jr/sample-files/main/profile%20pics/netflix_profile_pic_1.png"
41 | ),
42 | ProfileStream(
43 | id = "2",
44 | name = "Martha",
45 | imageUrl = "https://raw.githubusercontent.com/git-jr/sample-files/main/profile%20pics/netflix_profile_pic_2.png"
46 | ),
47 | ProfileStream(
48 | id = "3",
49 | name = "Morningstar",
50 | imageUrl = "https://raw.githubusercontent.com/git-jr/sample-files/main/profile%20pics/netflix_profile_pic_3.png"
51 | ),
52 | ProfileStream(
53 | id = "4",
54 | name = "Shelby",
55 | imageUrl = "https://raw.githubusercontent.com/git-jr/sample-files/main/profile%20pics/netflix_profile_pic_4.png"
56 | ),
57 | ProfileStream(
58 | id = "5",
59 | name = "Mooncake",
60 | imageUrl = "https://raw.githubusercontent.com/git-jr/sample-files/main/profile%20pics/netflix_profile_pic_5.png"
61 | ),
62 | ProfileStream(
63 | id = "6",
64 | name = "CodandoTV",
65 | imageUrl = "https://raw.githubusercontent.com/git-jr/sample-files/main/profile%20pics/netflix_profile_pic_6.png"
66 | )
67 | )
68 |
--------------------------------------------------------------------------------
/feature-profile/src/main/java/com/codandotv/streamplayerapp/feature_profile/profile/data/ProfilePickerStreamService.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_profile.profile.data
2 |
3 | import com.codandotv.streamplayerapp.core_networking.handleError.NetworkResponse
4 | import com.codandotv.streamplayerapp.feature_profile.profile.data.model.ProfilesResponse
5 | import retrofit2.http.GET
6 |
7 | interface ProfilePickerStreamService {
8 | @GET("profiles")
9 | suspend fun getProfiles(): NetworkResponse
10 | }
--------------------------------------------------------------------------------
/feature-profile/src/main/java/com/codandotv/streamplayerapp/feature_profile/profile/data/model/ProfileStreamResponse.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_profile.profile.data.model
2 |
3 | @Suppress("ConstructorParameterNaming")
4 | data class ProfileStreamResponse(
5 | val id: String,
6 | val name: String,
7 | val profile_url: String,
8 | )
9 |
10 | data class ProfilesResponse(
11 | val profiles: List
12 | )
--------------------------------------------------------------------------------
/feature-profile/src/main/java/com/codandotv/streamplayerapp/feature_profile/profile/di/ProfilePickerStreamModule.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_profile.profile.di
2 |
3 | import com.codandotv.streamplayerapp.core_networking.di.QualifierProfileRetrofit
4 | import com.codandotv.streamplayerapp.feature_profile.profile.data.ProfilePickerStreamService
5 | import org.koin.core.annotation.ComponentScan
6 | import org.koin.core.annotation.Factory
7 | import org.koin.core.annotation.Module
8 | import org.koin.core.context.GlobalContext
9 | import retrofit2.Retrofit
10 |
11 | @Module
12 | @ComponentScan("com.codandotv.streamplayerapp.feature_profile")
13 | class ProfilePickerStreamModule {
14 |
15 | @Factory
16 | fun service(): ProfilePickerStreamService {
17 | val koin = GlobalContext.get()
18 | val retrofit = koin.get(QualifierProfileRetrofit)
19 | return retrofit.create(ProfilePickerStreamService::class.java)
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/feature-profile/src/main/java/com/codandotv/streamplayerapp/feature_profile/profile/domain/ProfilePickerStreamMapper.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_profile.profile.domain
2 |
3 | import com.codandotv.streamplayerapp.feature_profile.profile.data.model.ProfilesResponse
4 |
5 | fun ProfilesResponse.toProfiles(): List = this.profiles.map { profileResponse ->
6 | ProfileStream(
7 | id = profileResponse.id,
8 | name = profileResponse.name,
9 | imageUrl = profileResponse.profile_url,
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/feature-profile/src/main/java/com/codandotv/streamplayerapp/feature_profile/profile/domain/ProfilePickerStreamUseCase.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("MagicNumber")
2 | package com.codandotv.streamplayerapp.feature_profile.profile.domain
3 |
4 | import com.codandotv.streamplayerapp.feature_profile.profile.data.ProfilePickerStreamRepository
5 | import kotlinx.coroutines.flow.Flow
6 | import org.koin.core.annotation.Factory
7 |
8 | interface ProfilePickerStreamUseCase {
9 | suspend fun getProfile(): Flow>
10 | fun getListOffsetProfiles(
11 | haltSizeImage: Int,
12 | oneThirdOfWidthScreen: Int,
13 | oneQuarterOfHeightScreen: Int
14 | ): List>
15 |
16 | fun calculateOneThirdOfWidthScreen(widthPx: Int): Int
17 | fun calculateOneQuarterOfHeightScreen(heightPx: Int): Int
18 | fun calculateCenterScreen(
19 | oneThirdOfWidthScreen: Int,
20 | halfExpandedSizeImage: Int,
21 | oneQuarterOfHeightScreen: Int
22 | ): Pair
23 | }
24 |
25 | @Factory
26 | class ProfilePickerStreamUseCaseImpl(
27 | private val profilePickerStreamRepository: ProfilePickerStreamRepository
28 | ) : ProfilePickerStreamUseCase {
29 |
30 | override suspend fun getProfile(): Flow> =
31 | profilePickerStreamRepository.getProfiles()
32 |
33 | override fun getListOffsetProfiles(
34 | haltSizeImage: Int,
35 | oneThirdOfWidthScreen: Int,
36 | oneQuarterOfHeightScreen: Int
37 | ): List> {
38 |
39 | return listOf(
40 | Pair(oneThirdOfWidthScreen - haltSizeImage, 0),
41 | Pair(oneThirdOfWidthScreen * 2 - haltSizeImage, 0),
42 | Pair(oneThirdOfWidthScreen - haltSizeImage, oneQuarterOfHeightScreen),
43 | Pair(
44 | oneThirdOfWidthScreen * 2 - haltSizeImage,
45 | oneQuarterOfHeightScreen
46 | ),
47 | Pair(
48 | oneThirdOfWidthScreen - haltSizeImage,
49 | oneQuarterOfHeightScreen * 2
50 | ),
51 | Pair(
52 | oneThirdOfWidthScreen * 2 - haltSizeImage,
53 | oneQuarterOfHeightScreen * 2
54 | ),
55 | )
56 | }
57 |
58 | override fun calculateOneThirdOfWidthScreen(widthPx: Int) = widthPx / 3
59 |
60 | override fun calculateOneQuarterOfHeightScreen(heightPx: Int) = heightPx / 4
61 |
62 | override fun calculateCenterScreen(
63 | oneThirdOfWidthScreen: Int,
64 | halfExpandedSizeImage: Int,
65 | oneQuarterOfHeightScreen: Int
66 | ): Pair {
67 | return Pair(
68 | oneThirdOfWidthScreen + oneThirdOfWidthScreen / 2 - halfExpandedSizeImage,
69 | oneQuarterOfHeightScreen
70 | )
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/feature-profile/src/main/java/com/codandotv/streamplayerapp/feature_profile/profile/domain/ProfileStream.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_profile.profile.domain
2 |
3 | data class ProfileStream(
4 | val id: String,
5 | val name: String,
6 | val imageUrl: String
7 | )
--------------------------------------------------------------------------------
/feature-profile/src/main/java/com/codandotv/streamplayerapp/feature_profile/profile/presentation/navigation/ProfilePickerStreamNavigation.kt:
--------------------------------------------------------------------------------
1 | @file:OptIn(KoinExperimentalAPI::class)
2 |
3 | package com.codandotv.streamplayerapp.feature_profile.profile.presentation.navigation
4 |
5 | import androidx.navigation.NavGraphBuilder
6 | import androidx.navigation.NavHostController
7 | import androidx.navigation.compose.composable
8 | import com.codandotv.streamplayerapp.core_navigation.routes.BottomNavRoutes.HOME
9 | import com.codandotv.streamplayerapp.core_navigation.routes.BottomNavRoutes.PARAM.PROFILE_ID
10 | import com.codandotv.streamplayerapp.core_navigation.routes.Routes
11 | import com.codandotv.streamplayerapp.feature_profile.profile.di.ProfilePickerStreamModule
12 | import com.codandotv.streamplayerapp.feature_profile.profile.presentation.screens.ProfilePickerStreamScreen
13 | import org.koin.compose.module.rememberKoinModules
14 | import org.koin.core.annotation.KoinExperimentalAPI
15 | import org.koin.ksp.generated.module
16 |
17 | fun NavGraphBuilder.profilePickerStreamNavGraph(navController: NavHostController) {
18 | composable(Routes.PROFILE_PICKER) { nav ->
19 | rememberKoinModules {
20 | listOf(ProfilePickerStreamModule().module)
21 | }
22 |
23 | ProfilePickerStreamScreen(
24 | onNavigateListStreams = { profilePic ->
25 | navController.navigate("$HOME?$PROFILE_ID=$profilePic")
26 | }
27 | )
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/feature-profile/src/main/java/com/codandotv/streamplayerapp/feature_profile/profile/presentation/screens/ProfilePickerStreamsUIState.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_profile.profile.presentation.screens
2 |
3 | import com.codandotv.streamplayerapp.feature_profile.profile.domain.ProfileStream
4 |
5 |
6 | data class ProfilePickerStreamsUIState(
7 | val profilesStream: List = emptyList(),
8 | val selectedItem: ProfileStream? = null,
9 | val isLoading: Boolean = true,
10 | val showCenterImage: Boolean = false,
11 | val centerImageAlpha: Float = 1f,
12 | val canMoveImageToCenter: Boolean = false,
13 | val expandImage: Boolean = false,
14 |
15 | val expandedImageSize: Float = 150f,
16 | val defaultImageSize: Float = 110f,
17 | val haltSizeImage: Int = 0,
18 | val halfExpandedSizeImage: Int = 0,
19 | val screenWidth: Float = 0f,
20 | val screenHeight: Float = 0f,
21 | val oneQuarterOfHeightScreen: Int = 0,
22 | val oneThirdOfWidthScreen: Int = 0,
23 |
24 | val lastItemPositioned: Boolean = false,
25 | val offsetProfiles: List> = emptyList(),
26 | val selectedImageAlpha: Float = 1f,
27 | val centerScreen: Pair = Pair(0, 0),
28 | )
29 |
30 |
--------------------------------------------------------------------------------
/feature-profile/src/main/java/com/codandotv/streamplayerapp/feature_profile/profile/presentation/widget/ComposeExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_profile.profile.presentation.widget
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.platform.LocalDensity
5 | import androidx.compose.ui.unit.Dp
6 |
7 | @Composable
8 | fun Dp.dpToPx(): Int = with(LocalDensity.current) { this@dpToPx.roundToPx() }
--------------------------------------------------------------------------------
/feature-profile/src/main/java/com/codandotv/streamplayerapp/feature_profile/profile/presentation/widget/ProfilePickerOpacityLayer.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_profile.profile.presentation.widget
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.fillMaxSize
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.graphics.Color
9 |
10 | @Composable
11 | fun ProfilePickerOpacityLayer(animatedBackground: Color) {
12 | Box(
13 | Modifier
14 | .fillMaxSize()
15 | .background(animatedBackground)
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/feature-profile/src/main/java/com/codandotv/streamplayerapp/feature_profile/profile/presentation/widget/ProfilePickerSelectedProfileContainer.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_profile.profile.presentation.widget
2 |
3 | import androidx.compose.foundation.layout.BoxWithConstraints
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.fillMaxSize
6 | import androidx.compose.foundation.layout.offset
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.foundation.layout.size
9 | import androidx.compose.foundation.shape.RoundedCornerShape
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.draw.alpha
14 | import androidx.compose.ui.draw.clip
15 | import androidx.compose.ui.platform.LocalContext
16 | import androidx.compose.ui.res.painterResource
17 | import androidx.compose.ui.res.stringResource
18 | import androidx.compose.ui.unit.Dp
19 | import androidx.compose.ui.unit.IntOffset
20 | import androidx.compose.ui.unit.dp
21 | import coil.compose.AsyncImage
22 | import coil.request.ImageRequest
23 | import com.codandotv.streamplayerapp.feature.profile.R
24 | import com.codandotv.streamplayerapp.feature_profile.profile.presentation.screens.ProfilePickerStreamsUIState
25 |
26 | @Suppress("MagicNumber")
27 | @Composable
28 | fun ProfilePickerSelectedProfileContainer(
29 | state: ProfilePickerStreamsUIState,
30 | offsetSelectedProfileImage: IntOffset,
31 | animatedSizeImage: Dp
32 | ) {
33 | with(state) {
34 | BoxWithConstraints(
35 | Modifier
36 | .fillMaxSize()
37 | .padding(top = 100.dp)
38 | ) {
39 | if (showCenterImage) {
40 | Column(
41 | horizontalAlignment = Alignment.CenterHorizontally,
42 | modifier = Modifier
43 | .offset { offsetSelectedProfileImage }
44 | ) {
45 | AsyncImage(
46 | model = ImageRequest.Builder(LocalContext.current)
47 | .data(selectedItem?.imageUrl)
48 | .crossfade(true)
49 | .build(),
50 | placeholder = painterResource(id = R.drawable.image_placeholder),
51 | contentDescription = selectedItem?.let {
52 | stringResource(
53 | id = R.string.profile_current_profile_name,
54 | it.name
55 | )
56 | },
57 | modifier = Modifier
58 | .clip(RoundedCornerShape(5))
59 | .size(animatedSizeImage)
60 | .alpha(centerImageAlpha)
61 | )
62 | }
63 | }
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/feature-profile/src/main/java/com/codandotv/streamplayerapp/feature_profile/profile/presentation/widget/ProfilePickerStreamLoad.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_profile.profile.presentation.widget
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.fillMaxSize
5 | import androidx.compose.material3.CircularProgressIndicator
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Alignment
8 | import androidx.compose.ui.Modifier
9 |
10 |
11 | @Composable
12 | fun LoadScreen() {
13 | Box(Modifier.fillMaxSize()) {
14 | CircularProgressIndicator(
15 | modifier = Modifier.align(
16 | Alignment.Center
17 | )
18 | )
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/feature-profile/src/main/java/com/codandotv/streamplayerapp/feature_profile/profile/presentation/widget/ProfilePickerStreamToolbar.kt:
--------------------------------------------------------------------------------
1 | package com.codandotv.streamplayerapp.feature_profile.profile.presentation.widget
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.foundation.layout.Arrangement
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.Row
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.foundation.layout.height
9 | import androidx.compose.material.icons.Icons
10 | import androidx.compose.material.icons.filled.Edit
11 | import androidx.compose.material3.Icon
12 | import androidx.compose.material3.IconButton
13 | import androidx.compose.material3.MaterialTheme
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.res.painterResource
18 | import androidx.compose.ui.unit.dp
19 | import com.codandotv.streamplayerapp.core_shared_ui.theme.ThemePreviews
20 | import com.codandotv.streamplayerapp.feature.profile.R
21 |
22 | @Composable
23 | fun ProfilePickerStreamToolbar(modifier: Modifier = Modifier) {
24 |
25 | Box(
26 | modifier
27 | .fillMaxWidth()
28 | .height(56.dp),
29 | contentAlignment = Alignment.CenterEnd
30 | ) {
31 | Row(
32 | Modifier.fillMaxWidth(),
33 | verticalAlignment = Alignment.CenterVertically,
34 | horizontalArrangement = Arrangement.Center
35 | ) {
36 | Image(
37 | painter = painterResource(id = R.drawable.netflix_horizontal_logo),
38 | contentDescription = null,
39 | modifier = Modifier
40 | .height(28.dp)
41 | )
42 | }
43 |
44 | IconButton(onClick = { }) {
45 | Icon(
46 | Icons.Default.Edit,
47 | contentDescription = null,
48 | tint = MaterialTheme.colorScheme.onBackground
49 | )
50 | }
51 | }
52 |
53 | Box(
54 | modifier
55 | .fillMaxWidth()
56 | .height(56.dp)
57 | )
58 | }
59 |
60 |
61 | @ThemePreviews
62 | @Composable
63 | fun ProfilePickerStreamToolbarPreview() {
64 | MaterialTheme {
65 | ProfilePickerStreamToolbar()
66 | }
67 | }
--------------------------------------------------------------------------------
/feature-profile/src/main/res/drawable/image_placeholder.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
--------------------------------------------------------------------------------
/feature-profile/src/main/res/drawable/netflix_horizontal_logo.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/feature-profile/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Fundo da tela escurecendo
4 | Imagem de perfil selecionada aumentando de tamanho
5 | Imagem de perfil selecionada se movendo para o centro
6 | Mostrando todos os perfis
7 | Imagem de perfil de %s
8 |
--------------------------------------------------------------------------------
/file_readme/codandotv.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodandoTV/Netflix-Android/7dc3eee526a58135ae2001293c50a42cf648a308/file_readme/codandotv.png
--------------------------------------------------------------------------------
/file_readme/splash_list_detail.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodandoTV/Netflix-Android/7dc3eee526a58135ae2001293c50a42cf648a308/file_readme/splash_list_detail.gif
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx6g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC -XX:MaxMetaspaceSize=1g
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
24 | android.defaults.buildfeatures.buildconfig=true
25 | android.nonFinalResIds=false
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodandoTV/Netflix-Android/7dc3eee526a58135ae2001293c50a42cf648a308/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo.
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
48 | echo.
49 | echo Please set the JAVA_HOME variable in your environment to match the
50 | echo location of your Java installation.
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo.
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
62 | echo.
63 | echo Please set the JAVA_HOME variable in your environment to match the
64 | echo location of your Java installation.
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 | pluginManagement {
3 | includeBuild("build-logic")
4 | repositories {
5 | gradlePluginPortal()
6 | google()
7 | mavenCentral()
8 | }
9 | }
10 |
11 | dependencyResolutionManagement {
12 | repositories {
13 | google()
14 | mavenCentral()
15 | maven { setUrl("https://jitpack.io") }
16 | maven(url = uri("https://oss.sonatype.org/content/repositories/snapshots/"))
17 | }
18 | }
19 |
20 |
21 | include(":app")
22 | include(":feature-list-streams")
23 | include(":core-shared")
24 | include(":core-networking")
25 | include(":core-shared-ui")
26 | include(":core-navigation")
27 | include(":feature-profile")
28 | include(":core-local-storage")
29 | include(":feature-favorites")
30 |
31 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
--------------------------------------------------------------------------------