├── .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") --------------------------------------------------------------------------------