From 495c1bdd1e06e6b658f0b3b7ef6a964772b4b406 Mon Sep 17 00:00:00 2001 From: PavloNetrebchuk <141041606+PavloNetrebchuk@users.noreply.github.com> Date: Mon, 16 Oct 2023 16:34:24 +0300 Subject: [PATCH] Feature/landscape (#68) * Added landscape orientation, fixed UI bugs in landscape * Merge remote-tracking branch 'origin/develop' into feature/landscape_screens * Added a landscape layout to the dialog before the user leaves the edit profile screen without saving * Remembers if the video was playing before the screen was rotated or when opening/closing full screen mode and restore that state * Cutout padding * Added cutout paddings * Comment text field padding fix * Added cancel button to ChapterEndDialog * Moved NavigationUnitsButtons to top in landscape orientation * Added border to HtmlUnitFragment + minor UI fixes --------- Co-authored-by: Volodymyr Chekyrta --- app/src/main/AndroidManifest.xml | 7 +- .../main/java/org/openedx/app/AppActivity.kt | 31 ++- .../main/java/org/openedx/app/AppRouter.kt | 10 +- .../restore/RestorePasswordFragment.kt | 2 + .../presentation/signin/SignInFragment.kt | 1 + .../presentation/signup/SignUpFragment.kt | 3 +- .../org/openedx/core/extension/ViewExt.kt | 11 + .../core/presentation/global/InsetHolder.kt | 1 + .../notifier/CourseVideoPositionChanged.kt | 3 +- .../org/openedx/core/ui/ComposeExtensions.kt | 58 ++++- .../presentation/ChapterEndFragmentDialog.kt | 243 ++++++++++++++++-- .../course/presentation/CourseRouter.kt | 6 +- .../detail/CourseDetailsFragment.kt | 10 +- .../presentation/handouts/HandoutsFragment.kt | 3 +- .../presentation/handouts/WebViewFragment.kt | 11 +- .../outline/CourseOutlineFragment.kt | 3 +- .../section/CourseSectionFragment.kt | 2 + .../course/presentation/ui/CourseUI.kt | 64 ++--- .../unit/NotSupportedUnitFragment.kt | 5 + .../container/CourseUnitContainerFragment.kt | 202 ++++++++------- .../container/CourseUnitContainerViewModel.kt | 18 +- .../unit/html/HtmlUnitFragment.kt | 33 ++- .../unit/video/VideoFullScreenFragment.kt | 50 ++-- .../unit/video/VideoUnitFragment.kt | 73 ++---- .../unit/video/VideoUnitViewModel.kt | 22 +- .../presentation/unit/video/VideoViewModel.kt | 3 +- .../video/YoutubeVideoFullScreenFragment.kt | 49 ++-- .../unit/video/YoutubeVideoUnitFragment.kt | 71 ++--- .../videos/CourseVideosFragment.kt | 3 +- .../fragment_course_unit_container.xml | 75 ++++++ .../res/layout-land/fragment_video_unit.xml | 58 +++++ .../fragment_youtube_video_unit.xml | 55 ++++ .../fragment_course_unit_container.xml | 17 +- .../layout/fragment_course_unit_container.xml | 17 +- .../main/res/layout/fragment_video_unit.xml | 11 - .../layout/fragment_youtube_video_unit.xml | 14 +- course/src/main/res/values-uk/strings.xml | 1 - course/src/main/res/values/strings.xml | 1 - .../unit/video/VideoUnitViewModelTest.kt | 2 +- .../unit/video/VideoViewModelTest.kt | 4 +- .../presentation/DashboardFragment.kt | 8 +- .../presentation/DiscoveryFragment.kt | 3 +- .../comments/DiscussionCommentsFragment.kt | 13 +- .../responses/DiscussionResponsesFragment.kt | 20 +- .../threads/DiscussionAddThreadFragment.kt | 3 +- .../threads/DiscussionThreadsFragment.kt | 125 ++++----- .../topics/DiscussionTopicsFragment.kt | 3 +- .../presentation/ui/DiscussionUI.kt | 6 +- .../delete/DeleteProfileFragment.kt | 3 +- .../presentation/edit/EditProfileFragment.kt | 145 ++++++++++- .../presentation/profile/ProfileFragment.kt | 33 ++- .../settings/video/VideoQualityFragment.kt | 9 +- .../settings/video/VideoSettingsFragment.kt | 3 +- 53 files changed, 1102 insertions(+), 525 deletions(-) create mode 100644 course/src/main/res/layout-land/fragment_course_unit_container.xml create mode 100644 course/src/main/res/layout-land/fragment_video_unit.xml create mode 100644 course/src/main/res/layout-land/fragment_youtube_video_unit.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 086cf5a34..74c1b0761 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,6 +20,7 @@ android:label="@string/app_name" android:localeConfig="@xml/locales_config" android:roundIcon="@mipmap/ic_launcher_round" + android:screenOrientation="sensor" android:supportsRtl="true" android:theme="@style/Theme.App.Starting" tools:targetApi="tiramisu"> @@ -27,7 +28,6 @@ android:name=".AppActivity" android:exported="true" android:fitsSystemWindows="true" - android:screenOrientation="portrait" android:theme="@style/Theme.App.Starting" android:windowSoftInputMode="adjustPan"> @@ -47,10 +47,11 @@ android:resource="@xml/file_provider_paths" /> - + tools:node="remove" /> \ No newline at end of file diff --git a/app/src/main/java/org/openedx/app/AppActivity.kt b/app/src/main/java/org/openedx/app/AppActivity.kt index 67981f548..e171aa20b 100644 --- a/app/src/main/java/org/openedx/app/AppActivity.kt +++ b/app/src/main/java/org/openedx/app/AppActivity.kt @@ -1,6 +1,5 @@ package org.openedx.app -import android.content.pm.ActivityInfo import android.content.res.Configuration import android.graphics.Color import android.os.Bundle @@ -12,7 +11,11 @@ import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsControllerCompat import androidx.window.layout.WindowMetricsCalculator +import org.koin.android.ext.android.inject +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.openedx.app.databinding.ActivityAppBinding import org.openedx.auth.presentation.signin.SignInFragment +import org.openedx.core.data.storage.CorePreferences import org.openedx.core.extension.requestApplyInsetsWhenAttached import org.openedx.core.presentation.global.AppData import org.openedx.core.presentation.global.AppDataHolder @@ -21,10 +24,6 @@ import org.openedx.core.presentation.global.WindowSizeHolder import org.openedx.core.ui.WindowSize import org.openedx.core.ui.WindowType import org.openedx.profile.presentation.ProfileRouter -import org.koin.android.ext.android.inject -import org.koin.androidx.viewmodel.ext.android.viewModel -import org.openedx.app.databinding.ActivityAppBinding -import org.openedx.core.data.storage.CorePreferences class AppActivity : AppCompatActivity(), InsetHolder, WindowSizeHolder, AppDataHolder { @@ -32,6 +31,8 @@ class AppActivity : AppCompatActivity(), InsetHolder, WindowSizeHolder, AppDataH get() = _insetTop override val bottomInset: Int get() = _insetBottom + override val cutoutInset: Int + get() = _insetCutout override val windowSize: WindowSize get() = _windowSize @@ -46,12 +47,14 @@ class AppActivity : AppCompatActivity(), InsetHolder, WindowSizeHolder, AppDataH private var _insetTop = 0 private var _insetBottom = 0 + private var _insetCutout = 0 private var _windowSize = WindowSize(WindowType.Compact, WindowType.Compact) override fun onSaveInstanceState(outState: Bundle) { outState.putInt(TOP_INSET, topInset) outState.putInt(BOTTOM_INSET, bottomInset) + outState.putInt(CUTOUT_INSET, cutoutInset) super.onSaveInstanceState(outState) } @@ -74,6 +77,7 @@ class AppActivity : AppCompatActivity(), InsetHolder, WindowSizeHolder, AppDataH if (savedInstanceState != null) { _insetTop = savedInstanceState.getInt(TOP_INSET, 0) _insetBottom = savedInstanceState.getInt(BOTTOM_INSET, 0) + _insetCutout = savedInstanceState.getInt(CUTOUT_INSET, 0) } window.apply { @@ -93,6 +97,14 @@ class AppActivity : AppCompatActivity(), InsetHolder, WindowSizeHolder, AppDataH _insetTop = insetsCompat.top _insetBottom = insetsCompat.bottom + val displayCutout = WindowInsetsCompat.toWindowInsetsCompat(insets).displayCutout + if (displayCutout != null) { + val top = displayCutout.safeInsetTop + val left = displayCutout.safeInsetLeft + val right = displayCutout.safeInsetRight + _insetCutout = maxOf(top, left, right) + } + insets } binding.root.requestApplyInsetsWhenAttached() @@ -134,14 +146,6 @@ class AppActivity : AppCompatActivity(), InsetHolder, WindowSizeHolder, AppDataH else -> WindowType.Expanded } _windowSize = WindowSize(widthWindowSize, heightWindowSize) - requestedOrientation = - if (widthWindowSize != WindowType.Compact && heightWindowSize != WindowType.Compact) { - ActivityInfo.SCREEN_ORIENTATION_SENSOR - } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR) { - ActivityInfo.SCREEN_ORIENTATION_SENSOR - } else { - ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - } } private fun isUsingNightModeResources(): Boolean { @@ -157,5 +161,6 @@ class AppActivity : AppCompatActivity(), InsetHolder, WindowSizeHolder, AppDataH companion object { const val TOP_INSET = "topInset" const val BOTTOM_INSET = "bottomInset" + const val CUTOUT_INSET = "cutoutInset" } } diff --git a/app/src/main/java/org/openedx/app/AppRouter.kt b/app/src/main/java/org/openedx/app/AppRouter.kt index d9938022c..144af22e5 100644 --- a/app/src/main/java/org/openedx/app/AppRouter.kt +++ b/app/src/main/java/org/openedx/app/AppRouter.kt @@ -140,11 +140,12 @@ class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, Di videoUrl: String, videoTime: Long, blockId: String, - courseId: String + courseId: String, + isPlaying: Boolean ) { replaceFragmentWithBackStack( fm, - VideoFullScreenFragment.newInstance(videoUrl, videoTime, blockId, courseId) + VideoFullScreenFragment.newInstance(videoUrl, videoTime, blockId, courseId, isPlaying) ) } @@ -153,11 +154,12 @@ class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, Di videoUrl: String, videoTime: Long, blockId: String, - courseId: String + courseId: String, + isPlaying: Boolean ) { replaceFragmentWithBackStack( fm, - YoutubeVideoFullScreenFragment.newInstance(videoUrl, videoTime, blockId, courseId) + YoutubeVideoFullScreenFragment.newInstance(videoUrl, videoTime, blockId, courseId, isPlaying) ) } diff --git a/auth/src/main/java/org/openedx/auth/presentation/restore/RestorePasswordFragment.kt b/auth/src/main/java/org/openedx/auth/presentation/restore/RestorePasswordFragment.kt index 014b233d8..a48b6371a 100644 --- a/auth/src/main/java/org/openedx/auth/presentation/restore/RestorePasswordFragment.kt +++ b/auth/src/main/java/org/openedx/auth/presentation/restore/RestorePasswordFragment.kt @@ -191,6 +191,7 @@ private fun RestorePasswordScreen( Column( Modifier .then(contentPaddings) + .displayCutoutForLandscape() .fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally ) { @@ -247,6 +248,7 @@ private fun RestorePasswordScreen( Column( Modifier .then(contentPaddings) + .displayCutoutForLandscape() .fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally diff --git a/auth/src/main/java/org/openedx/auth/presentation/signin/SignInFragment.kt b/auth/src/main/java/org/openedx/auth/presentation/signin/SignInFragment.kt index 9ea354c7f..9f6b8a23b 100644 --- a/auth/src/main/java/org/openedx/auth/presentation/signin/SignInFragment.kt +++ b/auth/src/main/java/org/openedx/auth/presentation/signin/SignInFragment.kt @@ -178,6 +178,7 @@ private fun LoginScreen( modifier = Modifier .background(MaterialTheme.appColors.background) .verticalScroll(scrollState) + .displayCutoutForLandscape() .then(contentPaddings), ) { Text( diff --git a/auth/src/main/java/org/openedx/auth/presentation/signup/SignUpFragment.kt b/auth/src/main/java/org/openedx/auth/presentation/signup/SignUpFragment.kt index 59a25fca0..4eb7416fd 100644 --- a/auth/src/main/java/org/openedx/auth/presentation/signup/SignUpFragment.kt +++ b/auth/src/main/java/org/openedx/auth/presentation/signup/SignUpFragment.kt @@ -3,7 +3,6 @@ package org.openedx.auth.presentation.signup import android.content.res.Configuration -import android.content.res.Configuration.ORIENTATION_PORTRAIT import android.os.Bundle import android.view.LayoutInflater import android.view.ViewGroup @@ -111,7 +110,6 @@ internal fun RegistrationScreen( onRegisterClick: (Map) -> Unit ) { val scaffoldState = rememberScaffoldState() - val configuration = LocalConfiguration.current val focusManager = LocalFocusManager.current val bottomSheetScaffoldState = rememberModalBottomSheetState( initialValue = ModalBottomSheetValue.Hidden, @@ -317,6 +315,7 @@ internal fun RegistrationScreen( Modifier .fillMaxHeight() .verticalScroll(scrollState) + .displayCutoutForLandscape() .then(contentPaddings), verticalArrangement = Arrangement.spacedBy(24.dp), horizontalAlignment = Alignment.CenterHorizontally diff --git a/core/src/main/java/org/openedx/core/extension/ViewExt.kt b/core/src/main/java/org/openedx/core/extension/ViewExt.kt index 4fb9bad85..cc63687c9 100644 --- a/core/src/main/java/org/openedx/core/extension/ViewExt.kt +++ b/core/src/main/java/org/openedx/core/extension/ViewExt.kt @@ -1,8 +1,12 @@ package org.openedx.core.extension import android.content.Context +import android.content.res.Resources +import android.graphics.Rect import android.util.DisplayMetrics import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.DialogFragment fun Context.dpToPixel(dp: Int): Float { return dp * (resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT) @@ -28,5 +32,12 @@ fun View.requestApplyInsetsWhenAttached() { override fun onViewDetachedFromWindow(v: View) = Unit }) } +} +fun DialogFragment.setWidthPercent(percentage: Int) { + val percent = percentage.toFloat() / 100 + val dm = Resources.getSystem().displayMetrics + val rect = dm.run { Rect(0, 0, widthPixels, heightPixels) } + val percentWidth = rect.width() * percent + dialog?.window?.setLayout(percentWidth.toInt(), ViewGroup.LayoutParams.WRAP_CONTENT) } \ No newline at end of file diff --git a/core/src/main/java/org/openedx/core/presentation/global/InsetHolder.kt b/core/src/main/java/org/openedx/core/presentation/global/InsetHolder.kt index 8dd5551de..26996f162 100644 --- a/core/src/main/java/org/openedx/core/presentation/global/InsetHolder.kt +++ b/core/src/main/java/org/openedx/core/presentation/global/InsetHolder.kt @@ -4,4 +4,5 @@ package org.openedx.core.presentation.global interface InsetHolder { val topInset: Int val bottomInset: Int + val cutoutInset: Int } \ No newline at end of file diff --git a/core/src/main/java/org/openedx/core/system/notifier/CourseVideoPositionChanged.kt b/core/src/main/java/org/openedx/core/system/notifier/CourseVideoPositionChanged.kt index a06a6b7e2..af7a0583e 100644 --- a/core/src/main/java/org/openedx/core/system/notifier/CourseVideoPositionChanged.kt +++ b/core/src/main/java/org/openedx/core/system/notifier/CourseVideoPositionChanged.kt @@ -2,5 +2,6 @@ package org.openedx.core.system.notifier data class CourseVideoPositionChanged( val videoUrl: String, - val videoTime: Long + val videoTime: Long, + val isPlaying: Boolean ) : CourseEvent \ No newline at end of file diff --git a/core/src/main/java/org/openedx/core/ui/ComposeExtensions.kt b/core/src/main/java/org/openedx/core/ui/ComposeExtensions.kt index 424bf6066..5d896e742 100644 --- a/core/src/main/java/org/openedx/core/ui/ComposeExtensions.kt +++ b/core/src/main/java/org/openedx/core/ui/ComposeExtensions.kt @@ -1,5 +1,6 @@ package org.openedx.core.ui +import android.content.res.Configuration import android.graphics.Rect import android.view.ViewTreeObserver import androidx.compose.foundation.MutatePriority @@ -7,19 +8,33 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.mapSaver import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.composed +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.PathEffect +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.platform.LocalView -import org.openedx.core.presentation.global.InsetHolder +import androidx.compose.ui.unit.Dp import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.launch +import org.openedx.core.presentation.global.InsetHolder inline val isPreview: Boolean @ReadOnlyComposable @@ -59,6 +74,16 @@ fun Modifier.statusBarsInset(): Modifier = composed { .padding(top = with(LocalDensity.current) { topInset.toDp() }) } +fun Modifier.displayCutoutForLandscape(): Modifier = composed { + val cutoutInset = (LocalContext.current as? InsetHolder)?.cutoutInset ?: 0 + val cutoutInsetDp = with(LocalDensity.current) { cutoutInset.toDp() } + return@composed if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE) { + this.padding(horizontal = cutoutInsetDp) + } else { + this + } +} + inline fun Modifier.noRippleClickable(crossinline onClick: () -> Unit): Modifier = composed { clickable( indication = null, @@ -67,6 +92,35 @@ inline fun Modifier.noRippleClickable(crossinline onClick: () -> Unit): Modifier } } +fun Modifier.roundBorderWithoutBottom(borderWidth: Dp, cornerRadius: Dp): Modifier = composed( + factory = { + var path: Path + this.then( + Modifier.drawWithCache { + val height = this.size.height + val width = this.size.width + onDrawWithContent { + drawContent() + path = Path().apply { + moveTo(width.times(0f), height.times(1f)) + lineTo(width.times(0f), height.times(0f)) + lineTo(width.times(1f), height.times(0f)) + lineTo(width.times(1f), height.times(1f)) + } + drawPath( + path = path, + color = Color.LightGray, + style = Stroke( + width = borderWidth.toPx(), + pathEffect = PathEffect.cornerPathEffect(cornerRadius.toPx()) + ) + ) + } + } + ) + } +) + @Composable fun rememberSaveableMap(init: () -> MutableMap): MutableMap { return rememberSaveable( diff --git a/course/src/main/java/org/openedx/course/presentation/ChapterEndFragmentDialog.kt b/course/src/main/java/org/openedx/course/presentation/ChapterEndFragmentDialog.kt index ccf2c11fa..3a0bfe50a 100644 --- a/course/src/main/java/org/openedx/course/presentation/ChapterEndFragmentDialog.kt +++ b/course/src/main/java/org/openedx/course/presentation/ChapterEndFragmentDialog.kt @@ -1,5 +1,6 @@ package org.openedx.course.presentation +import android.content.res.Configuration import android.content.res.Configuration.UI_MODE_NIGHT_NO import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.graphics.Color @@ -7,21 +8,29 @@ import android.graphics.drawable.ColorDrawable import android.os.Bundle import android.view.LayoutInflater import android.view.ViewGroup +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material.Card import androidx.compose.material.Icon +import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -30,6 +39,8 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.os.bundleOf import androidx.fragment.app.DialogFragment +import org.openedx.core.extension.setWidthPercent +import org.openedx.core.ui.AutoSizeText import org.openedx.core.ui.OpenEdXButton import org.openedx.core.ui.OpenEdXOutlinedButton import org.openedx.core.ui.TextIcon @@ -44,6 +55,13 @@ class ChapterEndFragmentDialog : DialogFragment() { var listener: DialogListener? = null + override fun onResume() { + super.onResume() + if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { + setWidthPercent(66) + } + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -55,22 +73,48 @@ class ChapterEndFragmentDialog : DialogFragment() { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { OpenEdXTheme { - ChapterEndDialogScreen( - sectionName = requireArguments().getString(ARG_SECTION_NAME) ?: "", - nextSectionName = requireArguments().getString(ARG_NEXT_SECTION_NAME) ?: "", - onBackButtonClick = { - dismiss() - listener?.onDismiss() - requireActivity().supportFragmentManager.popBackStack( - CourseSectionFragment::class.java.simpleName, - 0 - ) - }, - onProceedButtonClick = { - dismiss() - listener?.onClick(true) - } - ) + val configuration = LocalConfiguration.current + if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { + ChapterEndDialogScreen( + sectionName = requireArguments().getString(ARG_SECTION_NAME) ?: "", + nextSectionName = requireArguments().getString(ARG_NEXT_SECTION_NAME) ?: "", + onBackButtonClick = { + dismiss() + listener?.onDismiss() + requireActivity().supportFragmentManager.popBackStack( + CourseSectionFragment::class.java.simpleName, + 0 + ) + }, + onProceedButtonClick = { + dismiss() + listener?.onClick(true) + }, + onCancelButtonClick = { + dismiss() + } + ) + } else { + ChapterEndDialogScreenLandscape( + sectionName = requireArguments().getString(ARG_SECTION_NAME) ?: "", + nextSectionName = requireArguments().getString(ARG_NEXT_SECTION_NAME) ?: "", + onBackButtonClick = { + dismiss() + listener?.onDismiss() + requireActivity().supportFragmentManager.popBackStack( + CourseSectionFragment::class.java.simpleName, + 0 + ) + }, + onProceedButtonClick = { + dismiss() + listener?.onClick(true) + }, + onCancelButtonClick = { + dismiss() + } + ) + } } } } @@ -102,7 +146,8 @@ private fun ChapterEndDialogScreen( sectionName: String, nextSectionName: String, onBackButtonClick: () -> Unit, - onProceedButtonClick: () -> Unit + onProceedButtonClick: () -> Unit, + onCancelButtonClick: () -> Unit ) { Card( modifier = Modifier @@ -112,11 +157,24 @@ private fun ChapterEndDialogScreen( shape = MaterialTheme.appShapes.courseImageShape ) { Column( - Modifier - .padding(horizontal = 40.dp) - .padding(top = 48.dp, bottom = 38.dp), + modifier = Modifier.padding(30.dp), horizontalAlignment = Alignment.CenterHorizontally ) { + Box( + contentAlignment = Alignment.CenterEnd, + modifier = Modifier.fillMaxWidth() + ) { + IconButton( + modifier = Modifier.size(24.dp), + onClick = onCancelButtonClick + ) { + Icon( + imageVector = Icons.Filled.Close, + contentDescription = stringResource(id = org.openedx.core.R.string.core_cancel), + tint = MaterialTheme.appColors.primary + ) + } + } Icon( modifier = Modifier .width(76.dp) @@ -159,7 +217,14 @@ private fun ChapterEndDialogScreen( borderColor = MaterialTheme.appColors.buttonBackground, textColor = MaterialTheme.appColors.buttonBackground, text = stringResource(id = R.string.course_back_to_outline), - onClick = onBackButtonClick + onClick = onBackButtonClick, + content = { + AutoSizeText( + text = stringResource(id = R.string.course_back_to_outline), + style = MaterialTheme.appTypography.bodyMedium, + color = MaterialTheme.appColors.buttonBackground + ) + } ) if (nextSectionName.isNotEmpty()) { Spacer(Modifier.height(24.dp)) @@ -175,6 +240,124 @@ private fun ChapterEndDialogScreen( } } + +@Composable +private fun ChapterEndDialogScreenLandscape( + sectionName: String, + nextSectionName: String, + onBackButtonClick: () -> Unit, + onProceedButtonClick: () -> Unit, + onCancelButtonClick: () -> Unit +) { + Card( + modifier = Modifier + .fillMaxWidth() + .clip(MaterialTheme.appShapes.courseImageShape), + backgroundColor = MaterialTheme.appColors.background, + shape = MaterialTheme.appShapes.courseImageShape + ) { + Column( + modifier = Modifier.padding(38.dp) + ) { + Box( + contentAlignment = Alignment.CenterEnd, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 12.dp) + ) { + IconButton( + modifier = Modifier.size(24.dp), + onClick = onCancelButtonClick + ) { + Icon( + imageVector = Icons.Filled.Close, + contentDescription = stringResource(id = org.openedx.core.R.string.core_cancel), + tint = MaterialTheme.appColors.primary + ) + } + } + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Column( + Modifier.weight(1f), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + modifier = Modifier + .width(76.dp) + .height(72.dp), + painter = painterResource(id = R.drawable.course_id_diamond), + contentDescription = null, + tint = MaterialTheme.appColors.onBackground + ) + Spacer(Modifier.height(36.dp)) + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(id = R.string.course_good_work), + color = MaterialTheme.appColors.textPrimary, + style = MaterialTheme.appTypography.titleLarge, + textAlign = TextAlign.Center + ) + Spacer(Modifier.height(8.dp)) + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(id = R.string.course_section_finished, sectionName), + color = MaterialTheme.appColors.textFieldText, + style = MaterialTheme.appTypography.titleSmall, + textAlign = TextAlign.Center + ) + } + Spacer(Modifier.width(42.dp)) + Column( + Modifier.weight(1f), + horizontalAlignment = Alignment.CenterHorizontally + ) { + if (nextSectionName.isNotEmpty()) { + OpenEdXButton( + text = stringResource(id = R.string.course_next_section), + content = { + TextIcon( + text = stringResource(id = R.string.course_next_section), + painter = painterResource(org.openedx.core.R.drawable.core_ic_forward), + color = MaterialTheme.appColors.buttonText, + textStyle = MaterialTheme.appTypography.labelLarge + ) + }, + onClick = onProceedButtonClick + ) + Spacer(Modifier.height(16.dp)) + } + OpenEdXOutlinedButton( + borderColor = MaterialTheme.appColors.buttonBackground, + textColor = MaterialTheme.appColors.buttonBackground, + text = stringResource(id = R.string.course_back_to_outline), + onClick = onBackButtonClick, + content = { + AutoSizeText( + text = stringResource(id = R.string.course_back_to_outline), + style = MaterialTheme.appTypography.bodyMedium, + color = MaterialTheme.appColors.buttonBackground + ) + } + ) + if (nextSectionName.isNotEmpty()) { + Spacer(Modifier.height(24.dp)) + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(id = R.string.course_to_proceed, nextSectionName), + color = MaterialTheme.appColors.textPrimaryVariant, + style = MaterialTheme.appTypography.labelSmall, + textAlign = TextAlign.Center + ) + } + } + } + } + } +} + @Preview(uiMode = UI_MODE_NIGHT_NO) @Preview(uiMode = UI_MODE_NIGHT_YES) @Composable @@ -184,7 +367,23 @@ fun ChapterEndDialogScreenPreview() { sectionName = "Section", nextSectionName = "Section2", onBackButtonClick = {}, - onProceedButtonClick = {} + onProceedButtonClick = {}, + onCancelButtonClick = {} + ) + } +} + +@Preview(uiMode = UI_MODE_NIGHT_NO) +@Preview(uiMode = UI_MODE_NIGHT_YES) +@Composable +fun ChapterEndDialogScreenLandscapePreview() { + OpenEdXTheme { + ChapterEndDialogScreenLandscape( + sectionName = "Section", + nextSectionName = "Section2", + onBackButtonClick = {}, + onProceedButtonClick = {}, + onCancelButtonClick = {} ) } } \ No newline at end of file diff --git a/course/src/main/java/org/openedx/course/presentation/CourseRouter.kt b/course/src/main/java/org/openedx/course/presentation/CourseRouter.kt index 11c4420c0..0f4de4434 100644 --- a/course/src/main/java/org/openedx/course/presentation/CourseRouter.kt +++ b/course/src/main/java/org/openedx/course/presentation/CourseRouter.kt @@ -50,7 +50,8 @@ interface CourseRouter { videoUrl: String, videoTime: Long, blockId: String, - courseId: String + courseId: String, + isPlaying: Boolean ) fun navigateToFullScreenYoutubeVideo( @@ -58,7 +59,8 @@ interface CourseRouter { videoUrl: String, videoTime: Long, blockId: String, - courseId: String + courseId: String, + isPlaying: Boolean ) fun navigateToHandoutsWebView( diff --git a/course/src/main/java/org/openedx/course/presentation/detail/CourseDetailsFragment.kt b/course/src/main/java/org/openedx/course/presentation/detail/CourseDetailsFragment.kt index e08332d66..0e1b06909 100644 --- a/course/src/main/java/org/openedx/course/presentation/detail/CourseDetailsFragment.kt +++ b/course/src/main/java/org/openedx/course/presentation/detail/CourseDetailsFragment.kt @@ -36,7 +36,9 @@ import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.zIndex import androidx.core.os.bundleOf import androidx.fragment.app.Fragment -import org.openedx.core.BuildConfig +import org.koin.android.ext.android.inject +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf import org.openedx.core.UIMessage import org.openedx.core.domain.model.Course import org.openedx.core.domain.model.Media @@ -50,9 +52,6 @@ import org.openedx.core.utils.EmailUtil import org.openedx.course.R import org.openedx.course.presentation.CourseRouter import org.openedx.course.presentation.ui.CourseImageHeader -import org.koin.android.ext.android.inject -import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.core.parameter.parametersOf import java.nio.charset.StandardCharsets import java.util.* import org.openedx.course.R as courseR @@ -181,7 +180,8 @@ internal fun CourseDetailsScreen( modifier = Modifier .fillMaxSize() .padding(it) - .statusBarsInset(), + .statusBarsInset() + .displayCutoutForLandscape(), contentAlignment = Alignment.TopCenter ) { Column( diff --git a/course/src/main/java/org/openedx/course/presentation/handouts/HandoutsFragment.kt b/course/src/main/java/org/openedx/course/presentation/handouts/HandoutsFragment.kt index c5fd806e8..3778e549a 100644 --- a/course/src/main/java/org/openedx/course/presentation/handouts/HandoutsFragment.kt +++ b/course/src/main/java/org/openedx/course/presentation/handouts/HandoutsFragment.kt @@ -116,7 +116,8 @@ private fun HandoutsScreen( modifier = Modifier .fillMaxWidth() .padding(it) - .statusBarsInset(), + .statusBarsInset() + .displayCutoutForLandscape(), contentAlignment = Alignment.TopCenter ) { Column(screenWidth) { diff --git a/course/src/main/java/org/openedx/course/presentation/handouts/WebViewFragment.kt b/course/src/main/java/org/openedx/course/presentation/handouts/WebViewFragment.kt index d342b2f40..4c192ea88 100644 --- a/course/src/main/java/org/openedx/course/presentation/handouts/WebViewFragment.kt +++ b/course/src/main/java/org/openedx/course/presentation/handouts/WebViewFragment.kt @@ -33,7 +33,8 @@ import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.zIndex import androidx.core.os.bundleOf import androidx.fragment.app.Fragment -import org.openedx.core.BuildConfig +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf import org.openedx.core.extension.isEmailValid import org.openedx.core.extension.replaceLinkTags import org.openedx.core.ui.* @@ -41,8 +42,6 @@ import org.openedx.core.ui.theme.OpenEdXTheme import org.openedx.core.ui.theme.appColors import org.openedx.core.ui.theme.appTypography import org.openedx.core.utils.EmailUtil -import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.core.parameter.parametersOf import java.nio.charset.StandardCharsets class WebViewFragment : Fragment() { @@ -114,7 +113,8 @@ private fun WebContentScreen( val scaffoldState = rememberScaffoldState() Scaffold( modifier = Modifier - .fillMaxSize(), + .fillMaxSize() + .padding(bottom = 16.dp), scaffoldState = scaffoldState, backgroundColor = MaterialTheme.appColors.background ) { @@ -132,7 +132,8 @@ private fun WebContentScreen( modifier = Modifier .fillMaxWidth() .padding(it) - .statusBarsInset(), + .statusBarsInset() + .displayCutoutForLandscape(), contentAlignment = Alignment.TopCenter ) { Column(screenWidth) { diff --git a/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineFragment.kt b/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineFragment.kt index cb14aaa47..4aca1034d 100644 --- a/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineFragment.kt +++ b/course/src/main/java/org/openedx/course/presentation/outline/CourseOutlineFragment.kt @@ -230,7 +230,8 @@ internal fun CourseOutlineScreen( modifier = Modifier .fillMaxSize() .padding(it) - .statusBarsInset(), + .statusBarsInset() + .displayCutoutForLandscape(), contentAlignment = Alignment.TopCenter ) { Column( diff --git a/course/src/main/java/org/openedx/course/presentation/section/CourseSectionFragment.kt b/course/src/main/java/org/openedx/course/presentation/section/CourseSectionFragment.kt index 7fbd8dfb8..0e2cc27cb 100644 --- a/course/src/main/java/org/openedx/course/presentation/section/CourseSectionFragment.kt +++ b/course/src/main/java/org/openedx/course/presentation/section/CourseSectionFragment.kt @@ -77,6 +77,7 @@ import org.openedx.course.presentation.ui.CardArrow import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf +import org.openedx.core.ui.displayCutoutForLandscape import java.io.File class CourseSectionFragment : Fragment() { @@ -222,6 +223,7 @@ private fun CourseSectionScreen( Box( Modifier .fillMaxWidth() + .displayCutoutForLandscape() .zIndex(1f), contentAlignment = Alignment.CenterStart ) { diff --git a/course/src/main/java/org/openedx/course/presentation/ui/CourseUI.kt b/course/src/main/java/org/openedx/course/presentation/ui/CourseUI.kt index 4e268f11a..c46b576cc 100644 --- a/course/src/main/java/org/openedx/course/presentation/ui/CourseUI.kt +++ b/course/src/main/java/org/openedx/course/presentation/ui/CourseUI.kt @@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -53,6 +54,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.painterResource @@ -64,7 +66,6 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import coil.compose.AsyncImage import coil.request.ImageRequest -import org.jsoup.Jsoup import org.openedx.core.BlockType import org.openedx.core.domain.model.Block import org.openedx.core.domain.model.BlockCounts @@ -86,6 +87,8 @@ import org.openedx.core.ui.theme.appColors import org.openedx.core.ui.theme.appShapes import org.openedx.core.ui.theme.appTypography import org.openedx.course.R +import org.jsoup.Jsoup +import org.openedx.core.ui.rememberWindowSize import subtitleFile.Caption import subtitleFile.TimedTextObject import java.util.Date @@ -97,6 +100,13 @@ fun CourseImageHeader( courseImage: String?, courseCertificate: Certificate?, ) { + val configuration = LocalConfiguration.current + val windowSize = rememberWindowSize() + val contentScale = if (!windowSize.isTablet && configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { + ContentScale.Fit + } else { + ContentScale.Crop + } val uriHandler = LocalUriHandler.current val imageUrl = if (courseImage?.isLinkValid() == true) { courseImage @@ -111,7 +121,7 @@ fun CourseImageHeader( .placeholder(org.openedx.core.R.drawable.core_no_image_course) .build(), contentDescription = null, - contentScale = ContentScale.Crop, + contentScale = contentScale, modifier = Modifier .fillMaxSize() .clip(MaterialTheme.appShapes.cardShape) @@ -301,37 +311,13 @@ fun SequentialItem( } } -@Composable -fun VideoRotateView() { - Row( - modifier = Modifier - .fillMaxWidth() - .clip(MaterialTheme.appShapes.buttonShape) - .background(MaterialTheme.appColors.info) - .padding(horizontal = 16.dp, vertical = 12.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - painter = painterResource(id = R.drawable.course_ic_screen_rotation), - contentDescription = null, - tint = Color.White - ) - Spacer(Modifier.width(8.dp)) - Text( - text = stringResource(id = R.string.course_video_rotate_for_fullscreen), - style = MaterialTheme.appTypography.titleMedium, - color = Color.White - ) - } -} - @Composable fun VideoTitle(text: String) { Text( text = text, color = MaterialTheme.appColors.textPrimary, style = MaterialTheme.appTypography.titleLarge, - maxLines = 3, + maxLines = 1, overflow = TextOverflow.Ellipsis ) } @@ -351,13 +337,20 @@ fun NavigationUnitsButtons( painterResource(id = org.openedx.core.R.drawable.core_ic_check) } - val nextButtonModifier = if (hasPrevBlock) { + val subModifier = if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT) { + Modifier + .height(72.dp) + .fillMaxWidth() + } else { Modifier - } else Modifier.fillMaxWidth(0.6f) + .padding(end = 32.dp) + .padding(top = 2.dp) + } Row( modifier = Modifier - .fillMaxWidth() + .navigationBarsPadding() + .then(subModifier) .padding(horizontal = 12.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center @@ -395,7 +388,7 @@ fun NavigationUnitsButtons( } Button( modifier = Modifier - .height(42.dp).then(nextButtonModifier), + .height(42.dp), colors = ButtonDefaults.buttonColors( backgroundColor = MaterialTheme.appColors.buttonBackground ), @@ -647,15 +640,6 @@ private fun NavigationUnitsButtonsWithNextPreview() { } } -@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun VideoRotateViewPreview() { - OpenEdXTheme { - VideoRotateView() - } -} - @Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable diff --git a/course/src/main/java/org/openedx/course/presentation/unit/NotSupportedUnitFragment.kt b/course/src/main/java/org/openedx/course/presentation/unit/NotSupportedUnitFragment.kt index b89e4ae66..bdf5dcd8b 100644 --- a/course/src/main/java/org/openedx/course/presentation/unit/NotSupportedUnitFragment.kt +++ b/course/src/main/java/org/openedx/course/presentation/unit/NotSupportedUnitFragment.kt @@ -4,6 +4,8 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.ViewGroup import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.* import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -80,6 +82,7 @@ private fun NotSupportedUnitScreen( ) { val scaffoldState = rememberScaffoldState() val uriHandler = LocalUriHandler.current + val scrollState = rememberScrollState() Scaffold( modifier = Modifier.fillMaxSize(), scaffoldState = scaffoldState @@ -102,6 +105,7 @@ private fun NotSupportedUnitScreen( ) { Column( modifier = Modifier + .verticalScroll(scrollState) .padding(it) .then(contentWidth), verticalArrangement = Arrangement.Center, @@ -146,6 +150,7 @@ private fun NotSupportedUnitScreen( style = MaterialTheme.appTypography.labelLarge ) } + Spacer(Modifier.height(20.dp)) } } } diff --git a/course/src/main/java/org/openedx/course/presentation/unit/container/CourseUnitContainerFragment.kt b/course/src/main/java/org/openedx/course/presentation/unit/container/CourseUnitContainerFragment.kt index 8c7544e80..f4005e4e9 100644 --- a/course/src/main/java/org/openedx/course/presentation/unit/container/CourseUnitContainerFragment.kt +++ b/course/src/main/java/org/openedx/course/presentation/unit/container/CourseUnitContainerFragment.kt @@ -1,10 +1,9 @@ package org.openedx.course.presentation.unit.container +import android.content.res.Configuration import android.os.Bundle import android.os.SystemClock import android.view.View -import android.widget.FrameLayout -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.MaterialTheme import androidx.compose.runtime.getValue @@ -18,6 +17,10 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.os.bundleOf import androidx.fragment.app.Fragment import androidx.viewpager2.widget.ViewPager2 +import org.koin.android.ext.android.inject +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf +import org.openedx.core.BlockType import org.openedx.core.extension.serializable import org.openedx.core.presentation.course.CourseViewMode import org.openedx.core.presentation.global.InsetHolder @@ -33,9 +36,7 @@ import org.openedx.course.presentation.CourseRouter import org.openedx.course.presentation.DialogListener import org.openedx.course.presentation.ui.NavigationUnitsButtons import org.openedx.course.presentation.ui.VerticalPageIndicator -import org.koin.android.ext.android.inject -import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.core.parameter.parametersOf +import org.openedx.course.presentation.ui.VideoTitle class CourseUnitContainerFragment : Fragment(R.layout.fragment_course_unit_container) { @@ -68,14 +69,15 @@ class CourseUnitContainerFragment : Fragment(R.layout.fragment_course_unit_conta val statusBarParams = binding.statusBarInset.layoutParams as ConstraintLayout.LayoutParams statusBarParams.topMargin = insetHolder.topInset binding.statusBarInset.layoutParams = statusBarParams - val bottomNavigationParams = - binding.cvNavigationBar.layoutParams as ConstraintLayout.LayoutParams - bottomNavigationParams.bottomMargin = insetHolder.bottomInset - binding.cvNavigationBar.layoutParams = bottomNavigationParams - val containerParams = - binding.viewPager.layoutParams as FrameLayout.LayoutParams + val containerParams = binding.viewPager.layoutParams as ConstraintLayout.LayoutParams containerParams.bottomMargin = insetHolder.bottomInset binding.viewPager.layoutParams = containerParams + val configuration = requireActivity().resources.configuration + if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { + val countParams = binding.cvCount.layoutParams as ConstraintLayout.LayoutParams + countParams.rightMargin = insetHolder.cutoutInset + binding.cvCount.layoutParams = countParams + } initViewPager() if (savedInstanceState == null) { @@ -87,6 +89,15 @@ class CourseUnitContainerFragment : Fragment(R.layout.fragment_course_unit_conta } } + binding.cvVideoTitle?.setContent { + OpenEdXTheme { + val block by viewModel.currentBlock.observeAsState() + if (block?.type == BlockType.VIDEO) { + VideoTitle(text = block?.displayName ?: "") + } + } + } + binding.cvNavigationBar.setContent { OpenEdXTheme { var nextButtonText by rememberSaveable { @@ -106,93 +117,23 @@ class CourseUnitContainerFragment : Fragment(R.layout.fragment_course_unit_conta hasPrevBlock = hasPrev hasNextBlock = hasNext } - NavigationUnitsButtons( windowSize = windowSize, hasPrevBlock = hasPrevBlock, nextButtonText = nextButtonText, hasNextBlock = hasNextBlock, onPrevClick = { - if (!restrictDoubleClick()) { - val block = viewModel.moveToPrevBlock() - if (block != null) { - viewModel.prevBlockClickedEvent(block.blockId, block.displayName) - if (!block.type.isContainer()) { - binding.viewPager.setCurrentItem( - binding.viewPager.currentItem - 1, - true - ) - updateNavigationButtons { next, hasPrev, hasNext -> - nextButtonText = next - hasPrevBlock = hasPrev - hasNextBlock = hasNext - } - } - } + handlePrevClick { next, hasPrev, hasNext -> + nextButtonText = next + hasPrevBlock = hasPrev + hasNextBlock = hasNext } }, onNextClick = { - if (!restrictDoubleClick()) { - val block = viewModel.moveToNextBlock() - if (block != null) { - viewModel.nextBlockClickedEvent(block.blockId, block.displayName) - if (!block.type.isContainer()) { - binding.viewPager.setCurrentItem( - binding.viewPager.currentItem + 1, - true - ) - updateNavigationButtons { next, hasPrev, hasNext -> - nextButtonText = next - hasPrevBlock = hasPrev - hasNextBlock = hasNext - } - } - } else { - val currentVerticalBlock = viewModel.getCurrentVerticalBlock() - val nextVerticalBlock = viewModel.getNextVerticalBlock() - val dialog = ChapterEndFragmentDialog.newInstance( - currentVerticalBlock?.displayName ?: "", - nextVerticalBlock?.displayName ?: "" - ) - currentVerticalBlock?.let { - viewModel.finishVerticalClickedEvent( - it.blockId, - it.displayName - ) - } - dialog.listener = object : DialogListener { - override fun onClick(value: T) { - viewModel.proceedToNext() - val nextBlock = viewModel.getCurrentVerticalBlock() - nextBlock?.let { - viewModel.finishVerticalNextClickedEvent( - it.blockId, - it.displayName - ) - if (it.type.isContainer()) { - router.replaceCourseContainer( - requireActivity().supportFragmentManager, - it.id, - viewModel.courseId, - requireArguments().getString( - ARG_COURSE_NAME, - "" - ), - requireArguments().serializable(ARG_MODE)!! - ) - } - } - } - - override fun onDismiss() { - viewModel.finishVerticalBackClickedEvent() - } - } - dialog.show( - requireActivity().supportFragmentManager, - ChapterEndFragmentDialog::class.simpleName - ) - } + handleNextClick { next, hasPrev, hasNext -> + nextButtonText = next + hasPrevBlock = hasPrev + hasNextBlock = hasNext } } ) @@ -201,7 +142,6 @@ class CourseUnitContainerFragment : Fragment(R.layout.fragment_course_unit_conta binding.cvCount.setContent { OpenEdXTheme { - val index by viewModel.indexInContainer.observeAsState(1) val units by viewModel.verticalBlockCounts.observeAsState(1) @@ -214,7 +154,6 @@ class CourseUnitContainerFragment : Fragment(R.layout.fragment_course_unit_conta selectedLength = 5.dp, modifier = Modifier .width(24.dp) - .padding(end = 6.dp) ) } } @@ -258,6 +197,87 @@ class CourseUnitContainerFragment : Fragment(R.layout.fragment_course_unit_conta binding.viewPager.isUserInputEnabled = false } + private fun handlePrevClick(buttonChanged: (String, Boolean, Boolean) -> Unit) { + if (!restrictDoubleClick()) { + val block = viewModel.moveToPrevBlock() + if (block != null) { + viewModel.prevBlockClickedEvent(block.blockId, block.displayName) + if (!block.type.isContainer()) { + binding.viewPager.setCurrentItem( + binding.viewPager.currentItem - 1, + true + ) + updateNavigationButtons { next, hasPrev, hasNext -> + buttonChanged(next, hasPrev, hasNext) + } + } + } + } + } + + private fun handleNextClick(buttonChanged: (String, Boolean, Boolean) -> Unit) { + if (!restrictDoubleClick()) { + val block = viewModel.moveToNextBlock() + if (block != null) { + viewModel.nextBlockClickedEvent(block.blockId, block.displayName) + if (!block.type.isContainer()) { + binding.viewPager.setCurrentItem( + binding.viewPager.currentItem + 1, + true + ) + updateNavigationButtons { next, hasPrev, hasNext -> + buttonChanged(next, hasPrev, hasNext) + } + } + } else { + val currentVerticalBlock = viewModel.getCurrentVerticalBlock() + val nextVerticalBlock = viewModel.getNextVerticalBlock() + val dialog = ChapterEndFragmentDialog.newInstance( + currentVerticalBlock?.displayName ?: "", + nextVerticalBlock?.displayName ?: "" + ) + currentVerticalBlock?.let { + viewModel.finishVerticalClickedEvent( + it.blockId, + it.displayName + ) + } + dialog.listener = object : DialogListener { + override fun onClick(value: T) { + viewModel.proceedToNext() + val nextBlock = viewModel.getCurrentVerticalBlock() + nextBlock?.let { + viewModel.finishVerticalNextClickedEvent( + it.blockId, + it.displayName + ) + if (it.type.isContainer()) { + router.replaceCourseContainer( + requireActivity().supportFragmentManager, + it.id, + viewModel.courseId, + requireArguments().getString( + ARG_COURSE_NAME, + "" + ), + requireArguments().serializable(ARG_MODE)!! + ) + } + } + } + + override fun onDismiss() { + viewModel.finishVerticalBackClickedEvent() + } + } + dialog.show( + requireActivity().supportFragmentManager, + ChapterEndFragmentDialog::class.simpleName + ) + } + } + } + companion object { private const val ARG_BLOCK_ID = "blockId" diff --git a/course/src/main/java/org/openedx/course/presentation/unit/container/CourseUnitContainerViewModel.kt b/course/src/main/java/org/openedx/course/presentation/unit/container/CourseUnitContainerViewModel.kt index 553c1a1c7..e4c671617 100644 --- a/course/src/main/java/org/openedx/course/presentation/unit/container/CourseUnitContainerViewModel.kt +++ b/course/src/main/java/org/openedx/course/presentation/unit/container/CourseUnitContainerViewModel.kt @@ -29,10 +29,8 @@ class CourseUnitContainerViewModel( private val blocks = ArrayList() - var currentIndex = 0 - private set - var currentVerticalIndex = 0 - private set + private var currentIndex = 0 + private var currentVerticalIndex = 0 private var currentSectionIndex = -1 val isFirstIndexInContainer: Boolean @@ -53,6 +51,10 @@ class CourseUnitContainerViewModel( val indexInContainer: LiveData get() = _indexInContainer + private val _currentBlock = MutableLiveData() + val currentBlock: LiveData + get() = _currentBlock + var nextButtonText = "" var hasNextBlock = false private var courseName = "" @@ -78,6 +80,9 @@ class CourseUnitContainerViewModel( } fun setupCurrentIndex(blockId: String) { + if (currentSectionIndex != -1) { + return + } blocks.forEachIndexed { index, block -> if (block.id == blockId) { currentVerticalIndex = index @@ -92,6 +97,9 @@ class CourseUnitContainerViewModel( if (currentVerticalIndex != -1) { _verticalBlockCounts.value = blocks[currentVerticalIndex].descendants.size } + if (block.descendants.isNotEmpty()) { + _currentBlock.value = blocks.first { it.id == block.descendants.first() } + } return } } @@ -136,6 +144,7 @@ class CourseUnitContainerViewModel( if (currentVerticalIndex != -1) { _indexInContainer.value = currentIndex } + _currentBlock.value = block return block } return null @@ -148,6 +157,7 @@ class CourseUnitContainerViewModel( if (currentVerticalIndex != -1) { _indexInContainer.value = currentIndex } + _currentBlock.value = block return block } return null diff --git a/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitFragment.kt b/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitFragment.kt index b4724fcb7..fea1b21f7 100644 --- a/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitFragment.kt +++ b/course/src/main/java/org/openedx/course/presentation/unit/html/HtmlUnitFragment.kt @@ -2,6 +2,7 @@ package org.openedx.course.presentation.unit.html import android.annotation.SuppressLint import android.content.Intent +import android.content.res.Configuration import android.net.Uri import android.os.Bundle import android.util.Log @@ -9,13 +10,17 @@ import android.view.LayoutInflater import android.view.ViewGroup import android.webkit.* import androidx.compose.foundation.background +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.unit.Dp @@ -24,18 +29,19 @@ import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.zIndex import androidx.core.os.bundleOf import androidx.fragment.app.Fragment +import kotlinx.coroutines.launch +import org.koin.android.ext.android.inject import org.openedx.core.extension.isEmailValid import org.openedx.core.system.AppCookieManager import org.openedx.core.system.connection.NetworkConnection import org.openedx.core.ui.WindowSize import org.openedx.core.ui.rememberWindowSize +import org.openedx.core.ui.roundBorderWithoutBottom import org.openedx.core.ui.theme.OpenEdXTheme import org.openedx.core.ui.theme.appColors import org.openedx.core.ui.windowSizeValue import org.openedx.core.utils.EmailUtil import org.openedx.course.presentation.ui.ConnectionErrorView -import kotlinx.coroutines.launch -import org.koin.android.ext.android.inject class HtmlUnitFragment : Fragment() { @@ -67,14 +73,35 @@ class HtmlUnitFragment : Fragment() { var hasInternetConnection by remember { mutableStateOf(networkConnection.isOnline()) } + + val configuration = LocalConfiguration.current + + val bottomPadding = if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { + 72.dp + } else { + 0.dp + } + + val border = if (!isSystemInDarkTheme()) { + Modifier.roundBorderWithoutBottom( + borderWidth = 2.dp, + cornerRadius = 30.dp + ) + } else { + Modifier + } + Surface( + modifier = Modifier + .clip(RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp)), color = Color.White ) { Box( modifier = Modifier .fillMaxSize() + .padding(bottom = bottomPadding) .background(Color.White) - .padding(bottom = 72.dp), + .then(border), contentAlignment = Alignment.TopCenter ) { if (hasInternetConnection) { diff --git a/course/src/main/java/org/openedx/course/presentation/unit/video/VideoFullScreenFragment.kt b/course/src/main/java/org/openedx/course/presentation/unit/video/VideoFullScreenFragment.kt index 8bbd649d4..20ef5629c 100644 --- a/course/src/main/java/org/openedx/course/presentation/unit/video/VideoFullScreenFragment.kt +++ b/course/src/main/java/org/openedx/course/presentation/unit/video/VideoFullScreenFragment.kt @@ -1,7 +1,6 @@ package org.openedx.course.presentation.unit.video import android.annotation.SuppressLint -import android.content.pm.ActivityInfo import android.os.Bundle import android.view.View import android.widget.FrameLayout @@ -15,7 +14,6 @@ import androidx.media3.exoplayer.ExoPlayer import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf import org.openedx.core.extension.requestApplyInsetsWhenAttached -import org.openedx.core.presentation.global.WindowSizeHolder import org.openedx.core.presentation.global.viewBinding import org.openedx.course.R import org.openedx.course.databinding.FragmentVideoFullScreenBinding @@ -29,7 +27,18 @@ class VideoFullScreenFragment : Fragment(R.layout.fragment_video_full_screen) { private var exoPlayer: ExoPlayer? = null private var blockId = "" - private var isTabletDevice = false + private val exoPlayerListener = object : Player.Listener { + override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) { + super.onPlayWhenReadyChanged(playWhenReady, reason) + viewModel.isPlaying = playWhenReady + } + override fun onPlaybackStateChanged(playbackState: Int) { + super.onPlaybackStateChanged(playbackState) + if (playbackState == Player.STATE_ENDED) { + viewModel.markBlockCompleted(blockId) + } + } + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -38,14 +47,8 @@ class VideoFullScreenFragment : Fragment(R.layout.fragment_video_full_screen) { if (viewModel.currentVideoTime == 0L) { viewModel.currentVideoTime = requireArguments().getLong(ARG_VIDEO_TIME, 0) } - setOrientationBasedOnDeviceType() - } - - private fun setOrientationBasedOnDeviceType() { - val windowSize = (requireActivity() as WindowSizeHolder).windowSize - isTabletDevice = windowSize.isTablet - if (!isTabletDevice) { - requireActivity().requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR + if (viewModel.isPlaying == null) { + viewModel.isPlaying = requireArguments().getBoolean(ARG_IS_PLAYING) } } @@ -80,7 +83,7 @@ class VideoFullScreenFragment : Fragment(R.layout.fragment_video_full_screen) { val mediaItem = MediaItem.fromUri(viewModel.videoUrl) exoPlayer?.setMediaItem(mediaItem, viewModel.currentVideoTime) exoPlayer?.prepare() - exoPlayer?.playWhenReady = false + exoPlayer?.playWhenReady = viewModel.isPlaying ?: false playerView.setFullscreenButtonClickListener { isFullScreen -> requireActivity().supportFragmentManager.popBackStackImmediate() @@ -103,13 +106,10 @@ class VideoFullScreenFragment : Fragment(R.layout.fragment_video_full_screen) { exoPlayer = null } - override fun onPause() { super.onPause() - if (exoPlayer?.isPlaying == true) { - exoPlayer?.pause() - exoPlayer?.playWhenReady = false - } + exoPlayer?.removeListener(exoPlayerListener) + exoPlayer?.pause() } override fun onDestroyView() { @@ -118,33 +118,39 @@ class VideoFullScreenFragment : Fragment(R.layout.fragment_video_full_screen) { super.onDestroyView() } + @SuppressLint("SourceLockedOrientationActivity") override fun onDestroy() { - if (!isTabletDevice) { - requireActivity().requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - } releasePlayer() super.onDestroy() } + override fun onResume() { + super.onResume() + exoPlayer?.addListener(exoPlayerListener) + } + companion object { private const val ARG_BLOCK_VIDEO_URL = "blockVideoUrl" private const val ARG_VIDEO_TIME = "videoTime" private const val ARG_BLOCK_ID = "blockId" private const val ARG_COURSE_ID = "courseId" + private const val ARG_IS_PLAYING = "isPlaying" fun newInstance( videoUrl: String, videoTime: Long, blockId: String, - courseId: String + courseId: String, + isPlaying: Boolean ): VideoFullScreenFragment { val fragment = VideoFullScreenFragment() fragment.arguments = bundleOf( ARG_BLOCK_VIDEO_URL to videoUrl, ARG_VIDEO_TIME to videoTime, ARG_BLOCK_ID to blockId, - ARG_COURSE_ID to courseId + ARG_COURSE_ID to courseId, + ARG_IS_PLAYING to isPlaying ) return fragment } diff --git a/course/src/main/java/org/openedx/course/presentation/unit/video/VideoUnitFragment.kt b/course/src/main/java/org/openedx/course/presentation/unit/video/VideoUnitFragment.kt index b73fcd440..65f94eba0 100644 --- a/course/src/main/java/org/openedx/course/presentation/unit/video/VideoUnitFragment.kt +++ b/course/src/main/java/org/openedx/course/presentation/unit/video/VideoUnitFragment.kt @@ -1,9 +1,9 @@ package org.openedx.course.presentation.unit.video +import android.content.res.Configuration import android.os.Bundle import android.os.Handler import android.os.Looper -import android.view.OrientationEventListener import android.view.View import android.widget.FrameLayout import androidx.compose.foundation.background @@ -38,7 +38,6 @@ import org.openedx.course.R import org.openedx.course.databinding.FragmentVideoUnitBinding import org.openedx.course.presentation.CourseRouter import org.openedx.course.presentation.ui.ConnectionErrorView -import org.openedx.course.presentation.ui.VideoRotateView import org.openedx.course.presentation.ui.VideoSubtitles import org.openedx.course.presentation.ui.VideoTitle import kotlin.math.roundToInt @@ -54,7 +53,6 @@ class VideoUnitFragment : Fragment(R.layout.fragment_video_unit) { private var exoPlayer: ExoPlayer? = null private var windowSize: WindowSize? = null - private var orientationListener: OrientationEventListener? = null private var blockId = "" private val handler = Handler(Looper.getMainLooper()) @@ -74,6 +72,10 @@ class VideoUnitFragment : Fragment(R.layout.fragment_video_unit) { } private val exoPlayerListener = object : Player.Listener { + override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) { + super.onPlayWhenReadyChanged(playWhenReady, reason) + viewModel.isPlaying = playWhenReady + } override fun onPlaybackStateChanged(playbackState: Int) { super.onPlaybackStateChanged(playbackState) if (playbackState == Player.STATE_ENDED) { @@ -97,38 +99,12 @@ class VideoUnitFragment : Fragment(R.layout.fragment_video_unit) { blockId = getString(ARG_BLOCK_ID, "") } viewModel.downloadSubtitles() - orientationListener = object : OrientationEventListener(requireActivity()) { - override fun onOrientationChanged(orientation: Int) { - if (windowSize?.isTablet != true) { - if (orientation in 75..300) { - if (!viewModel.fullscreenHandled) { - router.navigateToFullScreenVideo( - requireActivity().supportFragmentManager, - viewModel.videoUrl, - exoPlayer?.currentPosition ?: viewModel.getCurrentVideoTime(), - blockId, - viewModel.courseId - ) - viewModel.fullscreenHandled = true - } - } else { - viewModel.fullscreenHandled = false - } - } - } - } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - binding.cvRotateHelper.setContent { - OpenEdXTheme { - VideoRotateView() - } - } - - binding.cvVideoTitle.setContent { + binding.cvVideoTitle?.setContent { OpenEdXTheme { VideoTitle(text = requireArguments().getString(ARG_TITLE) ?: "") } @@ -180,19 +156,23 @@ class VideoUnitFragment : Fragment(R.layout.fragment_video_unit) { binding.connectionError.isVisible = !viewModel.hasInternetConnection && !viewModel.isDownloaded + val orientation = resources.configuration.orientation val windowMetrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(requireActivity()) val currentBounds = windowMetrics.bounds - val width = currentBounds.width() - requireContext().dpToPixel(32) - val minHeight = requireContext().dpToPixel(194).roundToInt() - val height = (width / 16f * 9f).roundToInt() val layoutParams = binding.playerView.layoutParams as FrameLayout.LayoutParams - layoutParams.height = if (windowSize?.isTablet == true) { - requireContext().dpToPixel(320).roundToInt() - } else if (height < minHeight) { - minHeight - } else { - height + if (orientation == Configuration.ORIENTATION_PORTRAIT || windowSize?.isTablet == true) { + val width = currentBounds.width() - requireContext().dpToPixel(32) + val minHeight = requireContext().dpToPixel(194).roundToInt() + val height = (width / 16f * 9f).roundToInt() + layoutParams.height = if (windowSize?.isTablet == true) { + requireContext().dpToPixel(320).roundToInt() + } else if (height < minHeight) { + minHeight + } else { + height + } } + binding.playerView.layoutParams = layoutParams viewModel.isUpdated.observe(viewLifecycleOwner) { isUpdated -> @@ -200,12 +180,6 @@ class VideoUnitFragment : Fragment(R.layout.fragment_video_unit) { initPlayer() } } - - viewModel.isPopUpViewShow.observe(viewLifecycleOwner) { - if (windowSize?.isTablet != true) { - binding.cvRotateHelper.isVisible = it - } - } } @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) @@ -223,6 +197,7 @@ class VideoUnitFragment : Fragment(R.layout.fragment_video_unit) { val mediaItem = MediaItem.fromUri(viewModel.videoUrl) exoPlayer?.setMediaItem(mediaItem, viewModel.getCurrentVideoTime()) exoPlayer?.prepare() + exoPlayer?.playWhenReady = viewModel.isPlaying playerView.setFullscreenButtonClickListener { isFullScreen -> router.navigateToFullScreenVideo( @@ -230,18 +205,15 @@ class VideoUnitFragment : Fragment(R.layout.fragment_video_unit) { viewModel.videoUrl, exoPlayer?.currentPosition ?: 0L, blockId, - viewModel.courseId + viewModel.courseId, + viewModel.isPlaying ) - viewModel.fullscreenHandled = true } } } override fun onResume() { super.onResume() - if (orientationListener?.canDetectOrientation() == true) { - orientationListener?.enable() - } exoPlayer?.addListener(exoPlayerListener) } @@ -249,7 +221,6 @@ class VideoUnitFragment : Fragment(R.layout.fragment_video_unit) { super.onPause() exoPlayer?.removeListener(exoPlayerListener) exoPlayer?.pause() - orientationListener?.disable() } override fun onDestroyView() { diff --git a/course/src/main/java/org/openedx/course/presentation/unit/video/VideoUnitViewModel.kt b/course/src/main/java/org/openedx/course/presentation/unit/video/VideoUnitViewModel.kt index 1f9f47f1f..b5d949082 100644 --- a/course/src/main/java/org/openedx/course/presentation/unit/video/VideoUnitViewModel.kt +++ b/course/src/main/java/org/openedx/course/presentation/unit/video/VideoUnitViewModel.kt @@ -4,6 +4,9 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch import org.openedx.core.AppDataConstants import org.openedx.core.BaseViewModel import org.openedx.core.module.TranscriptManager @@ -12,10 +15,6 @@ import org.openedx.core.system.notifier.CourseNotifier import org.openedx.core.system.notifier.CourseSubtitleLanguageChanged import org.openedx.core.system.notifier.CourseVideoPositionChanged import org.openedx.course.data.repository.CourseRepository -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.launch import subtitleFile.TimedTextObject class VideoUnitViewModel( @@ -28,11 +27,10 @@ class VideoUnitViewModel( var videoUrl = "" var transcripts = emptyMap() + var isPlaying = false var transcriptLanguage = AppDataConstants.defaultLocale.language ?: "en" private set - var fullscreenHandled = false - var isDownloaded = false private val _currentVideoTime = MutableLiveData(0) @@ -43,10 +41,6 @@ class VideoUnitViewModel( val isUpdated: LiveData get() = _isUpdated - private val _isPopUpViewShow = MutableLiveData(true) - val isPopUpViewShow: LiveData - get() = _isPopUpViewShow - private val _currentIndex = MutableStateFlow(0) val currentIndex = _currentIndex.asStateFlow() @@ -61,13 +55,6 @@ class VideoUnitViewModel( private var isBlockAlreadyCompleted = false - init { - viewModelScope.launch { - delay(4000) - _isPopUpViewShow.value = false - } - } - override fun onCreate(owner: LifecycleOwner) { super.onCreate(owner) viewModelScope.launch { @@ -76,6 +63,7 @@ class VideoUnitViewModel( _isUpdated.value = false _currentVideoTime.value = it.videoTime _isUpdated.value = true + isPlaying = it.isPlaying } else if (it is CourseSubtitleLanguageChanged) { transcriptLanguage = it.value _transcriptObject.value = null diff --git a/course/src/main/java/org/openedx/course/presentation/unit/video/VideoViewModel.kt b/course/src/main/java/org/openedx/course/presentation/unit/video/VideoViewModel.kt index 720013a70..79f4e8acc 100644 --- a/course/src/main/java/org/openedx/course/presentation/unit/video/VideoViewModel.kt +++ b/course/src/main/java/org/openedx/course/presentation/unit/video/VideoViewModel.kt @@ -16,6 +16,7 @@ class VideoViewModel( var videoUrl = "" var currentVideoTime = 0L + var isPlaying: Boolean? = null private var isBlockAlreadyCompleted = false @@ -23,7 +24,7 @@ class VideoViewModel( fun sendTime() { if (currentVideoTime != C.TIME_UNSET) { viewModelScope.launch { - notifier.send(CourseVideoPositionChanged(videoUrl, currentVideoTime)) + notifier.send(CourseVideoPositionChanged(videoUrl, currentVideoTime, isPlaying ?: false)) } } } diff --git a/course/src/main/java/org/openedx/course/presentation/unit/video/YoutubeVideoFullScreenFragment.kt b/course/src/main/java/org/openedx/course/presentation/unit/video/YoutubeVideoFullScreenFragment.kt index 8ab9468ce..adc652dca 100644 --- a/course/src/main/java/org/openedx/course/presentation/unit/video/YoutubeVideoFullScreenFragment.kt +++ b/course/src/main/java/org/openedx/course/presentation/unit/video/YoutubeVideoFullScreenFragment.kt @@ -1,7 +1,5 @@ package org.openedx.course.presentation.unit.video -import android.annotation.SuppressLint -import android.content.pm.ActivityInfo import android.os.Bundle import android.view.View import android.widget.FrameLayout @@ -15,14 +13,12 @@ import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.listeners.Abs import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.options.IFramePlayerOptions import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.utils.YouTubePlayerTracker import com.pierfrancescosoffritti.androidyoutubeplayer.core.ui.DefaultPlayerUiController +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf import org.openedx.core.extension.requestApplyInsetsWhenAttached -import org.openedx.core.presentation.global.WindowSizeHolder import org.openedx.core.presentation.global.viewBinding -import org.openedx.core.ui.WindowType import org.openedx.course.R import org.openedx.course.databinding.FragmentYoutubeVideoFullScreenBinding -import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.core.parameter.parametersOf class YoutubeVideoFullScreenFragment : Fragment(R.layout.fragment_youtube_video_full_screen) { @@ -33,11 +29,9 @@ class YoutubeVideoFullScreenFragment : Fragment(R.layout.fragment_youtube_video_ } private var blockId = "" - private var isTabletDevice = false private val youtubeTrackerListener = YouTubePlayerTracker() - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewModel.videoUrl = requireArguments().getString(ARG_BLOCK_VIDEO_URL, "") @@ -45,15 +39,8 @@ class YoutubeVideoFullScreenFragment : Fragment(R.layout.fragment_youtube_video_ if (viewModel.currentVideoTime == 0L) { viewModel.currentVideoTime = requireArguments().getLong(ARG_VIDEO_TIME, 0) } - setOrientationBasedOnDeviceType() - } - - private fun setOrientationBasedOnDeviceType() { - val windowSize = requireActivity() as WindowSizeHolder - isTabletDevice = windowSize.windowSize.width != WindowType.Compact && - windowSize.windowSize.height != WindowType.Compact - if (!isTabletDevice) { - requireActivity().requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR + if (viewModel.isPlaying == null) { + viewModel.isPlaying = requireArguments().getBoolean(ARG_IS_PLAYING) } } @@ -90,6 +77,11 @@ class YoutubeVideoFullScreenFragment : Fragment(R.layout.fragment_youtube_video_ if (state == PlayerConstants.PlayerState.ENDED) { viewModel.markBlockCompleted(blockId) } + viewModel.isPlaying = when(state) { + PlayerConstants.PlayerState.PLAYING -> true + PlayerConstants.PlayerState.PAUSED -> false + else -> return + } } override fun onCurrentSecond(youTubePlayer: YouTubePlayer, second: Float) { @@ -104,8 +96,7 @@ class YoutubeVideoFullScreenFragment : Fragment(R.layout.fragment_youtube_video_ override fun onReady(youTubePlayer: YouTubePlayer) { super.onReady(youTubePlayer) binding.youtubePlayerView.isVisible = true - val defPlayerUiController = - DefaultPlayerUiController(binding.youtubePlayerView, youTubePlayer) + val defPlayerUiController = DefaultPlayerUiController(binding.youtubePlayerView, youTubePlayer) defPlayerUiController.setFullScreenButtonClickListener { parentFragmentManager.popBackStack() } @@ -113,7 +104,11 @@ class YoutubeVideoFullScreenFragment : Fragment(R.layout.fragment_youtube_video_ binding.youtubePlayerView.setCustomPlayerUi(defPlayerUiController.rootView) val videoId = viewModel.videoUrl.split("watch?v=")[1] - youTubePlayer.loadVideo(videoId, viewModel.currentVideoTime.toFloat() / 1000) + if (viewModel.isPlaying == true) { + youTubePlayer.loadVideo(videoId, viewModel.currentVideoTime.toFloat() / 1000) + } else { + youTubePlayer.cueVideo(videoId, viewModel.currentVideoTime.toFloat() / 1000) + } youTubePlayer.addListener(youtubeTrackerListener) } @@ -126,33 +121,27 @@ class YoutubeVideoFullScreenFragment : Fragment(R.layout.fragment_youtube_video_ super.onDestroyView() } - @SuppressLint("SourceLockedOrientationActivity") - override fun onDestroy() { - if (!isTabletDevice) { - requireActivity().requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - } - super.onDestroy() - } - companion object { private const val ARG_BLOCK_VIDEO_URL = "blockVideoUrl" private const val ARG_VIDEO_TIME = "videoTime" private const val ARG_BLOCK_ID = "blockID" private const val ARG_COURSE_ID = "courseId" + private const val ARG_IS_PLAYING = "isPlaying" fun newInstance( videoUrl: String, videoTime: Long, blockId: String, courseId: String, + isPlaying: Boolean ): YoutubeVideoFullScreenFragment { val fragment = YoutubeVideoFullScreenFragment() fragment.arguments = bundleOf( ARG_BLOCK_VIDEO_URL to videoUrl, ARG_VIDEO_TIME to videoTime, ARG_BLOCK_ID to blockId, - ARG_COURSE_ID to courseId - + ARG_COURSE_ID to courseId, + ARG_IS_PLAYING to isPlaying ) return fragment } diff --git a/course/src/main/java/org/openedx/course/presentation/unit/video/YoutubeVideoUnitFragment.kt b/course/src/main/java/org/openedx/course/presentation/unit/video/YoutubeVideoUnitFragment.kt index 7931834ce..505128eac 100644 --- a/course/src/main/java/org/openedx/course/presentation/unit/video/YoutubeVideoUnitFragment.kt +++ b/course/src/main/java/org/openedx/course/presentation/unit/video/YoutubeVideoUnitFragment.kt @@ -2,7 +2,6 @@ package org.openedx.course.presentation.unit.video import android.os.Bundle import android.view.LayoutInflater -import android.view.OrientationEventListener import android.view.View import android.view.ViewGroup import androidx.compose.foundation.background @@ -16,11 +15,15 @@ import androidx.compose.ui.Modifier import androidx.core.os.bundleOf import androidx.core.view.isVisible import androidx.fragment.app.Fragment +import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.PlayerConstants import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.YouTubePlayer import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.listeners.AbstractYouTubePlayerListener import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.options.IFramePlayerOptions import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.utils.YouTubePlayerTracker import com.pierfrancescosoffritti.androidyoutubeplayer.core.ui.DefaultPlayerUiController +import org.koin.android.ext.android.inject +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf import org.openedx.core.extension.computeWindowSizeClasses import org.openedx.core.extension.objectToString import org.openedx.core.extension.stringToObject @@ -33,12 +36,8 @@ import org.openedx.course.R import org.openedx.course.databinding.FragmentYoutubeVideoUnitBinding import org.openedx.course.presentation.CourseRouter import org.openedx.course.presentation.ui.ConnectionErrorView -import org.openedx.course.presentation.ui.VideoRotateView import org.openedx.course.presentation.ui.VideoSubtitles import org.openedx.course.presentation.ui.VideoTitle -import org.koin.android.ext.android.inject -import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.core.parameter.parametersOf class YoutubeVideoUnitFragment : Fragment(R.layout.fragment_youtube_video_unit) { @@ -50,7 +49,6 @@ class YoutubeVideoUnitFragment : Fragment(R.layout.fragment_youtube_video_unit) private val binding get() = _binding!! private var windowSize: WindowSize? = null - private var orientationListener: OrientationEventListener? = null private var _youTubePlayer: YouTubePlayer? = null private var blockId = "" @@ -71,26 +69,6 @@ class YoutubeVideoUnitFragment : Fragment(R.layout.fragment_youtube_video_unit) blockId = getString(ARG_BLOCK_ID, "") } viewModel.downloadSubtitles() - orientationListener = object : OrientationEventListener(requireActivity()) { - override fun onOrientationChanged(orientation: Int) { - if (windowSize?.isTablet != true) { - if (orientation in 75..300) { - if (!viewModel.fullscreenHandled) { - router.navigateToFullScreenYoutubeVideo( - requireActivity().supportFragmentManager, - viewModel.videoUrl, - viewModel.getCurrentVideoTime(), - blockId, - viewModel.courseId - ) - viewModel.fullscreenHandled = true - } - } else { - viewModel.fullscreenHandled = false - } - } - } - } } override fun onCreateView( @@ -105,13 +83,7 @@ class YoutubeVideoUnitFragment : Fragment(R.layout.fragment_youtube_video_unit) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - binding.cvRotateHelper.setContent { - OpenEdXTheme { - VideoRotateView() - } - } - - binding.cvVideoTitle.setContent { + binding.cvVideoTitle?.setContent { OpenEdXTheme { VideoTitle(text = requireArguments().getString(ARG_TITLE) ?: "") } @@ -180,6 +152,15 @@ class YoutubeVideoUnitFragment : Fragment(R.layout.fragment_youtube_video_unit) } } + override fun onStateChange(youTubePlayer: YouTubePlayer, state: PlayerConstants.PlayerState) { + super.onStateChange(youTubePlayer, state) + viewModel.isPlaying = when(state) { + PlayerConstants.PlayerState.PLAYING -> true + PlayerConstants.PlayerState.PAUSED -> false + else -> return + } + } + override fun onReady(youTubePlayer: YouTubePlayer) { super.onReady(youTubePlayer) _youTubePlayer = youTubePlayer @@ -189,20 +170,24 @@ class YoutubeVideoUnitFragment : Fragment(R.layout.fragment_youtube_video_unit) youTubePlayer ) defPlayerUiController.setFullScreenButtonClickListener { - viewModel.fullscreenHandled = true router.navigateToFullScreenYoutubeVideo( requireActivity().supportFragmentManager, viewModel.videoUrl, viewModel.getCurrentVideoTime(), blockId, - viewModel.courseId + viewModel.courseId, + viewModel.isPlaying ) } binding.youtubePlayerView.setCustomPlayerUi(defPlayerUiController.rootView) } val videoId = viewModel.videoUrl.split("watch?v=")[1] - youTubePlayer.cueVideo(videoId, viewModel.getCurrentVideoTime().toFloat() / 1000) + if (viewModel.isPlaying) { + youTubePlayer.loadVideo(videoId, viewModel.getCurrentVideoTime().toFloat() / 1000) + } else { + youTubePlayer.cueVideo(videoId, viewModel.getCurrentVideoTime().toFloat() / 1000) + } youTubePlayer.addListener(youtubeTrackerListener) } } @@ -211,25 +196,11 @@ class YoutubeVideoUnitFragment : Fragment(R.layout.fragment_youtube_video_unit) binding.youtubePlayerView.initialize(listener, options) isPlayerInitialized = true } - - viewModel.isPopUpViewShow.observe(viewLifecycleOwner) { - if (windowSize?.isTablet != true) { - binding.cvRotateHelper.isVisible = it - } - } - } - - override fun onResume() { - super.onResume() - if (orientationListener?.canDetectOrientation() == true) { - orientationListener?.enable() - } } override fun onPause() { super.onPause() _youTubePlayer?.pause() - orientationListener?.disable() } override fun onDestroyView() { diff --git a/course/src/main/java/org/openedx/course/presentation/videos/CourseVideosFragment.kt b/course/src/main/java/org/openedx/course/presentation/videos/CourseVideosFragment.kt index 83faabd68..787665f29 100644 --- a/course/src/main/java/org/openedx/course/presentation/videos/CourseVideosFragment.kt +++ b/course/src/main/java/org/openedx/course/presentation/videos/CourseVideosFragment.kt @@ -203,7 +203,8 @@ private fun CourseVideosScreen( modifier = Modifier .fillMaxSize() .padding(it) - .statusBarsInset(), + .statusBarsInset() + .displayCutoutForLandscape(), contentAlignment = Alignment.TopCenter ) { Column(screenWidth) { diff --git a/course/src/main/res/layout-land/fragment_course_unit_container.xml b/course/src/main/res/layout-land/fragment_course_unit_container.xml new file mode 100644 index 000000000..f259583a3 --- /dev/null +++ b/course/src/main/res/layout-land/fragment_course_unit_container.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/course/src/main/res/layout-land/fragment_video_unit.xml b/course/src/main/res/layout-land/fragment_video_unit.xml new file mode 100644 index 000000000..c80b2fdc4 --- /dev/null +++ b/course/src/main/res/layout-land/fragment_video_unit.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/course/src/main/res/layout-land/fragment_youtube_video_unit.xml b/course/src/main/res/layout-land/fragment_youtube_video_unit.xml new file mode 100644 index 000000000..b8d5984c2 --- /dev/null +++ b/course/src/main/res/layout-land/fragment_youtube_video_unit.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/course/src/main/res/layout-w600dp-h480dp/fragment_course_unit_container.xml b/course/src/main/res/layout-w600dp-h480dp/fragment_course_unit_container.xml index 8dbf3ac6f..072f897ae 100644 --- a/course/src/main/res/layout-w600dp-h480dp/fragment_course_unit_container.xml +++ b/course/src/main/res/layout-w600dp-h480dp/fragment_course_unit_container.xml @@ -45,7 +45,7 @@ - - - - - + app:layout_constraintTop_toTopOf="parent" /> diff --git a/course/src/main/res/layout/fragment_course_unit_container.xml b/course/src/main/res/layout/fragment_course_unit_container.xml index a99cdb98c..0e63af231 100644 --- a/course/src/main/res/layout/fragment_course_unit_container.xml +++ b/course/src/main/res/layout/fragment_course_unit_container.xml @@ -34,7 +34,7 @@ - - - - - + app:layout_constraintTop_toTopOf="parent" /> diff --git a/course/src/main/res/layout/fragment_video_unit.xml b/course/src/main/res/layout/fragment_video_unit.xml index 45227eff0..9c1e2e1d5 100644 --- a/course/src/main/res/layout/fragment_video_unit.xml +++ b/course/src/main/res/layout/fragment_video_unit.xml @@ -51,17 +51,6 @@ app:layout_constraintTop_toBottomOf="@id/cardView" /> - - - - + app:layout_constraintTop_toBottomOf="@id/cardView" /> Далі Наступна одиниця Завершити - Поверніть свій пристрій для перегляду цього відео на повний екран. Цей курс не містить відео. Остання одиниця: Продовжити diff --git a/course/src/main/res/values/strings.xml b/course/src/main/res/values/strings.xml index a9fb56914..454cbcb60 100644 --- a/course/src/main/res/values/strings.xml +++ b/course/src/main/res/values/strings.xml @@ -19,7 +19,6 @@ Next Next Unit Finish - Rotate your device to view this video in full screen. This course does not include any videos. Last unit: Resume diff --git a/course/src/test/java/org/openedx/course/presentation/unit/video/VideoUnitViewModelTest.kt b/course/src/test/java/org/openedx/course/presentation/unit/video/VideoUnitViewModelTest.kt index 30a06d0c7..a77b6ae38 100644 --- a/course/src/test/java/org/openedx/course/presentation/unit/video/VideoUnitViewModelTest.kt +++ b/course/src/test/java/org/openedx/course/presentation/unit/video/VideoUnitViewModelTest.kt @@ -107,7 +107,7 @@ class VideoUnitViewModelTest { networkConnection, transcriptManager ) - coEvery { notifier.notifier } returns flow { emit(CourseVideoPositionChanged("", 10)) } + coEvery { notifier.notifier } returns flow { emit(CourseVideoPositionChanged("", 10, false)) } val mockLifeCycleOwner: LifecycleOwner = mockk() val lifecycleRegistry = LifecycleRegistry(mockLifeCycleOwner) lifecycleRegistry.addObserver(viewModel) diff --git a/course/src/test/java/org/openedx/course/presentation/unit/video/VideoViewModelTest.kt b/course/src/test/java/org/openedx/course/presentation/unit/video/VideoViewModelTest.kt index e517d192a..144698ec5 100644 --- a/course/src/test/java/org/openedx/course/presentation/unit/video/VideoViewModelTest.kt +++ b/course/src/test/java/org/openedx/course/presentation/unit/video/VideoViewModelTest.kt @@ -40,11 +40,11 @@ class VideoViewModelTest { @Test fun `sendTime test`() = runTest { val viewModel = VideoViewModel("", courseRepository, notifier) - coEvery { notifier.send(CourseVideoPositionChanged("", 0)) } returns Unit + coEvery { notifier.send(CourseVideoPositionChanged("", 0, false)) } returns Unit viewModel.sendTime() advanceUntilIdle() - coVerify(exactly = 1) { notifier.send(CourseVideoPositionChanged("", 0)) } + coVerify(exactly = 1) { notifier.send(CourseVideoPositionChanged("", 0, false)) } } @Test diff --git a/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardFragment.kt b/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardFragment.kt index 45a81e378..33f5b280a 100644 --- a/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardFragment.kt +++ b/dashboard/src/main/java/org/openedx/dashboard/presentation/DashboardFragment.kt @@ -39,7 +39,8 @@ import androidx.compose.ui.unit.dp import androidx.fragment.app.Fragment import coil.compose.AsyncImage import coil.request.ImageRequest -import org.openedx.core.BuildConfig +import org.koin.android.ext.android.inject +import org.koin.androidx.viewmodel.ext.android.viewModel import org.openedx.core.UIMessage import org.openedx.core.domain.model.* import org.openedx.core.ui.* @@ -49,8 +50,6 @@ import org.openedx.core.ui.theme.appShapes import org.openedx.core.ui.theme.appTypography import org.openedx.core.utils.TimeUtils import org.openedx.dashboard.R -import org.koin.android.ext.android.inject -import org.koin.androidx.viewmodel.ext.android.viewModel import java.util.* class DashboardFragment : Fragment() { @@ -177,7 +176,8 @@ internal fun MyCoursesScreen( Column( modifier = Modifier .padding(paddingValues) - .statusBarsInset(), + .statusBarsInset() + .displayCutoutForLandscape(), horizontalAlignment = Alignment.CenterHorizontally ) { Text( diff --git a/discovery/src/main/java/org/openedx/discovery/presentation/DiscoveryFragment.kt b/discovery/src/main/java/org/openedx/discovery/presentation/DiscoveryFragment.kt index 2d7809826..1dee5a0af 100644 --- a/discovery/src/main/java/org/openedx/discovery/presentation/DiscoveryFragment.kt +++ b/discovery/src/main/java/org/openedx/discovery/presentation/DiscoveryFragment.kt @@ -160,7 +160,8 @@ internal fun DiscoveryScreen( Modifier .fillMaxSize() .padding(it) - .statusBarsInset(), + .statusBarsInset() + .displayCutoutForLandscape(), horizontalAlignment = Alignment.CenterHorizontally ) { Column( diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/comments/DiscussionCommentsFragment.kt b/discussion/src/main/java/org/openedx/discussion/presentation/comments/DiscussionCommentsFragment.kt index 04f3dcffa..abd5fc5e3 100644 --- a/discussion/src/main/java/org/openedx/discussion/presentation/comments/DiscussionCommentsFragment.kt +++ b/discussion/src/main/java/org/openedx/discussion/presentation/comments/DiscussionCommentsFragment.kt @@ -27,6 +27,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.pluralStringResource @@ -206,6 +208,8 @@ private fun DiscussionCommentsScreen( .navigationBarsPadding(), backgroundColor = MaterialTheme.appColors.background ) { + val keyboardController = LocalSoftwareKeyboardController.current + val focusManager = LocalFocusManager.current val screenWidth by remember(key1 = windowSize) { mutableStateOf( @@ -242,7 +246,8 @@ private fun DiscussionCommentsScreen( ) { Box( modifier = Modifier - .fillMaxWidth(), + .fillMaxWidth() + .displayCutoutForLandscape(), contentAlignment = Alignment.CenterStart, ) { BackBtn { @@ -277,6 +282,7 @@ private fun DiscussionCommentsScreen( Modifier .then(screenWidth) .weight(1f) + .displayCutoutForLandscape() .background(MaterialTheme.appColors.background), verticalArrangement = Arrangement.spacedBy(16.dp), horizontalAlignment = Alignment.CenterHorizontally, @@ -363,7 +369,8 @@ private fun DiscussionCommentsScreen( .then(screenWidth) .heightIn(84.dp, Dp.Unspecified) .padding(top = 16.dp, bottom = 24.dp) - .padding(horizontal = 24.dp), + .padding(horizontal = 24.dp) + .displayCutoutForLandscape(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp) ) { @@ -398,6 +405,8 @@ private fun DiscussionCommentsScreen( .clip(CircleShape) .background(sendButtonColor) .clickable { + keyboardController?.hide() + focusManager.clearFocus() if (responseValue.isNotEmpty()) { onAddResponseClick(responseValue.trim()) responseValue = "" diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/responses/DiscussionResponsesFragment.kt b/discussion/src/main/java/org/openedx/discussion/presentation/responses/DiscussionResponsesFragment.kt index f668ed391..cafcf1cb2 100644 --- a/discussion/src/main/java/org/openedx/discussion/presentation/responses/DiscussionResponsesFragment.kt +++ b/discussion/src/main/java/org/openedx/discussion/presentation/responses/DiscussionResponsesFragment.kt @@ -27,6 +27,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.pluralStringResource @@ -39,6 +41,8 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.core.os.bundleOf import androidx.fragment.app.Fragment +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf import org.koin.android.ext.android.inject import org.openedx.core.UIMessage import org.openedx.core.domain.model.ProfileImage @@ -52,8 +56,6 @@ import org.openedx.core.ui.theme.appTypography import org.openedx.discussion.domain.model.DiscussionComment import org.openedx.discussion.presentation.comments.DiscussionCommentsFragment import org.openedx.discussion.presentation.ui.CommentMainItem -import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.core.parameter.parametersOf import org.openedx.discussion.presentation.DiscussionRouter import org.openedx.discussion.R as discussionR @@ -169,6 +171,9 @@ private fun DiscussionResponsesScreen( ) { val scaffoldState = rememberScaffoldState() val scrollState = rememberLazyListState() + val keyboardController = LocalSoftwareKeyboardController.current + val focusManager = LocalFocusManager.current + val firstVisibleIndex = remember { mutableStateOf(scrollState.firstVisibleItemIndex) } @@ -242,7 +247,8 @@ private fun DiscussionResponsesScreen( ) { Box( modifier = Modifier - .fillMaxWidth(), + .fillMaxWidth() + .displayCutoutForLandscape(), contentAlignment = Alignment.CenterStart, ) { BackBtn { @@ -276,7 +282,8 @@ private fun DiscussionResponsesScreen( Column( Modifier .fillMaxWidth() - .weight(1f), + .weight(1f) + .displayCutoutForLandscape(), horizontalAlignment = Alignment.CenterHorizontally ) { LazyColumn( @@ -382,7 +389,8 @@ private fun DiscussionResponsesScreen( .then(screenWidth) .heightIn(84.dp, Dp.Unspecified) .padding(top = 16.dp, bottom = 24.dp) - .padding(horizontal = 24.dp), + .padding(horizontal = 24.dp) + .displayCutoutForLandscape(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp) ) { @@ -417,6 +425,8 @@ private fun DiscussionResponsesScreen( .clip(CircleShape) .background(sendButtonColor) .clickable { + keyboardController?.hide() + focusManager.clearFocus() if (commentValue.isNotEmpty()) { addCommentClick(commentValue.trim()) commentValue = "" diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionAddThreadFragment.kt b/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionAddThreadFragment.kt index 9cffcd75a..dda5a41f4 100644 --- a/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionAddThreadFragment.kt +++ b/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionAddThreadFragment.kt @@ -236,7 +236,8 @@ private fun DiscussionAddThreadScreen( modifier = Modifier .fillMaxSize() .padding(it) - .statusBarsInset(), + .statusBarsInset() + .displayCutoutForLandscape(), contentAlignment = Alignment.TopCenter ) { Column( diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionThreadsFragment.kt b/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionThreadsFragment.kt index 7922919fa..fb1d05a01 100644 --- a/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionThreadsFragment.kt +++ b/discussion/src/main/java/org/openedx/discussion/presentation/threads/DiscussionThreadsFragment.kt @@ -10,9 +10,10 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material.* - import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState @@ -24,7 +25,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.ViewCompositionStrategy @@ -39,6 +39,10 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.core.os.bundleOf import androidx.fragment.app.Fragment +import kotlinx.coroutines.launch +import org.koin.android.ext.android.inject +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf import org.openedx.core.FragmentViewType import org.openedx.core.UIMessage import org.openedx.core.extension.TextConverter @@ -47,10 +51,6 @@ import org.openedx.core.ui.theme.* import org.openedx.discussion.domain.model.DiscussionType import org.openedx.discussion.presentation.DiscussionRouter import org.openedx.discussion.presentation.ui.ThreadItem -import kotlinx.coroutines.launch -import org.koin.android.ext.android.inject -import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.core.parameter.parametersOf import org.openedx.discussion.R as discussionR class DiscussionThreadsFragment : Fragment() { @@ -332,6 +332,7 @@ private fun DiscussionThreadsScreen( modifier = Modifier .fillMaxSize() .padding(it) + .displayCutoutForLandscape() .then(statusBarInsets), contentAlignment = Alignment.TopCenter ) { @@ -516,69 +517,69 @@ private fun DiscussionThreadsScreen( } } } else { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.TopStart + val noDiscussionsScrollState = rememberScrollState() + Column( + modifier = Modifier + .align(Alignment.CenterHorizontally) + .verticalScroll(noDiscussionsScrollState) + .padding(24.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center ) { Text( modifier = Modifier - .padding(start = 24.dp, top = 32.dp), + .fillMaxWidth(), text = title, color = MaterialTheme.appColors.textPrimary, style = MaterialTheme.appTypography.titleLarge ) - Column( - modifier = Modifier.align(Alignment.Center), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - Icon( - modifier = Modifier.size(100.dp), - painter = painterResource(id = discussionR.drawable.discussion_ic_empty), - contentDescription = null, - tint = MaterialTheme.appColors.textPrimary - ) - Spacer(Modifier.height(36.dp)) - Text( - modifier = Modifier.fillMaxWidth(), - text = stringResource(discussionR.string.discussion_no_yet), - style = MaterialTheme.appTypography.titleLarge, - color = MaterialTheme.appColors.textPrimary, - textAlign = TextAlign.Center - ) - Spacer(Modifier.height(12.dp)) - Text( - modifier = Modifier.fillMaxWidth(), - text = stringResource(discussionR.string.discussion_click_button_create_discussion), - style = MaterialTheme.appTypography.bodyLarge, - color = MaterialTheme.appColors.textPrimary, - textAlign = TextAlign.Center - ) - Spacer(Modifier.height(40.dp)) - OpenEdXOutlinedButton( - modifier = Modifier - .widthIn(184.dp, Dp.Unspecified), - text = stringResource(id = discussionR.string.discussion_create_post), - onClick = { - onCreatePostClick() - }, - content = { - Icon( - painter = painterResource(id = discussionR.drawable.discussion_ic_add_comment), - contentDescription = null, - tint = MaterialTheme.appColors.primary - ) - Spacer(modifier = Modifier.width(6.dp)) - Text( - text = stringResource(id = discussionR.string.discussion_create_post), - color = MaterialTheme.appColors.primary, - style = MaterialTheme.appTypography.labelLarge - ) - }, - borderColor = MaterialTheme.appColors.primary, - textColor = MaterialTheme.appColors.primary - ) - } + Spacer(modifier = Modifier.height(20.dp)) + Icon( + modifier = Modifier.size(100.dp), + painter = painterResource(id = discussionR.drawable.discussion_ic_empty), + contentDescription = null, + tint = MaterialTheme.appColors.textPrimary + ) + Spacer(Modifier.height(36.dp)) + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(discussionR.string.discussion_no_yet), + style = MaterialTheme.appTypography.titleLarge, + color = MaterialTheme.appColors.textPrimary, + textAlign = TextAlign.Center + ) + Spacer(Modifier.height(12.dp)) + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(discussionR.string.discussion_click_button_create_discussion), + style = MaterialTheme.appTypography.bodyLarge, + color = MaterialTheme.appColors.textPrimary, + textAlign = TextAlign.Center + ) + Spacer(Modifier.height(40.dp)) + OpenEdXOutlinedButton( + modifier = Modifier + .widthIn(184.dp, Dp.Unspecified), + text = stringResource(id = discussionR.string.discussion_create_post), + onClick = { + onCreatePostClick() + }, + content = { + Icon( + painter = painterResource(id = discussionR.drawable.discussion_ic_add_comment), + contentDescription = null, + tint = MaterialTheme.appColors.primary + ) + Spacer(modifier = Modifier.width(6.dp)) + Text( + text = stringResource(id = discussionR.string.discussion_create_post), + color = MaterialTheme.appColors.primary, + style = MaterialTheme.appTypography.labelLarge + ) + }, + borderColor = MaterialTheme.appColors.primary, + textColor = MaterialTheme.appColors.primary + ) } } } diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/topics/DiscussionTopicsFragment.kt b/discussion/src/main/java/org/openedx/discussion/presentation/topics/DiscussionTopicsFragment.kt index 6d2d7d4f1..d6af4393b 100644 --- a/discussion/src/main/java/org/openedx/discussion/presentation/topics/DiscussionTopicsFragment.kt +++ b/discussion/src/main/java/org/openedx/discussion/presentation/topics/DiscussionTopicsFragment.kt @@ -192,7 +192,8 @@ private fun DiscussionTopicsScreen( modifier = Modifier .fillMaxSize() .padding(it) - .statusBarsInset(), + .statusBarsInset() + .displayCutoutForLandscape(), contentAlignment = Alignment.TopCenter ) { Column(screenWidth) { diff --git a/discussion/src/main/java/org/openedx/discussion/presentation/ui/DiscussionUI.kt b/discussion/src/main/java/org/openedx/discussion/presentation/ui/DiscussionUI.kt index dd25a1284..285b01d66 100644 --- a/discussion/src/main/java/org/openedx/discussion/presentation/ui/DiscussionUI.kt +++ b/discussion/src/main/java/org/openedx/discussion/presentation/ui/DiscussionUI.kt @@ -504,8 +504,7 @@ fun ThreadItem( text = textType, painter = icon, color = MaterialTheme.appColors.textPrimaryVariant, - textStyle = MaterialTheme.appTypography.labelSmall, - onClick = {}) + textStyle = MaterialTheme.appTypography.labelSmall) if (thread.unreadCommentCount > 0 && !thread.read) { Row( modifier = Modifier, @@ -563,8 +562,7 @@ fun ThreadItem( ), painter = painterResource(id = R.drawable.discussion_ic_responses), color = MaterialTheme.appColors.textAccent, - textStyle = MaterialTheme.appTypography.labelLarge, - onClick = {}) + textStyle = MaterialTheme.appTypography.labelLarge) } } diff --git a/profile/src/main/java/org/openedx/profile/presentation/delete/DeleteProfileFragment.kt b/profile/src/main/java/org/openedx/profile/presentation/delete/DeleteProfileFragment.kt index f4ea03806..cf5a393ee 100644 --- a/profile/src/main/java/org/openedx/profile/presentation/delete/DeleteProfileFragment.kt +++ b/profile/src/main/java/org/openedx/profile/presentation/delete/DeleteProfileFragment.kt @@ -159,7 +159,8 @@ fun DeleteProfileScreen( Column( modifier = Modifier .padding(paddingValues) - .statusBarsInset(), + .statusBarsInset() + .displayCutoutForLandscape(), horizontalAlignment = Alignment.CenterHorizontally ) { Box( diff --git a/profile/src/main/java/org/openedx/profile/presentation/edit/EditProfileFragment.kt b/profile/src/main/java/org/openedx/profile/presentation/edit/EditProfileFragment.kt index 43515f98a..1423ea47e 100644 --- a/profile/src/main/java/org/openedx/profile/presentation/edit/EditProfileFragment.kt +++ b/profile/src/main/java/org/openedx/profile/presentation/edit/EditProfileFragment.kt @@ -41,6 +41,7 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Card import androidx.compose.material.Divider import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon @@ -77,6 +78,7 @@ import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalSoftwareKeyboardController @@ -107,12 +109,12 @@ import org.koin.core.parameter.parametersOf import org.openedx.core.AppDataConstants.DEFAULT_MIME_TYPE import org.openedx.core.R import org.openedx.core.UIMessage -import org.openedx.profile.domain.model.Account import org.openedx.core.domain.model.LanguageProficiency import org.openedx.core.domain.model.ProfileImage import org.openedx.core.domain.model.RegistrationField import org.openedx.core.extension.getFileName import org.openedx.core.extension.parcelable +import org.openedx.core.ui.AutoSizeText import org.openedx.core.ui.BackBtn import org.openedx.core.ui.HandleUIMessage import org.openedx.core.ui.IconText @@ -121,6 +123,7 @@ import org.openedx.core.ui.OpenEdXOutlinedButton import org.openedx.core.ui.SheetContent import org.openedx.core.ui.WindowSize import org.openedx.core.ui.WindowType +import org.openedx.core.ui.displayCutoutForLandscape import org.openedx.core.ui.isImeVisibleState import org.openedx.core.ui.noRippleClickable import org.openedx.core.ui.rememberSaveableMap @@ -132,6 +135,7 @@ import org.openedx.core.ui.theme.appShapes import org.openedx.core.ui.theme.appTypography import org.openedx.core.ui.windowSizeValue import org.openedx.core.utils.LocaleUtils +import org.openedx.profile.domain.model.Account import org.openedx.profile.presentation.ProfileRouter import java.io.ByteArrayOutputStream import java.io.File @@ -520,21 +524,34 @@ private fun EditProfileScreen( } if (leaveDialog) { - LeaveProfile( - onDismissRequest = { - onKeepEdit() - }, - onLeaveClick = { - onBackClick(false) - } - ) + val configuration = LocalConfiguration.current + if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT || windowSize.isTablet) { + LeaveProfile( + onDismissRequest = { + onKeepEdit() + }, + onLeaveClick = { + onBackClick(false) + } + ) + } else { + LeaveProfileLandscape( + onDismissRequest = { + onKeepEdit() + }, + onLeaveClick = { + onBackClick(false) + } + ) + } } Column( modifier = Modifier .fillMaxWidth() .padding(paddingValues) - .statusBarsInset(), + .statusBarsInset() + .displayCutoutForLandscape(), horizontalAlignment = Alignment.CenterHorizontally ) { Box( @@ -1035,12 +1052,14 @@ private fun LeaveProfile( onDismissRequest: () -> Unit, onLeaveClick: () -> Unit, ) { + val scrollState = rememberScrollState() Dialog( onDismissRequest = onDismissRequest, properties = DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false), content = { Column( Modifier + .verticalScroll(scrollState) .fillMaxWidth() .background( MaterialTheme.appColors.background, @@ -1102,6 +1121,112 @@ private fun LeaveProfile( }) } +@Composable +private fun LeaveProfileLandscape( + onDismissRequest: () -> Unit, + onLeaveClick: () -> Unit, +) { + val configuration = LocalConfiguration.current + val screenWidth = configuration.screenWidthDp.dp + Dialog( + onDismissRequest = onDismissRequest, + properties = DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false, usePlatformDefaultWidth = false), + content = { + Card( + modifier = Modifier + .width(screenWidth * 0.7f) + .clip(MaterialTheme.appShapes.courseImageShape), + backgroundColor = MaterialTheme.appColors.background, + shape = MaterialTheme.appShapes.courseImageShape + ) { + Row( + Modifier + .padding(horizontal = 40.dp) + .padding(top = 48.dp, bottom = 38.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Column( + Modifier.weight(1f), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + modifier = Modifier.size(100.dp), + painter = painterResource(id = org.openedx.profile.R.drawable.profile_ic_save), + contentDescription = null, + tint = MaterialTheme.appColors.onBackground + ) + Spacer(Modifier.height(20.dp)) + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(id = org.openedx.profile.R.string.profile_leave_profile), + color = MaterialTheme.appColors.textPrimary, + style = MaterialTheme.appTypography.titleLarge, + textAlign = TextAlign.Center + ) + Spacer(Modifier.height(8.dp)) + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(id = org.openedx.profile.R.string.profile_changes_you_made), + color = MaterialTheme.appColors.textFieldText, + style = MaterialTheme.appTypography.titleSmall, + textAlign = TextAlign.Center + ) + } + Spacer(Modifier.width(42.dp)) + Column( + Modifier.weight(1f), + horizontalAlignment = Alignment.CenterHorizontally + ) { + OpenEdXButton( + text = stringResource(id = org.openedx.profile.R.string.profile_leave), + backgroundColor = MaterialTheme.appColors.warning, + content = { + AutoSizeText( + text = stringResource(id = org.openedx.profile.R.string.profile_leave), + style = MaterialTheme.appTypography.bodyMedium, + color = MaterialTheme.appColors.textDark + ) + }, + onClick = onLeaveClick + ) + Spacer(Modifier.height(16.dp)) + OpenEdXOutlinedButton( + borderColor = MaterialTheme.appColors.textPrimary, + textColor = MaterialTheme.appColors.textPrimary, + text = stringResource(id = org.openedx.profile.R.string.profile_keep_editing), + onClick = onDismissRequest, + content = { + AutoSizeText( + text = stringResource(id = org.openedx.profile.R.string.profile_keep_editing), + style = MaterialTheme.appTypography.bodyMedium, + color = MaterialTheme.appColors.textPrimary + ) + } + ) + } + } + } + }) +} + +@Preview +@Composable +fun LeaveProfilePreview() { + LeaveProfile( + onDismissRequest = {}, + onLeaveClick = {} + ) +} + +@Preview +@Composable +fun LeaveProfileLandscapePreview() { + LeaveProfileLandscape( + onDismissRequest = {}, + onLeaveClick = {} + ) +} @Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) @Preview(uiMode = UI_MODE_NIGHT_YES) diff --git a/profile/src/main/java/org/openedx/profile/presentation/profile/ProfileFragment.kt b/profile/src/main/java/org/openedx/profile/presentation/profile/ProfileFragment.kt index 93ae78c75..1507fda36 100644 --- a/profile/src/main/java/org/openedx/profile/presentation/profile/ProfileFragment.kt +++ b/profile/src/main/java/org/openedx/profile/presentation/profile/ProfileFragment.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowForwardIos +import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.ExitToApp import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh @@ -190,7 +191,8 @@ private fun ProfileScreen( Column( modifier = Modifier .padding(paddingValues) - .statusBarsInset(), + .statusBarsInset() + .displayCutoutForLandscape(), horizontalAlignment = Alignment.CenterHorizontally ) { Box( @@ -417,6 +419,7 @@ private fun LogoutDialog( content = { Column( Modifier + .verticalScroll(rememberScrollState()) .fillMaxWidth() .background( MaterialTheme.appColors.background, @@ -428,10 +431,24 @@ private fun LogoutDialog( MaterialTheme.appColors.cardViewBorder, MaterialTheme.appShapes.cardShape ) - .padding(horizontal = 40.dp) - .padding(top = 48.dp, bottom = 36.dp), + .padding(horizontal = 40.dp, vertical = 36.dp), horizontalAlignment = Alignment.CenterHorizontally ) { + Box( + modifier = Modifier.fillMaxWidth(), + contentAlignment = Alignment.CenterEnd + ) { + IconButton( + modifier = Modifier.size(24.dp), + onClick = onDismissRequest + ) { + Icon( + imageVector = Icons.Filled.Close, + contentDescription = stringResource(id = R.string.core_cancel), + tint = MaterialTheme.appColors.primary + ) + } + } Icon( modifier = Modifier .width(88.dp) @@ -440,14 +457,14 @@ private fun LogoutDialog( contentDescription = null, tint = MaterialTheme.appColors.onBackground ) - Spacer(Modifier.size(40.dp)) + Spacer(Modifier.size(36.dp)) Text( text = stringResource(id = org.openedx.profile.R.string.profile_logout_dialog_body), color = MaterialTheme.appColors.textPrimary, style = MaterialTheme.appTypography.titleLarge, textAlign = TextAlign.Center ) - Spacer(Modifier.size(44.dp)) + Spacer(Modifier.size(36.dp)) OpenEdXButton( text = stringResource(id = org.openedx.profile.R.string.profile_logout), backgroundColor = MaterialTheme.appColors.warning, @@ -500,6 +517,12 @@ private fun ProfileInfoItem(text: String, onClick: () -> Unit) { } } +@Preview +@Composable +fun LogoutDialogPreview() { + LogoutDialog({}, {}) +} + @Preview(uiMode = UI_MODE_NIGHT_NO) @Preview(uiMode = UI_MODE_NIGHT_YES) @Preview(name = "NEXUS_5_Light", device = Devices.NEXUS_5, uiMode = UI_MODE_NIGHT_NO) diff --git a/profile/src/main/java/org/openedx/profile/presentation/settings/video/VideoQualityFragment.kt b/profile/src/main/java/org/openedx/profile/presentation/settings/video/VideoQualityFragment.kt index ccda6e9d2..7acb2a9c4 100644 --- a/profile/src/main/java/org/openedx/profile/presentation/settings/video/VideoQualityFragment.kt +++ b/profile/src/main/java/org/openedx/profile/presentation/settings/video/VideoQualityFragment.kt @@ -7,6 +7,8 @@ import android.view.LayoutInflater import android.view.ViewGroup import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Done @@ -108,7 +110,8 @@ private fun VideoQualityScreen( Column( modifier = Modifier .padding(paddingValues) - .statusBarsInset(), + .statusBarsInset() + .displayCutoutForLandscape(), horizontalAlignment = Alignment.CenterHorizontally ) { Box( @@ -129,7 +132,9 @@ private fun VideoQualityScreen( } Column( - modifier = Modifier.then(contentWidth), + modifier = Modifier + .then(contentWidth) + .verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally ) { val autoQuality = diff --git a/profile/src/main/java/org/openedx/profile/presentation/settings/video/VideoSettingsFragment.kt b/profile/src/main/java/org/openedx/profile/presentation/settings/video/VideoSettingsFragment.kt index 8816decd9..ca86d48ec 100644 --- a/profile/src/main/java/org/openedx/profile/presentation/settings/video/VideoSettingsFragment.kt +++ b/profile/src/main/java/org/openedx/profile/presentation/settings/video/VideoSettingsFragment.kt @@ -123,7 +123,8 @@ private fun VideoSettingsScreen( Column( modifier = Modifier .padding(paddingValues) - .statusBarsInset(), + .statusBarsInset() + .displayCutoutForLandscape(), horizontalAlignment = Alignment.CenterHorizontally ) { Box(