Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Media 3 Exoplayer fine-tuning #50

Merged
merged 19 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
3f0c841
[chore] AndroidManifest: make MainActivity singleTask
ryanw-mobile May 3, 2024
104914d
[refactor] App: move onTriggerPIPMode lambda to AppNavHost
ryanw-mobile May 3, 2024
35b9fa7
[feature] ExoPlayerViewModel: add listener to extract video dimension
ryanw-mobile May 3, 2024
3e24da9
[feature] ExoPlayerScreen: implement full screen button
ryanw-mobile May 3, 2024
4be9a2a
[feature] ExoPlayerScreen: hide player buttons
ryanw-mobile May 3, 2024
11b1040
[refactor] ExoPlayerScreen: move player state persistence to viewmodel
ryanw-mobile May 3, 2024
36b7ef2
[refactor] ExoPlayerScreen: remove unused variable
ryanw-mobile May 3, 2024
34df385
[feature] ExoPlayerScreen: PIP button now animate together with the p…
ryanw-mobile May 3, 2024
0072cbd
[feature] ExoPlayerScreen: implement addOnLayoutChangeListener as rec…
ryanw-mobile May 3, 2024
6d50e1d
[feature] ExoPlayerScreen: change to make PIP button animate earlier
ryanw-mobile May 3, 2024
81152b6
[refactor] DAZNCodeChallengeApp: change enum class case
ryanw-mobile May 3, 2024
2229c7b
[refactor] PictureInPictureButton: extract into separate component
ryanw-mobile May 3, 2024
6abce23
[feature] ExoPlayerScreen: implement finer controller transition state
ryanw-mobile May 3, 2024
734a42e
[refactor] ControllerTransitionState: extract enum into separate clas…
ryanw-mobile May 3, 2024
6a66c2b
[refactor] ExoPlayerScreen: minor code rearrangement
ryanw-mobile May 3, 2024
35094cb
[test] ExoPlayerViewModelTest: add unit tests
ryanw-mobile May 3, 2024
20f4a21
[docs] README.md: update screenshot
ryanw-mobile May 3, 2024
e1dcf0f
[docs] screencap: update animated gif for root repository
ryanw-mobile May 3, 2024
58647a4
[chore] App: bump version to 2.3.0 build 8
ryanw-mobile May 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,20 @@ The schedule under the Schedule tab refreshes automatically every 30 seconds.
For both tabs, you can always do swipe-to-refresh, or tap the navigation icon to scroll to the top
of the list.

## Download the App

If you want to try out the app without building it, check out
the [Releases section](https://github.com/ryanw-mobile/dazn-code-challenge/releases) where you can
find the APK and App Bundles for each major release.

### Known issues

The video player has [known _refused-to-fix_ bugs](https://github.com/androidx/media/issues/1270) on
Android 14 and 15. You may see the video playback not resizing properly. As a workaround, rotating
the device, toggling between full screen or Picture-in-Picture mode can solve the issue.

 

## History

This was a code test assignment as a part of the interview process. The task covered
Expand All @@ -34,14 +48,14 @@ any time.
<p align="center">
<img src="screenshots/240424_screen0.png" width="150" />
<img src="screenshots/240424_screen1.png" width="150" />
<img src="screenshots/240424_screen2.png" width="150" />
<img src="screenshots/240503_screen2.png" width="150" />
<img src="screenshots/240501_screen4.png" width="150" />
</p>
<p align="center">
<img src="screenshots/240424_screen3.png" width="400" />
</p>

_&nbsp;
&nbsp;

## To-do lists

Expand Down Expand Up @@ -99,12 +113,6 @@ now [logged as issues](https://github.com/ryanw-mobile/dazn-code-challenge/issue
* [Github Action](https://github.com/features/actions) - CI/CD
* [codecov](https://codecov.io/) - code coverage

## Binaries download

If you want to try out the app without building it, check out
the [Releases section](https://github.com/ryanw-mobile/dazn-code-challenge/releases) where you can
find the APK and App Bundles for each major version.

## Requirements

* Android Studio Iguana | 2023.2.1
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:exported="true"
android:launchMode="singleTask"
android:resizeableActivity="true"
android:supportsPictureInPicture="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ package com.rwmobi.dazncodechallenge
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.os.Bundle
import android.util.Rational
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
Expand All @@ -29,7 +28,6 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.navigation.compose.rememberNavController
import com.rwmobi.dazncodechallenge.ui.components.DAZNCodeChallengeApp
import com.rwmobi.dazncodechallenge.ui.theme.DAZNCodeChallengeTheme
import com.rwmobi.dazncodechallenge.ui.utils.enterPIPMode
import com.rwmobi.dazncodechallenge.ui.utils.hasPictureInPicturePermission
import dagger.hilt.android.AndroidEntryPoint
import timber.log.Timber
Expand Down Expand Up @@ -61,7 +59,6 @@ class MainActivity : ComponentActivity() {
isInPictureInPictureMode = isInPipMode,
navController = navController,
snackbarHostState = snackbarHostState,
onTriggerPIPMode = { enterPIPMode(Rational(16, 9)) },
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,25 +46,25 @@ import com.rwmobi.dazncodechallenge.ui.theme.dazn_divider
import com.rwmobi.dazncodechallenge.ui.utils.getPreviewWindowSizeClass

private enum class NavigationLayoutType {
BottomNavigation,
NavigationRail,
FullScreen,
BOTTOM_NAVIGATION,
NAVIGATION_RAIL,
FULL_SCREEN,
}

private fun WindowSizeClass.calculateNavigationLayout(currentRoute: String?, isInPictureInPictureMode: Boolean): NavigationLayoutType {
if (currentRoute?.startsWith(AppNavItem.Exoplayer.screenRoute) == true || isInPictureInPictureMode) {
return NavigationLayoutType.FullScreen
return NavigationLayoutType.FULL_SCREEN
}

return when (widthSizeClass) {
WindowWidthSizeClass.Compact -> {
NavigationLayoutType.BottomNavigation
NavigationLayoutType.BOTTOM_NAVIGATION
}

else -> {
// WindowWidthSizeClass.Medium, -- tablet portrait
// WindowWidthSizeClass.Expanded, -- phone landscape mode
NavigationLayoutType.NavigationRail
NavigationLayoutType.NAVIGATION_RAIL
}
}
}
Expand All @@ -77,7 +77,6 @@ fun DAZNCodeChallengeApp(
windowSizeClass: WindowSizeClass,
navController: NavHostController,
snackbarHostState: SnackbarHostState,
onTriggerPIPMode: () -> Unit,
) {
val lastDoubleTappedNavItem = remember { mutableStateOf<AppNavItem?>(null) }
val navBackStackEntry by navController.currentBackStackEntryAsState()
Expand All @@ -89,7 +88,7 @@ fun DAZNCodeChallengeApp(

Row(modifier = modifier) {
AnimatedVisibility(
visible = (navigationLayoutType == NavigationLayoutType.NavigationRail),
visible = (navigationLayoutType == NavigationLayoutType.NAVIGATION_RAIL),
enter = slideInHorizontally(initialOffsetX = { -it }),
exit = shrinkHorizontally() + fadeOut(),
) {
Expand All @@ -116,7 +115,7 @@ fun DAZNCodeChallengeApp(
},
bottomBar = {
AnimatedVisibility(
visible = (navigationLayoutType == NavigationLayoutType.BottomNavigation),
visible = (navigationLayoutType == NavigationLayoutType.BOTTOM_NAVIGATION),
enter = slideInVertically(initialOffsetY = { it }),
exit = shrinkVertically() + fadeOut(),
) {
Expand Down Expand Up @@ -153,7 +152,6 @@ fun DAZNCodeChallengeApp(
}
},
onScrolledToTop = { lastDoubleTappedNavItem.value = null },
onTriggerPIPMode = onTriggerPIPMode,
)
}
}
Expand All @@ -175,7 +173,6 @@ private fun Preview() {
windowSizeClass = getPreviewWindowSizeClass(),
navController = rememberNavController(),
snackbarHostState = remember { SnackbarHostState() },
onTriggerPIPMode = {},
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2024. Ryan Wong
* https://github.com/ryanw-mobile
* Sponsored by RW MobiMedia UK Limited
*
*/

package com.rwmobi.dazncodechallenge.ui.components

import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import com.rwmobi.dazncodechallenge.R

@Composable
fun PictureInPictureButton(
modifier: Modifier = Modifier,
onClick: () -> Unit,
) {
IconButton(
modifier = modifier,
onClick = onClick,
) {
Icon(
painter = painterResource(id = R.drawable.rounded_picture_in_picture_24),
contentDescription = stringResource(R.string.content_description_play_video_in_picture_in_picture_mode),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2024. Ryan Wong
* https://github.com/ryanw-mobile
* Sponsored by RW MobiMedia UK Limited
*
*/

package com.rwmobi.dazncodechallenge.ui.destinations.exoplayer

import android.view.View

internal enum class ControllerTransitionState {
GONE,
APPEARING,
VISIBLE,
DISAPPEARING,
;

fun updateState(visibility: Int, isControllerFullyVisible: Boolean): ControllerTransitionState {
return when {
visibility == View.INVISIBLE && !isControllerFullyVisible -> GONE
visibility == View.VISIBLE && isControllerFullyVisible -> VISIBLE
visibility == View.VISIBLE && !isControllerFullyVisible -> {
when (this) {
VISIBLE -> DISAPPEARING
GONE -> APPEARING
else -> DISAPPEARING
}
}

else -> GONE
}
}
}
Loading