Skip to content

Commit

Permalink
Feature/landscape (#68)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
PavloNetrebchuk and volodymyr-chekyrta authored Oct 16, 2023
1 parent 0557842 commit 495c1bd
Show file tree
Hide file tree
Showing 53 changed files with 1,102 additions and 525 deletions.
7 changes: 4 additions & 3 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@
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">
<activity
android:name=".AppActivity"
android:exported="true"
android:fitsSystemWindows="true"
android:screenOrientation="portrait"
android:theme="@style/Theme.App.Starting"
android:windowSoftInputMode="adjustPan">
<intent-filter>
Expand All @@ -47,10 +47,11 @@
android:resource="@xml/file_provider_paths" />
</provider>

<provider android:authorities="${applicationId}.firebaseinitprovider"
<provider
android:name="com.google.firebase.provider.FirebaseInitProvider"
android:authorities="${applicationId}.firebaseinitprovider"
android:exported="false"
tools:node="remove"/>
tools:node="remove" />
</application>

</manifest>
31 changes: 18 additions & 13 deletions app/src/main/java/org/openedx/app/AppActivity.kt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -21,17 +24,15 @@ 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 {

override val topInset: Int
get() = _insetTop
override val bottomInset: Int
get() = _insetBottom
override val cutoutInset: Int
get() = _insetCutout

override val windowSize: WindowSize
get() = _windowSize
Expand All @@ -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)
}

Expand All @@ -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 {
Expand All @@ -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()
Expand Down Expand Up @@ -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 {
Expand All @@ -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"
}
}
10 changes: 6 additions & 4 deletions app/src/main/java/org/openedx/app/AppRouter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)
}

Expand All @@ -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)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ private fun RestorePasswordScreen(
Column(
Modifier
.then(contentPaddings)
.displayCutoutForLandscape()
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Expand Down Expand Up @@ -247,6 +248,7 @@ private fun RestorePasswordScreen(
Column(
Modifier
.then(contentPaddings)
.displayCutoutForLandscape()
.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ private fun LoginScreen(
modifier = Modifier
.background(MaterialTheme.appColors.background)
.verticalScroll(scrollState)
.displayCutoutForLandscape()
.then(contentPaddings),
) {
Text(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -111,7 +110,6 @@ internal fun RegistrationScreen(
onRegisterClick: (Map<String, String?>) -> Unit
) {
val scaffoldState = rememberScaffoldState()
val configuration = LocalConfiguration.current
val focusManager = LocalFocusManager.current
val bottomSheetScaffoldState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden,
Expand Down Expand Up @@ -317,6 +315,7 @@ internal fun RegistrationScreen(
Modifier
.fillMaxHeight()
.verticalScroll(scrollState)
.displayCutoutForLandscape()
.then(contentPaddings),
verticalArrangement = Arrangement.spacedBy(24.dp),
horizontalAlignment = Alignment.CenterHorizontally
Expand Down
11 changes: 11 additions & 0 deletions core/src/main/java/org/openedx/core/extension/ViewExt.kt
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ package org.openedx.core.presentation.global
interface InsetHolder {
val topInset: Int
val bottomInset: Int
val cutoutInset: Int
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
58 changes: 56 additions & 2 deletions core/src/main/java/org/openedx/core/ui/ComposeExtensions.kt
Original file line number Diff line number Diff line change
@@ -1,25 +1,40 @@
package org.openedx.core.ui

import android.content.res.Configuration
import android.graphics.Rect
import android.view.ViewTreeObserver
import androidx.compose.foundation.MutatePriority
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
Expand Down Expand Up @@ -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,
Expand All @@ -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 <T : Any> rememberSaveableMap(init: () -> MutableMap<String, T?>): MutableMap<String, T?> {
return rememberSaveable(
Expand Down
Loading

0 comments on commit 495c1bd

Please sign in to comment.