Skip to content

Commit

Permalink
Merge pull request #55 from Divinelink/feature/deeplinks
Browse files Browse the repository at this point in the history
[DeepLinks] Introduce Deeplink Mechanism for Movie/TV Details
  • Loading branch information
Divinelink authored Jul 12, 2024
2 parents d6adeaa + 9a4b059 commit 2f17b69
Show file tree
Hide file tree
Showing 39 changed files with 654 additions and 111 deletions.
2 changes: 1 addition & 1 deletion app/src/debug/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">MovieRama DEBUG</string>

</resources>
12 changes: 11 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
android:name=".Application"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:label="@string/core_commons_app_name"
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/Theme.MovieRama"
Expand All @@ -23,6 +23,16 @@

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data
android:host="www.themoviedb.org"
android:scheme="https" />
</intent-filter>
</activity>

<provider
Expand Down
20 changes: 15 additions & 5 deletions app/src/main/kotlin/com/andreolas/movierama/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.andreolas.movierama

import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
Expand All @@ -19,12 +20,19 @@ class MainActivity : ComponentActivity() {

private val viewModel: MainViewModel by viewModels()

override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
if (intent != null && intent.action == Intent.ACTION_VIEW) {
viewModel.handleDeepLink(intent.data?.toString())
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setContent {
val darkTheme = shouldUseDarkTheme(
uiState = viewModel.viewState.collectAsState().value,
uiState = viewModel.uiState.collectAsState().value,
selectedTheme = viewModel.theme.collectAsState().value,
)

Expand All @@ -34,7 +42,9 @@ class MainActivity : ComponentActivity() {
blackBackground = viewModel.blackBackgrounds.collectAsState().value,
) {
MovieApp(
uiState = viewModel.viewState.collectAsState().value,
uiState = viewModel.uiState.collectAsState().value,
uiEvent = viewModel.uiEvent.collectAsState().value,
onConsumeEvent = viewModel::consumeUiEvent,
)
}
}
Expand All @@ -43,11 +53,11 @@ class MainActivity : ComponentActivity() {

@Composable
private fun shouldUseDarkTheme(
uiState: MainViewState,
uiState: MainUiState,
selectedTheme: Theme,
): Boolean = when (uiState) {
is MainViewState.Loading -> isSystemInDarkTheme()
is MainViewState.Completed -> when (selectedTheme) {
is MainUiState.Loading -> isSystemInDarkTheme()
is MainUiState.Completed -> when (selectedTheme) {
Theme.SYSTEM -> isSystemInDarkTheme()
Theme.LIGHT -> false
Theme.DARK -> true
Expand Down
8 changes: 8 additions & 0 deletions app/src/main/kotlin/com/andreolas/movierama/MainUiEvent.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.andreolas.movierama

import com.divinelink.feature.details.ui.DetailsNavArguments

sealed interface MainUiEvent {
data object None : MainUiEvent
data class NavigateToDetails(val navArgs: DetailsNavArguments) : MainUiEvent
}
8 changes: 8 additions & 0 deletions app/src/main/kotlin/com/andreolas/movierama/MainUiState.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.andreolas.movierama

sealed interface MainUiState {

data object Loading : MainUiState

data object Completed : MainUiState
}
83 changes: 57 additions & 26 deletions app/src/main/kotlin/com/andreolas/movierama/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package com.andreolas.movierama

import androidx.lifecycle.ViewModel
import com.andreolas.movierama.ui.ThemedActivityDelegate
import com.divinelink.core.commons.extensions.extractDetailsFromDeepLink
import com.divinelink.core.model.media.MediaType
import com.divinelink.feature.details.ui.DetailsNavArguments
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
Expand All @@ -12,38 +15,66 @@ class MainViewModel @Inject constructor(themedActivityDelegate: ThemedActivityDe
ViewModel(),
ThemedActivityDelegate by themedActivityDelegate {

private val _viewState: MutableStateFlow<MainViewState> =
MutableStateFlow(MainViewState.Completed)
val viewState: StateFlow<MainViewState> = _viewState
private val _uiState: MutableStateFlow<MainUiState> =
MutableStateFlow(MainUiState.Completed)
val uiState: StateFlow<MainUiState> = _uiState

private val _uiEvent: MutableStateFlow<MainUiEvent> = MutableStateFlow(MainUiEvent.None)
val uiEvent: StateFlow<MainUiEvent> = _uiEvent

private fun updateUiEvent(event: MainUiEvent) {
_uiEvent.value = event
}

fun consumeUiEvent() {
_uiEvent.value = MainUiEvent.None
}

fun handleDeepLink(url: String?) {
val (id, mediaType) = url.extractDetailsFromDeepLink() ?: return

if (MediaType.from(mediaType) != MediaType.UNKNOWN) {
updateUiEvent(
MainUiEvent.NavigateToDetails(
DetailsNavArguments(
id = id,
mediaType = mediaType,
isFavorite = false,
),
),
)
} else {
updateUiEvent(MainUiEvent.None)
}
}

/**
* Activate remote config once Main Activity starts.
* This is crucial since we can fetch data from remote config and then update our UI
* once we're ready.
*/
/*
init {
setRemoteConfig()
}
/**
init {
setRemoteConfig()
}
fun retryFetchRemoteConfig() {
setRemoteConfig()
}
private fun setRemoteConfig() {
_viewState.value = MainViewState.Loading
viewModelScope.launch {
val result = setRemoteConfigUseCase.invoke(Unit)
if (result.isSuccess) {
_viewState.value = MainViewState.Completed
} else {
_viewState.value = MainViewState.Error(
UIText.StringText("Something went wrong. Trying again...")
)
}
}
}
fun retryFetchRemoteConfig() {
setRemoteConfig()
}
private fun setRemoteConfig() {
_uiState.value = MainViewState.Loading
viewModelScope.launch {
val result = setRemoteConfigUseCase.invoke(Unit)
if (result.isSuccess) {
_uiState.value = MainViewState.Completed
} else {
_uiState.value = MainViewState.Error(
UIText.StringText("Something went wrong. Trying again..."),
)
}
}
}
*/
}
8 changes: 0 additions & 8 deletions app/src/main/kotlin/com/andreolas/movierama/MainViewState.kt

This file was deleted.

31 changes: 25 additions & 6 deletions app/src/main/kotlin/com/andreolas/movierama/ui/MovieApp.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.andreolas.movierama.ui

import android.annotation.SuppressLint
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Icon
Expand All @@ -11,6 +10,7 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
Expand All @@ -21,24 +21,43 @@ import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavHostController
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.andreolas.movierama.MainViewState
import com.andreolas.movierama.MainUiEvent
import com.andreolas.movierama.MainUiState
import com.andreolas.movierama.navigation.AppNavHost
import com.andreolas.movierama.navigation.TopLevelDestination
import com.divinelink.core.ui.components.LoadingContent
import com.divinelink.core.ui.snackbar.controller.ProvideSnackbarController
import com.divinelink.feature.details.screens.destinations.DetailsScreenDestination
import com.divinelink.ui.screens.destinations.HomeScreenDestination
import com.ramcosta.composedestinations.utils.navGraph
import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator

@Composable
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
fun MovieApp(uiState: MainViewState) {
fun MovieApp(
uiState: MainUiState,
uiEvent: MainUiEvent,
onConsumeEvent: () -> Unit,
) {
val navController: NavHostController = rememberNavController()
val navigator = navController.rememberDestinationsNavigator()

val snackbarHostState = remember { SnackbarHostState() }
val coroutineScope = rememberCoroutineScope()

LaunchedEffect(uiEvent) {
when (uiEvent) {
is MainUiEvent.NavigateToDetails -> {
navigator.navigate(
direction = DetailsScreenDestination(uiEvent.navArgs),
)
onConsumeEvent()
}
MainUiEvent.None -> {
// Do nothing
}
}
}

ProvideSnackbarController(
snackbarHostState = snackbarHostState,
coroutineScope = coroutineScope,
Expand Down Expand Up @@ -99,11 +118,11 @@ fun MovieApp(uiState: MainViewState) {
.padding(it),
) {
when (uiState) {
is MainViewState.Completed -> AppNavHost(
is MainUiState.Completed -> AppNavHost(
navController = navController,
startRoute = HomeScreenDestination,
)
MainViewState.Loading -> LoadingContent()
MainUiState.Loading -> LoadingContent()
}
}
}
Expand Down
2 changes: 0 additions & 2 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
<resources>
<string name="app_name">MovieRama</string>

<string name="ok">OK</string>
<string name="close">Close</string>
<string name="save">Save</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.andreolas.movierama.main.ui

import com.andreolas.movierama.MainUiEvent
import com.andreolas.movierama.MainUiState
import com.andreolas.movierama.MainViewModel
import com.andreolas.movierama.MainViewState
import com.andreolas.movierama.fakes.usecase.FakeSetRemoteConfigUseCase
import com.andreolas.movierama.test.util.fakes.FakeThemedActivityDelegate
import com.divinelink.core.testing.MainDispatcherRule
Expand All @@ -23,8 +24,20 @@ class MainViewModelRobot {
)
}

fun assertViewState(expectedViewState: MainViewState) = apply {
assertThat(viewModel.viewState.value).isEqualTo(expectedViewState)
fun onHandleDeeplink(uri: String?) = apply {
viewModel.handleDeepLink(uri)
}

fun onConsumeUiEvent() = apply {
viewModel.consumeUiEvent()
}

fun assertUiState(expectedUiState: MainUiState) = apply {
assertThat(viewModel.uiState.value).isEqualTo(expectedUiState)
}

fun assertUiEvent(expectedUiEvent: MainUiEvent) = apply {
assertThat(viewModel.uiEvent.value).isEqualTo(expectedUiEvent)
}

fun mockSetRemoteConfigResult(result: Unit) = apply {
Expand Down
Loading

0 comments on commit 2f17b69

Please sign in to comment.