Skip to content
Merged
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
4 changes: 4 additions & 0 deletions docs/.vale/writing-styles/expo-docs/HeadingCase.yml
Original file line number Diff line number Diff line change
Expand Up @@ -306,3 +306,7 @@ exceptions:
- Expo Skills
- DSL
- TriStateCheckbox
- MainApplication
- MainActivity
- HorizontalCenteredHeroCarousel
- HorizontalUncontainedCarousel
9 changes: 9 additions & 0 deletions docs/pages/eas/workflows/pre-packaged-jobs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,14 @@ jobs:

You can pass a list of environment variables into the `env` parameter. These environment variables will be pulled from [EAS environment variables](/eas/environment-variables/). The passed `environment` parameter will be used for the environment variable's environment, which is useful when the same environment variable is defined across different environments.

#### Parameters

You can pass the following parameters into the `params` list:

| Parameter | Type | Description |
| ----------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| unstable_skip_cng_check | boolean | Optional. Whether to skip the check for [Continuous Native Generation (CNG)](/workflow/continuous-native-generation/) compatibility. Defaults to `false`. |

#### Outputs

You can reference the following outputs in subsequent jobs:
Expand Down Expand Up @@ -1686,6 +1694,7 @@ You can pass the following parameters into the `params` list:
| build_id | string | Required. The source build ID of the build to repack. |
| profile | string | Optional. The build profile to use. Defaults to the profile of the source build retrieved from `build_id`. |
| embed_bundle_assets | boolean | Optional. Whether to embed the bundle assets in the repacked build. By default, this is automatically determined based on the source build. |
| js_bundle_only | boolean | Optional. Whether to only repack the JavaScript bundle. Defaults to false, which means the entire app metadata will be updated. |
| message | string | Optional. Custom message attached to the build. Corresponds to the `--message` flag when running `eas build`. |
| repack_version | string | Optional. The version of the `@expo/repack-app` to use. Defaults to the latest version. |

Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/expo-image-picker/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

### 🐛 Bug fixes

- [android] Handle edge-to-edge display in crop activity. ([#44208](https://github.com/expo/expo/pull/44208) by [@zoontek](https://github.com/zoontek))
- fix potential `null` mime type reported ([#43734](https://github.com/expo/expo/pull/43734) by [@vonovak](https://github.com/vonovak))
- [android] fix cropper default colors in light mode ([#42437](https://github.com/expo/expo/pull/42437) by [@fobos531](https://github.com/fobos531))
- [iOS] Fix `base64` result not being a JPEG data. ([#43806](https://github.com/expo/expo/pull/43806) by [@barthap](https://github.com/barthap))
Expand Down
1 change: 1 addition & 0 deletions packages/expo-image-picker/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ android {
dependencies {
implementation "androidx.activity:activity-ktx:1.11.0"
implementation "androidx.appcompat:appcompat:1.7.1"
implementation "androidx.core:core-ktx:1.17.0"
implementation "androidx.exifinterface:exifinterface:1.4.1"
implementation "com.vanniktech:android-image-cropper:4.7.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,18 @@ package expo.modules.imagepicker

import android.graphics.Color
import android.view.Menu
import android.view.View
import android.view.ViewGroup
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.updateLayoutParams
import com.canhub.cropper.CropImageActivity
import com.canhub.cropper.CropImageOptions
import com.canhub.cropper.CropImageView
import expo.modules.imagepicker.ExpoCropImageUtils.getColorResource
import expo.modules.imagepicker.ExpoCropImageUtils.getThemeColor

/**
* A wrapper around `CropImageActivity` to provide custom theming and functionality.
Expand All @@ -15,6 +25,7 @@ import com.canhub.cropper.CropImageOptions
*/
class ExpoCropImageActivity : CropImageActivity() {
private var currentIconColor: Int = Color.BLACK
private var cropImageViewRef: CropImageView? = null

// region Lifecycle Methods
override fun onCreate(savedInstanceState: android.os.Bundle?) {
Expand All @@ -23,12 +34,21 @@ class ExpoCropImageActivity : CropImageActivity() {
// the toolbar and menu icons are tinted correctly on first render.
getCropOptions()?.let { opts ->
val isNight = (resources.configuration.uiMode and android.content.res.Configuration.UI_MODE_NIGHT_MASK) == android.content.res.Configuration.UI_MODE_NIGHT_YES
applyPalette(isNight, opts)
applyCustomization(isNight, opts)
invokeSetCustomizations()
invalidateOptionsMenu() // Recreate the menu to apply the new icon colors.
}
}

override fun onDestroy() {
ViewCompat.setOnApplyWindowInsetsListener(window.decorView, null)

cropImageViewRef?.let { ViewCompat.setOnApplyWindowInsetsListener(it, null) }
cropImageViewRef = null

super.onDestroy()
}

override fun onCreateOptionsMenu(menu: Menu): Boolean {
val result = super.onCreateOptionsMenu(menu)
tintAllMenuItems(menu)
Expand All @@ -40,22 +60,88 @@ class ExpoCropImageActivity : CropImageActivity() {
tintAllMenuItems(menu)
return result
}

override fun setCropImageView(cropImageView: CropImageView) {
super.setCropImageView(cropImageView)

cropImageViewRef = cropImageView

// Inset the crop view margins so it doesn't overlap with system bars or display cutouts
ViewCompat.setOnApplyWindowInsetsListener(cropImageView) { view, insets ->
val values = insets.getInsets(
WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout())

view.updateLayoutParams<ViewGroup.MarginLayoutParams> {
setMargins(values.left, values.top, values.right, values.bottom)
}

insets
}
}

// endregion

private fun applyPalette(isNight: Boolean, opts: CropImageOptions) {
// Apply palette to options and get the toolbar widget color
val toolbarWidgetColor = ExpoCropImageUtils.applyPaletteToOptions(theme, resources, isNight, opts)
private fun applyCustomization(isNight: Boolean, options: CropImageOptions) {
val defaultBackgroundColor = if (isNight) Color.BLACK else Color.WHITE
val defaultContentColor = if (isNight) Color.WHITE else Color.BLACK

// Set the current icon color for menu tinting
currentIconColor = toolbarWidgetColor
// Try theme attributes first, then fall back to color resources
val expoCropBackButtonIconColor = getThemeColor(theme, R.attr.expoCropBackButtonIconColor)
?: getColorResource(resources, R.color.expoCropBackButtonIconColor)
val expoCropBackgroundColor = getThemeColor(theme, R.attr.expoCropBackgroundColor)
?: getColorResource(resources, R.color.expoCropBackgroundColor)
val expoCropToolbarActionTextColor = getThemeColor(theme, R.attr.expoCropToolbarActionTextColor)
?: getColorResource(resources, R.color.expoCropToolbarActionTextColor)
val expoCropToolbarColor = getThemeColor(theme, R.attr.expoCropToolbarColor)
?: getColorResource(resources, R.color.expoCropToolbarColor)
val expoCropToolbarIconColor = getThemeColor(theme, R.attr.expoCropToolbarIconColor)
?: getColorResource(resources, R.color.expoCropToolbarIconColor)

val activityBackgroundColor = expoCropBackgroundColor ?: defaultBackgroundColor
val toolbarColor = expoCropToolbarColor ?: defaultBackgroundColor
val toolbarIconColor = expoCropToolbarIconColor ?: defaultContentColor

// Set up toolbar color with fallback for status bar theming
val defaultToolbarColor = if (isNight) Color.BLACK else Color.WHITE
val toolbarColor = opts.toolbarColor ?: defaultToolbarColor
ExpoCropImageUtils.applyWindowTheming(window, toolbarColor, isNight)
// Set the current icon color for menu tinting
currentIconColor = toolbarIconColor

// Remove action bar elevation for a flat design
supportActionBar?.elevation = 0f

options.activityBackgroundColor = activityBackgroundColor
options.activityMenuIconColor = toolbarIconColor
options.activityMenuTextColor = expoCropToolbarActionTextColor ?: defaultContentColor
options.toolbarBackButtonColor = expoCropBackButtonIconColor ?: toolbarIconColor
options.toolbarColor = toolbarColor
options.toolbarTitleColor = toolbarIconColor

window.run {
// Create a view that will sit behind the status bar, colored to match the toolbar
val statusBarView = View(context).apply { setBackgroundColor(toolbarColor) }

// Draw content edge-to-edge, behind system bars
WindowCompat.enableEdgeToEdge(this)

// Set system bar icon colors based on the current theme (dark icons on light bg, and vice versa)
WindowInsetsControllerCompat(this, decorView).run {
isAppearanceLightStatusBars = !isNight
isAppearanceLightNavigationBars = !isNight
}

// Set the root background color so it shows through transparent system bars
decorView.setBackgroundColor(activityBackgroundColor)

// Add the status bar view with zero initial height (will be sized by insets listener below)
addContentView(statusBarView,
ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0))

// Dynamically resize the status bar view to match the actual status bar / display cutout height
ViewCompat.setOnApplyWindowInsetsListener(decorView) { _, insets ->
val values = insets.getInsets(
WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.displayCutout())
statusBarView.updateLayoutParams { height = values.top }
insets
}
}
}

// region Helper Methods
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package expo.modules.imagepicker

import android.graphics.Color
import android.content.res.Resources
import android.util.TypedValue
import com.canhub.cropper.CropImageOptions
import androidx.core.view.WindowInsetsControllerCompat

/**
* Utility functions for ExpoCropImageActivity theming and color management.
Expand All @@ -30,58 +28,4 @@ object ExpoCropImageUtils {
fun getColorResource(resources: android.content.res.Resources, colorResId: Int): Int? = runCatching {
resources.getColor(colorResId, null)
}.getOrNull()

/**
* Applies a color palette to CropImageOptions based on theme attributes or color resources.
* @param theme The theme to resolve colors from
* @param resources The resources to get colors from
* @param isNight Whether the app is in dark mode
* @param options The CropImageOptions to apply colors to
* @return The toolbar widget color that was applied
*/
fun applyPaletteToOptions(
theme: android.content.res.Resources.Theme,
resources: android.content.res.Resources,
isNight: Boolean,
options: CropImageOptions
): Int {
// Try theme attributes first, then fall back to color resources
val customToolbar = getThemeColor(theme, R.attr.expoCropToolbarColor)
?: getColorResource(resources, R.color.expoCropToolbarColor)
val customIconColor = getThemeColor(theme, R.attr.expoCropToolbarIconColor)
?: getColorResource(resources, R.color.expoCropToolbarIconColor)
val customActionTextColor = getThemeColor(theme, R.attr.expoCropToolbarActionTextColor)
?: getColorResource(resources, R.color.expoCropToolbarActionTextColor)
val customBackButtonIconColor = getThemeColor(theme, R.attr.expoCropBackButtonIconColor)
?: getColorResource(resources, R.color.expoCropBackButtonIconColor)
val customBg = getThemeColor(theme, R.attr.expoCropBackgroundColor)
?: getColorResource(resources, R.color.expoCropBackgroundColor)

val defaultColor = if (isNight) Color.BLACK else Color.WHITE
val toolbarWidgetColor = customIconColor ?: if (isNight) Color.WHITE else Color.BLACK

options.activityBackgroundColor = customBg ?: defaultColor
options.toolbarColor = customToolbar ?: defaultColor
options.toolbarTitleColor = toolbarWidgetColor
options.toolbarBackButtonColor = customBackButtonIconColor ?: toolbarWidgetColor
options.activityMenuIconColor = toolbarWidgetColor
options.activityMenuTextColor = customActionTextColor ?: if (isNight) Color.WHITE else Color.BLACK

return toolbarWidgetColor
}

/**
* Applies window-level theming (status bar, etc.) based on the applied palette.
* @param window The window to apply theming to
* @param toolbarColor The toolbar color that was applied
* @param isNight Whether the app is in dark mode
*/
fun applyWindowTheming(
window: android.view.Window,
toolbarColor: Int,
isNight: Boolean
) {
window.statusBarColor = toolbarColor
WindowInsetsControllerCompat(window, window.decorView).isAppearanceLightStatusBars = !isNight
}
}
6 changes: 3 additions & 3 deletions packages/expo-ui/build/datetime-picker/types.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions packages/expo-ui/src/datetime-picker/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { ViewProps } from 'react-native';
// -- Public types matching @react-native-community/datetimepicker ----------

/**
* @deprecated used with the deprecated `onChange` prop
* @deprecated Used with the deprecated `onChange` prop.
* */
export type DateTimePickerEvent = {
/**
Expand All @@ -30,11 +30,11 @@ export type DateTimePickerProps = {
*/
value: Date;
/**
* @deprecated Use `onValueChange` and `onDismiss` instead.
*
* Called when the user changes the date/time or dismisses the picker.
* The event type is encoded in `event.type`.
* If the new specific listeners are provided, they take precedence.
*
* @deprecated Use `onValueChange` and `onDismiss` instead.
*/
onChange?: (event: DateTimePickerEvent, date?: Date) => void;
/**
Expand Down
Loading