Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ import com.instructure.canvasapi2.utils.PendoInitCallbackHandler
import com.instructure.canvasapi2.utils.weave.apiAsync
import com.instructure.canvasapi2.utils.weave.catch
import com.instructure.canvasapi2.utils.weave.tryWeave
import com.instructure.canvasapi2.utils.RemoteConfigParam
import com.instructure.canvasapi2.utils.RemoteConfigUtils
import com.instructure.horizon.HorizonActivity
import com.instructure.ngc.NGCActivity
import com.instructure.loginapi.login.tasks.LogoutTask
import com.instructure.loginapi.login.util.QRLogin.performSSOLogin
import com.instructure.loginapi.login.util.QRLogin.verifySSOLoginUri
Expand Down Expand Up @@ -212,7 +215,12 @@ class InterwebsToApplication : BaseCanvasActivity() {
return@tryWeave
} else {
delay(700)
if (ApiPrefs.canvasCareerView.orDefault()) {
if (RemoteConfigUtils.getBoolean(RemoteConfigParam.NEXT_GEN_CANVAS)) {
val intent = Intent(this@InterwebsToApplication, NGCActivity::class.java)
intent.data = Uri.parse(url)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
} else if (ApiPrefs.canvasCareerView.orDefault()) {
val intent = Intent(this@InterwebsToApplication, HorizonActivity::class.java)
intent.data = Uri.parse(url)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,25 @@ import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Color
import com.instructure.instui.token.component.InstUIText
import com.instructure.instui.token.semantic.InstUISemanticColors

val LocalCourseColor = staticCompositionLocalOf<Color> {
Color.Unspecified
}

@Composable
fun InstUITheme(content: @Composable () -> Unit) {
fun InstUITheme(
courseColor: Color = LocalCourseColor.current,
content: @Composable () -> Unit,
) {
MaterialTheme {
CompositionLocalProvider(
LocalTextStyle provides InstUIText.content,
LocalContentColor provides InstUISemanticColors.Text.base(),
LocalCourseColor provides courseColor,
content = content
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* Copyright (C) 2026 - present Instructure, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.instructure.instui.compose.navigation

import android.content.res.Configuration
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.layout.width
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.IconButton
import androidx.compose.material3.LargeTopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.instructure.instui.compose.InstUITheme
import com.instructure.instui.compose.LocalCourseColor
import com.instructure.instui.compose.indicator.Icon
import com.instructure.instui.compose.text.Text
import com.instructure.instui.token.component.InstUIHeading
import com.instructure.instui.token.icon.InstUIIcons
import com.instructure.instui.token.icon.line.ArrowOpenLeft
import com.instructure.instui.token.semantic.InstUILayoutSizes
import com.instructure.instui.token.semantic.InstUISemanticColors

private val LeadingSize = InstUILayoutSizes.Size.Interactive.height_lg
private val LeadingGap = InstUILayoutSizes.Spacing.SpaceMd.spaceMd
private val TitleEndPadding = InstUILayoutSizes.Spacing.SpaceLg.spaceLg

/**
* InstUI collapsing top bar with optional leading content.
*
* Wraps Material3 [LargeTopAppBar] with InstUI tokens. When expanded, shows the
* [leading] content (image, color swatch, avatar, etc.) next to the title. As
* the user scrolls, the leading content shrinks and fades, the background
* transitions from base to [accentColor], and text/icon colors transition from
* base to onColor. Status bar icons toggle light/dark automatically.
*
* When the real design system component is ready, only this file's internals change.
*
* Usage:
* ```
* val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
*
* CollapsingTopBar(
* title = "Course Name",
* scrollBehavior = scrollBehavior,
* onNavigateBack = { navController.popBackStack() },
* leading = { CourseImage(imageUrl, courseColor) },
* )
* ```
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CollapsingTopBar(
title: String,
scrollBehavior: TopAppBarScrollBehavior,
modifier: Modifier = Modifier,
leading: (@Composable () -> Unit)? = null,
accentColor: Color = LocalCourseColor.current,
expandedHeight: Dp = 112.dp,
onNavigateBack: (() -> Unit)? = null,
) {
val collapsedFraction = scrollBehavior.state.collapsedFraction
val expandedFraction = 1f - collapsedFraction
val contentColor = lerp(
InstUISemanticColors.Text.base(),
InstUISemanticColors.Text.onColor(),
collapsedFraction,
)

LargeTopAppBar(
modifier = modifier,
expandedHeight = expandedHeight,
title = {
Row(verticalAlignment = Alignment.CenterVertically) {
if (leading != null) {
Box(
modifier = Modifier
.width(LeadingSize * expandedFraction)
.clipToBounds()
.alpha(expandedFraction),
) {
// Content stays at full size; outer Box clips as it shrinks
Box(modifier = Modifier.requiredWidth(LeadingSize)) {
leading()
}
}
Spacer(modifier = Modifier.width(LeadingGap * expandedFraction))
}
Text(
text = title,
style = InstUIHeading.titleCardMini,
maxLines = if (collapsedFraction > 0.5f) 1 else Int.MAX_VALUE,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(end = TitleEndPadding),
)
}
},
navigationIcon = {
if (onNavigateBack != null) {
IconButton(onClick = onNavigateBack) {
Icon(
imageVector = InstUIIcons.Line.ArrowOpenLeft,
tint = contentColor,
)
}
}
},
scrollBehavior = scrollBehavior,
colors = TopAppBarDefaults.topAppBarColors(
containerColor = InstUISemanticColors.Background.base(),
scrolledContainerColor = accentColor,
titleContentColor = contentColor,
navigationIconContentColor = contentColor,
),
)
}

@OptIn(ExperimentalMaterial3Api::class)
@Preview(name = "CollapsingTopBar — Light", showBackground = true)
@Preview(name = "CollapsingTopBar — Dark", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun CollapsingTopBarPreview() {
InstUITheme(courseColor = Color(0xFFBF5811)) {
CollapsingTopBar(
title = "Introduction to Space Stations",
scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(),
onNavigateBack = {},
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright (C) 2026 - present Instructure, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.instructure.instui.compose.navigation

import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SecondaryTabRow
import androidx.compose.material3.Tab
import androidx.compose.material3.TabRowDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.graphics.Color
import com.instructure.instui.compose.InstUITheme
import com.instructure.instui.compose.LocalCourseColor
import com.instructure.instui.compose.text.Text
import com.instructure.instui.token.component.InstUIHeading
import com.instructure.instui.token.component.InstUIText as InstUITextTokens
import com.instructure.instui.token.semantic.InstUISemanticColors

/**
* InstUI segmented control / tab bar.
*
* Wraps Material3 [SecondaryTabRow] with InstUI token colors and typography.
* When the real design system component is ready, only this file's internals change.
*
* Usage:
* ```
* SegmentedControl(
* tabs = listOf("Home", "Modules", "My Work", "More"),
* selectedIndex = 1,
* onTabSelected = { index -> },
* )
* ```
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SegmentedControl(
tabs: List<String>,
selectedIndex: Int,
onTabSelected: (Int) -> Unit,
modifier: Modifier = Modifier,
accentColor: Color = LocalCourseColor.current,
) {
SecondaryTabRow(
selectedTabIndex = selectedIndex,
modifier = modifier,
containerColor = InstUISemanticColors.Background.base(),
contentColor = accentColor,
indicator = {
TabRowDefaults.SecondaryIndicator(
modifier = Modifier.tabIndicatorOffset(selectedIndex),
color = accentColor,
)
},
divider = {},
) {
tabs.forEachIndexed { index, title ->
val selected = index == selectedIndex
Tab(
selected = selected,
onClick = { onTabSelected(index) },
text = {
Text(
text = title,
style = if (selected) InstUIHeading.titleCardMini else InstUITextTokens.content,
color = if (selected) accentColor else InstUISemanticColors.Text.base(),
)
},
)
}
}
}

@Preview(name = "SegmentedControl — Light", showBackground = true)
@Preview(name = "SegmentedControl — Dark", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun SegmentedControlPreview() {
InstUITheme(courseColor = Color(0xFFBF5811)) {
Column(
modifier = Modifier
.background(InstUISemanticColors.Background.base())
.padding(16.dp)
) {
SegmentedControl(
tabs = listOf("Home", "Modules", "My Work", "More"),
selectedIndex = 1,
onTabSelected = {},
)
}
}
}
1 change: 1 addition & 0 deletions libs/ngc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ android {

dependencies {
implementation(project(":pandautils"))
implementation(project(":instui"))

implementation(Libs.NAVIGATION_COMPOSE)
implementation(Libs.HILT)
Expand Down
Loading
Loading