From 4e42af83bc23658f55980cde32b67afc49d34273 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Thu, 1 Feb 2024 22:08:17 +0100 Subject: [PATCH 01/61] Add comment about Text autosizing --- TODO.md | 5 +++-- .../java/io/noties/adapt/ui/element/Text.kt | 4 ++++ .../adapt/sample/explore/ExploreWrapper.kt | 18 ++++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 sample/src/main/java/io/noties/adapt/sample/explore/ExploreWrapper.kt diff --git a/TODO.md b/TODO.md index 6b54f5c3..31c7114f 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,6 @@ * explicit tags in sample (enum?), define colors for each +- [ ] `Text` autosize must be applied when text changes (maxLines?) - [ ] SHOW, a layout with rounded background, icon and text => just a text with padding and shape plus, clickable, foreground, cliptooutline - on view pred draw should have `once` as it delivers callback only once @@ -52,8 +53,8 @@ - [x] clipToOutline to allow clipping view by using the shape - [x] castLayout when inside an adapt item is not working, as by default just viewgroup params are set because by default element-item is using default parameters provided by view-factory - + Size of adapt-ui release binary - `421KB` with toString and properties -- `366KB` with static toString in shape and gradient \ No newline at end of file +- `366KB` with static toString in shape and gradient diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/element/Text.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/element/Text.kt index 6aea9fee..1c6526bc 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/element/Text.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/element/Text.kt @@ -350,6 +350,8 @@ fun ViewElement.textJustificationMode( } /** + * Please note that you _might_ need to use [textMaxLines] (`textMaxLines(1)`) in order + * for this to work. * Supplied values are in SP (so, 12 == 12.sp) * @see TextView.setAutoSizeTextTypeUniformWithPresetSizes */ @@ -361,6 +363,8 @@ fun ViewElement.textAutoSize( } /** + * Please note that you _might_ need to use [textMaxLines] (`textMaxLines(1)`) in order + * for this to work. * Supplied values are in SP. Maximum value by default uses current [TextView.getTextSize] * @see TextView.setAutoSizeTextTypeUniformWithConfiguration */ diff --git a/sample/src/main/java/io/noties/adapt/sample/explore/ExploreWrapper.kt b/sample/src/main/java/io/noties/adapt/sample/explore/ExploreWrapper.kt new file mode 100644 index 00000000..a5169c35 --- /dev/null +++ b/sample/src/main/java/io/noties/adapt/sample/explore/ExploreWrapper.kt @@ -0,0 +1,18 @@ +package io.noties.adapt.sample.explore + +import android.view.View +import io.noties.adapt.ui.LayoutParams +import io.noties.adapt.ui.ViewElement + +object ExploreWrapper { + // can we create an extension to the factory that would take current view + // put instead of it our wrapper (or multiple) and add original view there as a child? + + // hm, this could be also complicated, as view could have received some specific customization + // that would be invalid for our wrapper. So, it could break the typesafety that we have. + // Better would be to explicitly put a view in another + + fun ViewElement.frameWrapper() { + // could we access parent view-factory? + } +} \ No newline at end of file From b5994f235a932cb3512db95ab8e3952dc6178023 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Mon, 26 Feb 2024 02:31:26 +0100 Subject: [PATCH 02/61] Add various builders --- adapt-ui/src/main/AndroidManifest.xml | 9 ++- .../noties/adapt/ui/ViewElement+Extensions.kt | 62 ++++++++++++-- .../ui/ViewElement+ExtensionsAccessibility.kt | 6 -- .../adapt/ui/ViewElement+ExtensionsCast.kt | 28 +++---- .../io/noties/adapt/ui/ViewElement+Layout.kt | 22 +++++ .../java/io/noties/adapt/ui/ViewElement.kt | 4 + .../main/java/io/noties/adapt/ui/app/App.kt | 23 ++++++ .../adapt/ui/app/AppInitializationProvider.kt | 37 +++++++++ .../adapt/ui/app/TopMostActivityListener.kt | 40 ++++++++++ .../io/noties/adapt/ui/app/color/Colors.kt | 80 +++++++++++++++++++ .../io/noties/adapt/ui/app/dimen/Dimens.kt | 34 ++++++++ .../noties/adapt/ui/app/style/ViewStyles.kt | 37 +++++++++ .../io/noties/adapt/ui/app/text/TextSizes.kt | 38 +++++++++ .../io/noties/adapt/ui/app/text/TextStyles.kt | 29 +++++++ .../noties/adapt/ui/element/ElementStyle.kt | 10 ++- .../java/io/noties/adapt/ui/element/Image.kt | 7 ++ .../java/io/noties/adapt/ui/element/Item.kt | 4 +- .../java/io/noties/adapt/ui/element/Text.kt | 71 ++++++++++++++-- .../adapt/ui/preview/AdaptUIPreviewLayout.kt | 3 + .../adapt/ui/preview/PreviewApplication.kt | 18 +++++ .../io/noties/adapt/ui/shape/AssetShape.kt | 11 +++ .../adapt/ui/shape/CommonTextPaintData.kt | 16 ++++ .../io/noties/adapt/ui/shape/LabelShape.kt | 7 ++ .../java/io/noties/adapt/ui/shape/Shape.kt | 11 +++ .../io/noties/adapt/ui/shape/TextShape.kt | 7 ++ .../adapt/ui/util/ColorStateListBuilder.kt | 3 + .../java/io/noties/adapt/ui/util/Gravity.kt | 4 +- .../adapt/ui/util/RequireNamedParameters.kt | 3 + .../java/io/noties/adapt/ui/util/ViewUtil.kt | 14 +++- .../ui/ViewElement_ExtensionsCast_Test.kt | 4 +- .../adapt/ui/ViewElement_Extensions_Test.kt | 13 +-- .../io/noties/adapt/ui/ViewFactory_Test.kt | 3 +- .../adapt/ui/element/ElementStyleTest.kt | 9 ++- build.gradle | 6 +- .../io/noties/adapt/sample/MainActivity.kt | 3 + .../java/io/noties/adapt/sample/SampleView.kt | 4 +- .../sample/explore/ExploreAdaptUIBinding.kt | 3 +- .../explore/ExplorePreviewDrawBounds.kt | 4 +- .../adapt/sample/explore/TextSizesExplore.kt | 28 +++++++ .../adaptui/AdaptUIAccessibilitySample.kt | 3 + .../samples/adaptui/AdaptUICastSample.kt | 25 ++++-- .../samples/adaptui/AdaptUIClipSample.kt | 4 + .../adaptui/AdaptUIDrawableStateSample.kt | 12 ++- .../adaptui/AdaptUIDynamicItemSample.kt | 13 ++- .../samples/adaptui/AdaptUIElementItem.kt | 5 +- .../adaptui/AdaptUIElementLayoutSample.kt | 9 ++- .../adaptui/AdaptUIExtensionsSample.kt | 22 ++--- .../samples/adaptui/AdaptUIGradientSample.kt | 6 ++ .../samples/adaptui/AdaptUIInflateSample.kt | 9 ++- .../samples/adaptui/AdaptUIItemSample.kt | 5 +- .../samples/adaptui/AdaptUIItemsSample.kt | 3 + .../samples/adaptui/AdaptUILazySample.kt | 11 ++- .../adaptui/AdaptUILinearGradientSample.kt | 4 + .../samples/adaptui/AdaptUIOverlaySample.kt | 10 ++- .../samples/adaptui/AdaptUIPagerSample.kt | 4 + .../samples/adaptui/AdaptUIPivotSample.kt | 17 ++-- .../samples/adaptui/AdaptUIRelativeSample.kt | 13 ++- .../sample/samples/adaptui/AdaptUISample.kt | 36 ++++----- .../samples/adaptui/AdaptUIShapeSample.kt | 5 ++ .../adaptui/AdaptUIShapeShadowSample.kt | 11 ++- .../samples/adaptui/AdaptUIShapeTextSample.kt | 17 ++-- .../samples/adaptui/AdaptUIStickySample.kt | 13 +-- .../samples/adaptui/AdaptUITextSample.kt | 6 +- .../samples/adaptui/AdaptUIViewGroupSample.kt | 6 +- .../samples/adaptui/AdaptUIWebSample.kt | 14 ++-- .../adaptui/AdaptUiShapeLabelSample.kt | 6 +- .../adapt/sample/samples/adaptui/Colors.kt | 23 ------ .../flex/AdaptUIFlexInteractiveSample.kt | 30 ++++--- .../recyclerview/RecyclerViewSample.kt | 4 +- .../samples/showcase/AdaptUIShowcaseBasic2.kt | 9 ++- .../showcase/AdaptUIShowcaseCircleAvatar.kt | 3 +- .../samples/showcase/AdaptUIShowcaseImage.kt | 6 +- .../samples/showcase/AdaptUIShowcaseItem.kt | 4 +- .../samples/showcase/AdaptUIShowcasePager.kt | 6 +- .../showcase/AdaptUIShowcaseReference.kt | 3 +- .../samples/showcase/AdaptUIShowcaseShape2.kt | 4 +- .../samples/showcase/AdaptUIShowcaseShape3.kt | 4 +- .../showcase/AdaptUIShowcaseShapeDrawable.kt | 7 +- .../samples/showcase/AdaptUIShowcaseStyle.kt | 3 +- .../samples/showcase/AdaptUIShowcaseWeb.kt | 3 +- .../samples/viewpager/ViewPagerSample.kt | 3 +- .../io/noties/adapt/sample/ui/color/Color.kt | 12 +++ sample/src/main/res/values/dimens.xml | 2 + 83 files changed, 919 insertions(+), 211 deletions(-) create mode 100644 adapt-ui/src/main/java/io/noties/adapt/ui/app/App.kt create mode 100644 adapt-ui/src/main/java/io/noties/adapt/ui/app/AppInitializationProvider.kt create mode 100644 adapt-ui/src/main/java/io/noties/adapt/ui/app/TopMostActivityListener.kt create mode 100644 adapt-ui/src/main/java/io/noties/adapt/ui/app/color/Colors.kt create mode 100644 adapt-ui/src/main/java/io/noties/adapt/ui/app/dimen/Dimens.kt create mode 100644 adapt-ui/src/main/java/io/noties/adapt/ui/app/style/ViewStyles.kt create mode 100644 adapt-ui/src/main/java/io/noties/adapt/ui/app/text/TextSizes.kt create mode 100644 adapt-ui/src/main/java/io/noties/adapt/ui/app/text/TextStyles.kt create mode 100644 adapt-ui/src/main/java/io/noties/adapt/ui/preview/PreviewApplication.kt create mode 100644 adapt-ui/src/main/java/io/noties/adapt/ui/util/RequireNamedParameters.kt create mode 100644 sample/src/main/java/io/noties/adapt/sample/explore/TextSizesExplore.kt delete mode 100644 sample/src/main/java/io/noties/adapt/sample/samples/adaptui/Colors.kt create mode 100644 sample/src/main/java/io/noties/adapt/sample/ui/color/Color.kt diff --git a/adapt-ui/src/main/AndroidManifest.xml b/adapt-ui/src/main/AndroidManifest.xml index 1d26c87a..f8bf69b8 100644 --- a/adapt-ui/src/main/AndroidManifest.xml +++ b/adapt-ui/src/main/AndroidManifest.xml @@ -1,2 +1,9 @@ - \ No newline at end of file + + + + + \ No newline at end of file diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/ViewElement+Extensions.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/ViewElement+Extensions.kt index d8d31954..9e57d4ab 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/ViewElement+Extensions.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/ViewElement+Extensions.kt @@ -1,5 +1,6 @@ package io.noties.adapt.ui +import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable import android.os.Build import android.os.SystemClock @@ -10,7 +11,10 @@ import android.view.ViewTreeObserver import androidx.annotation.ChecksSdkIntAtLeast import androidx.annotation.ColorInt import androidx.annotation.FloatRange -import androidx.annotation.RequiresApi +import io.noties.adapt.ui.app.color.Colors +import io.noties.adapt.ui.app.color.ColorsBuilder +import io.noties.adapt.ui.app.dimen.Dimens +import io.noties.adapt.ui.app.dimen.DimensBuilder import io.noties.adapt.ui.shape.Shape import io.noties.adapt.ui.shape.ShapeFactory import io.noties.adapt.ui.shape.ShapeFactoryBuilder @@ -92,12 +96,34 @@ fun ViewElement.tag( * Background * @see View.setBackgroundColor */ +@Deprecated( + "Use `backgroundColor` instead", + replaceWith = ReplaceWith("backgroundColor(color)", imports = ["io.noties.adapt.ui.backgroundColor"]) +) fun ViewElement.background( @ColorInt color: Int +) = backgroundColor(color) + +/** + * Background + * @see View.setBackgroundColor + */ +fun ViewElement.backgroundColor( + @ColorInt color: Int ): ViewElement = onView { it.setBackgroundColor(color) } +/** + * Background that receives [Colors] instance to provide named color values + * ```kotlin + * Text() + * .background { main } + * ``` + */ +inline fun ViewElement.backgroundColor(builder: ColorsBuilder) = + backgroundColor(builder(Colors)) + /** * @see View.setBackground */ @@ -131,12 +157,27 @@ fun ViewElement.backgroundDefaultSelectable it.background = resolveDefaultSelectableDrawable(it.context) } +fun ViewElement.foregroundColor( + @ColorInt color: Int +) = foreground(ColorDrawable(color)) + +/** + * Foreground color + * ```kotlin + * View() + * .foregroundColor { main } + * ``` + * @see Colors + */ +inline fun ViewElement.foregroundColor( + builder: ColorsBuilder +) = foregroundColor(builder(Colors)) + /** * Foreground * @see View.setForeground * @see View.setForegroundGravity */ -@RequiresApi(Build.VERSION_CODES.M) fun ViewElement.foreground( drawable: Drawable?, gravity: Gravity? = null @@ -150,7 +191,6 @@ fun ViewElement.foreground( * @see View.setForegroundGravity * @see Shape.newDrawable */ -@RequiresApi(Build.VERSION_CODES.M) fun ViewElement.foreground( shape: Shape, gravity: Gravity? = null @@ -162,7 +202,6 @@ fun ViewElement.foreground( * @see Shape.newDrawable * @see ShapeFactory */ -@RequiresApi(Build.VERSION_CODES.M) fun ViewElement.foreground( gravity: Gravity? = null, block: ShapeFactoryBuilder @@ -171,7 +210,6 @@ fun ViewElement.foreground( /** * @see View.setForeground */ -@RequiresApi(Build.VERSION_CODES.M) fun ViewElement.foregroundDefaultSelectable(): ViewElement = onView { it.foreground = resolveDefaultSelectableDrawable(it.context) @@ -303,6 +341,18 @@ fun ViewElement.elevation( it.elevation = elevation.dip.toFloat() } +/** + * Elevation + * ```kotlin + * View() + * .elevation { elevationLight } + * ``` + * @see View.setElevation + */ +inline fun ViewElement.elevation( + builder: DimensBuilder +) = elevation(builder(Dimens)) + /** * Translation * @see View.setTranslationX @@ -519,8 +569,6 @@ fun ViewElement.onViewPreDrawOnce( unregisterOnPreDraw() } -// TODO: use normal and unregister on first event - /** * NB! This is a callback when view is attached to [android.view.Window], not its parent */ diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/ViewElement+ExtensionsAccessibility.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/ViewElement+ExtensionsAccessibility.kt index ef0aecaf..419605c4 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/ViewElement+ExtensionsAccessibility.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/ViewElement+ExtensionsAccessibility.kt @@ -1,9 +1,7 @@ package io.noties.adapt.ui -import android.os.Build import android.view.View import androidx.annotation.IdRes -import androidx.annotation.RequiresApi import androidx.annotation.StringRes /** @@ -75,7 +73,6 @@ fun ViewElement.accessibilityLabelFor( /** * @see View.setAccessibilityTraversalBefore */ -@RequiresApi(Build.VERSION_CODES.LOLLIPOP_MR1) fun ViewElement.accessibilityTraversalBefore( @IdRes targetId: Int, ): ViewElement = onView { @@ -85,7 +82,6 @@ fun ViewElement.accessibilityTraversalBefor /** * @see View.setAccessibilityTraversalBefore */ -@RequiresApi(Build.VERSION_CODES.LOLLIPOP_MR1) fun ViewElement.accessibilityTraversalBefore( provider: () -> ViewElement, ): ViewElement = this.also { @@ -97,7 +93,6 @@ fun ViewElement.accessibilityTraversalBefor /** * @see View.setAccessibilityTraversalAfter */ -@RequiresApi(Build.VERSION_CODES.LOLLIPOP_MR1) fun ViewElement.accessibilityTraversalAfter( @IdRes targetId: Int, ): ViewElement = onView { @@ -107,7 +102,6 @@ fun ViewElement.accessibilityTraversalAfter /** * @see View.setAccessibilityTraversalAfter */ -@RequiresApi(Build.VERSION_CODES.LOLLIPOP_MR1) fun ViewElement.accessibilityTraversalAfter( provider: () -> ViewElement, ): ViewElement = this.also { diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/ViewElement+ExtensionsCast.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/ViewElement+ExtensionsCast.kt index d04c2520..4fbc64f1 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/ViewElement+ExtensionsCast.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/ViewElement+ExtensionsCast.kt @@ -13,7 +13,7 @@ import kotlin.reflect.KClass */ fun ViewElement.castView( klass: KClass, -): ViewElement { +): ViewElement { fun matches() = klass.java.isAssignableFrom(this.view::class.java) fun deliver(cause: Throwable?) { @@ -39,18 +39,18 @@ fun ViewElement.castView( } @Suppress("UNCHECKED_CAST") - return this as ViewElement + return this as ViewElement } fun ViewElement.ifCastView( klass: KClass, - block: (ViewElement) -> Unit + block: (ViewElement) -> Unit ): ViewElement { fun matches() = klass.java.isAssignableFrom(this.view::class.java) fun deliver() { @Suppress("UNCHECKED_CAST") - block(this as ViewElement) + block(this as ViewElement) render() } @@ -72,9 +72,9 @@ fun ViewElement.ifCastView( //inline fun ViewElement.castLayout( //): ViewElement where RLP : LP = castLayout<_, _, _>(RLP::class) -fun ViewElement.castLayout( +fun ViewElement.castLayout( klass: KClass -): ViewElement where RLP : LP { +): ViewElement { fun matches(): Boolean = klass.java.isAssignableFrom(view.layoutParams::class.java) fun deliver(cause: Throwable?) { throw AdaptClassCastException( @@ -99,18 +99,18 @@ fun ViewElement.castLayout( // here casting would not fail and always succeed // crash would happen only when trying to access casted object @Suppress("UNCHECKED_CAST") - return this as ViewElement + return this as ViewElement } fun ViewElement.ifCastLayout( klass: KClass, - block: (ViewElement) -> Unit + block: (ViewElement) -> Unit ): ViewElement { fun matches(): Boolean = klass.java.isAssignableFrom(view.layoutParams::class.java) fun deliver() { @Suppress("UNCHECKED_CAST") - block(this as ViewElement) + block(this as ViewElement) render() } @@ -144,7 +144,7 @@ fun ViewElement.ifCastLayout( ) fun ViewElement.castView( view: Class, -): ViewElement = castView(view.kotlin) +): ViewElement = castView(view.kotlin) @Suppress("DeprecatedCallableAddReplaceWith") @Deprecated( @@ -152,22 +152,22 @@ fun ViewElement.castView( ) fun ViewElement.ifCastView( view: Class, - block: (ViewElement) -> Unit + block: (ViewElement) -> Unit ): ViewElement = ifCastView(view.kotlin, block) @Suppress("DeprecatedCallableAddReplaceWith") @Deprecated( "Use kotlin class instead, for example - `MarginLayoutParams::class`" ) -fun ViewElement.castLayout( +fun ViewElement.castLayout( layoutParams: Class, -): ViewElement where RLP : LP = castLayout(layoutParams.kotlin) +): ViewElement = castLayout(layoutParams.kotlin) @Suppress("DeprecatedCallableAddReplaceWith") @Deprecated("Use kotlin class instead, for example - `MarginLayoutParams::class`") fun ViewElement.ifCastLayout( layoutParams: Class, - block: (ViewElement) -> Unit + block: (ViewElement) -> Unit ): ViewElement = ifCastLayout(layoutParams.kotlin, block) internal class AdaptClassCastException( diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/ViewElement+Layout.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/ViewElement+Layout.kt index 50c062c5..075cffb1 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/ViewElement+Layout.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/ViewElement+Layout.kt @@ -7,6 +7,7 @@ import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.widget.FrameLayout import android.widget.LinearLayout import io.noties.adapt.ui.util.Gravity +import io.noties.adapt.ui.util.GravityBuilder import io.noties.adapt.ui.util.dip /** @@ -89,6 +90,19 @@ fun ViewElement.layoutGravit it.gravity = gravity.value } +/** + * Specifies `layout_gravity` for a view inside LinearLayout (`VStack` or `HStack`) + * ```kotlin + * VStack { + * View().layoutGravity { center } + * } + * ``` + */ +@JvmName("linearLayoutGravity") +inline fun ViewElement.layoutGravity( + builder: GravityBuilder +) = layoutGravity(builder(Gravity)) + /** * Specifies `layout_gravity` for a view inside FrameLayout (`ZStack`) */ @@ -99,6 +113,14 @@ fun ViewElement.layoutGravity it.gravity = gravity.value } +/** + * Specifies `layout_gravity` for a view inside FrameLayout (`ZStack`) + */ +@JvmName("frameLayoutGravity") +inline fun ViewElement.layoutGravity( + builder: GravityBuilder +) = layoutGravity(builder(Gravity)) + /** * Specifies value for all layout margins: `start`, `top`, `end` and `bottom`. * Value is in `dip`, it is automatically converted to pixels according to device density diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/ViewElement.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/ViewElement.kt index f14b3039..1bffd773 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/ViewElement.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/ViewElement.kt @@ -31,6 +31,10 @@ open class ViewElement( * // to render _now_, then explicit `render` call can be used * .render() * ``` + * If created `ViewElement` needs also layout parameters, then regular extensions: + * - `castLayout` - which casts received layout params and fails when mismatched + * - `ifCastLayout` - which allows selectively apply certain layout configuration + * if received parameters would be of the same type only (and without affecting the rest) */ fun create( view: V diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/app/App.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/app/App.kt new file mode 100644 index 00000000..a04ab391 --- /dev/null +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/app/App.kt @@ -0,0 +1,23 @@ +package io.noties.adapt.ui.app + +import android.annotation.SuppressLint +import android.app.Activity +import android.app.Application + +object App { + var shared: Application + get() = _shared ?: throw IllegalStateException("App.shared is not initialized") + set(value) { + _shared?.unregisterActivityLifecycleCallbacks(topMostActivityListener) + _shared = value.also { + it.registerActivityLifecycleCallbacks(topMostActivityListener) + } + } + + val topMostActivity: Activity? get() = topMostActivityListener.topMostActivity + + private var _shared: Application? = null + + @SuppressLint("StaticFieldLeak") + private val topMostActivityListener = TopMostActivityListener() +} \ No newline at end of file diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/app/AppInitializationProvider.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/app/AppInitializationProvider.kt new file mode 100644 index 00000000..f378aaef --- /dev/null +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/app/AppInitializationProvider.kt @@ -0,0 +1,37 @@ +package io.noties.adapt.ui.app + +import android.app.Application +import android.content.ContentProvider +import android.content.ContentValues +import android.database.Cursor +import android.net.Uri + +internal class AppInitializationProvider : ContentProvider() { + override fun onCreate(): Boolean { + val application = context?.applicationContext as? Application + ?: throw IllegalStateException("Cannot obtain Application") + App.shared = application + return false + } + + override fun query( + uri: Uri, + projection: Array?, + selection: String?, + selectionArgs: Array?, + sortOrder: String? + ): Cursor? = null + + override fun getType(uri: Uri): String? = null + + override fun insert(uri: Uri, values: ContentValues?): Uri? = null + + override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int = 0 + + override fun update( + uri: Uri, + values: ContentValues?, + selection: String?, + selectionArgs: Array? + ): Int = 0 +} \ No newline at end of file diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/app/TopMostActivityListener.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/app/TopMostActivityListener.kt new file mode 100644 index 00000000..16545394 --- /dev/null +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/app/TopMostActivityListener.kt @@ -0,0 +1,40 @@ +package io.noties.adapt.ui.app + +import android.app.Activity +import android.app.Application +import android.os.Bundle + +// TODO: validate new activity, that it would use created instead of previous started +class TopMostActivityListener : Application.ActivityLifecycleCallbacks { + + val topMostActivity: Activity? get() = startedActivity ?: createdActivity + + private var createdActivity: Activity? = null + private var startedActivity: Activity? = null + + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { + createdActivity = activity + } + + override fun onActivityDestroyed(activity: Activity) { + if (createdActivity == activity) { + createdActivity = null + } + } + + override fun onActivityStarted(activity: Activity) { + startedActivity = activity + } + + override fun onActivityStopped(activity: Activity) { + if (startedActivity == activity) { + startedActivity = null + } + } + + override fun onActivityResumed(activity: Activity) = Unit + + override fun onActivityPaused(activity: Activity) = Unit + + override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) = Unit +} \ No newline at end of file diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/app/color/Colors.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/app/color/Colors.kt new file mode 100644 index 00000000..b0b2dfdd --- /dev/null +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/app/color/Colors.kt @@ -0,0 +1,80 @@ +package io.noties.adapt.ui.app.color + +import android.content.Context +import android.graphics.Color +import androidx.annotation.ColorInt +import androidx.annotation.ColorRes +import androidx.annotation.FloatRange +import androidx.annotation.IntRange +import io.noties.adapt.ui.app.App +import kotlin.math.roundToInt +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +object Colors { + @ColorInt + fun res(@ColorRes resId: Int): Int = context.getColor(resId) + + @ColorInt + fun hex(hex: String): Int = io.noties.adapt.ui.util.hex(hex) + + @ColorInt + fun rgb( + @IntRange(from = 0, to = 255) red: Int, + @IntRange(from = 0, to = 255) green: Int, + @IntRange(from = 0, to = 255) blue: Int + ): Int = argb(1F, red, green, blue) + + @ColorInt + fun argb( + @FloatRange(from = 0.0, to = 1.0) alpha: Float, + @IntRange(from = 0, to = 255) red: Int, + @IntRange(from = 0, to = 255) green: Int, + @IntRange(from = 0, to = 255) blue: Int + ): Int = Color.argb( + (255F * alpha).roundToInt(), + red, + green, + blue + ) + + // hm, if we create a-new, activity might be not initialized and thus + // we might use context from previous activity, or just application + val context: Context get() = (App.topMostActivity ?: App.shared) +} + + +typealias ColorsBuilder = Colors.() -> /*@ColorInt*/ Int + + +fun resColor( + @ColorRes resId: Int +) = ColorsProperty(Colors.res(resId)) + +fun hex( + hex: String +) = ColorsProperty(Colors.hex(hex)) + +fun rgb( + red: Int, + green: Int, + blue: Int +) = argb(1F, red, green, blue) + +fun argb( + alpha: Float, + red: Int, + green: Int, + blue: Int +) = ColorsProperty(Colors.argb(alpha, red, green, blue)) + +fun rawColor( + @ColorInt color: Int +) = ColorsProperty(color) + + +class ColorsProperty(@ColorInt val value: Int) : ReadOnlyProperty { + override fun getValue(thisRef: Colors, property: KProperty<*>): Int { + return value + } +} \ No newline at end of file diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/app/dimen/Dimens.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/app/dimen/Dimens.kt new file mode 100644 index 00000000..9990d246 --- /dev/null +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/app/dimen/Dimens.kt @@ -0,0 +1,34 @@ +package io.noties.adapt.ui.app.dimen + +import android.content.Context +import androidx.annotation.DimenRes +import androidx.annotation.Dimension +import io.noties.adapt.ui.app.App +import kotlin.math.roundToInt +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +object Dimens { + fun res(@DimenRes resId: Int): Int = context.resources + .let { + val density = it.displayMetrics.density + val value = it.getDimension(resId) + (value / density).roundToInt() + } + + val context: Context get() = App.topMostActivity ?: App.shared +} + +typealias DimensBuilder = Dimens.() -> Int + + +fun resDip(@DimenRes resId: Int) = DimensProperty(Dimens.res(resId)) + +fun rawDip(@Dimension dip: Int) = DimensProperty(dip) + + +class DimensProperty(private val value: Int) : ReadOnlyProperty { + override fun getValue(thisRef: Dimens, property: KProperty<*>): Int { + return value + } +} \ No newline at end of file diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/app/style/ViewStyles.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/app/style/ViewStyles.kt new file mode 100644 index 00000000..f6d5ec40 --- /dev/null +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/app/style/ViewStyles.kt @@ -0,0 +1,37 @@ +package io.noties.adapt.ui.app.style + +import android.view.View +import io.noties.adapt.ui.LayoutParams +import io.noties.adapt.ui.ViewElement +import io.noties.adapt.ui.ViewFactoryConstants +import io.noties.adapt.ui.element.ElementStyle +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +object ViewStyles + + +fun style( + block: ViewFactoryConstants.(ViewElement) -> Unit +) = ViewStylesProperty(block) + +fun styleView( + block: ViewFactoryConstants.(ViewElement) -> Unit +) = ViewStylesProperty(block) + +fun styleLayout( + block: ViewFactoryConstants.(ViewElement) -> Unit +) = ViewStylesProperty(block) + +fun styleViewLayout( + block: ViewFactoryConstants.(ViewElement) -> Unit +) = ViewStylesProperty(block) + + +class ViewStylesProperty( + private val block: ViewFactoryConstants.(ViewElement) -> Unit +) : ReadOnlyProperty> { + override fun getValue(thisRef: ViewStyles, property: KProperty<*>): ElementStyle { + return ElementStyle(block) + } +} \ No newline at end of file diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/app/text/TextSizes.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/app/text/TextSizes.kt new file mode 100644 index 00000000..01cfd13a --- /dev/null +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/app/text/TextSizes.kt @@ -0,0 +1,38 @@ +package io.noties.adapt.ui.app.text + +import android.content.Context +import androidx.annotation.DimenRes +import androidx.annotation.Dimension +import io.noties.adapt.ui.app.App +import kotlin.math.roundToInt +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +object TextSizes { + /** + * Obtain scaled-pixel dimension resource + */ + fun res(@DimenRes resId: Int): Int = context + .resources + .let { + val scaledDensity = it.displayMetrics.scaledDensity + val value = it.getDimension(resId) + (value / scaledDensity).roundToInt() + } + + val context: Context get() = App.topMostActivity ?: App.shared +} + +typealias TextSizesBuilder = TextSizes.() -> Int + + +fun resSp(@DimenRes resId: Int) = TextSizesProperty(TextSizes.res(resId)) + +fun rawSp(@Dimension sp: Int) = TextSizesProperty(sp) + + +class TextSizesProperty(private val value: Int) : ReadOnlyProperty { + override fun getValue(thisRef: TextSizes, property: KProperty<*>): Int { + return value + } +} \ No newline at end of file diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/app/text/TextStyles.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/app/text/TextStyles.kt new file mode 100644 index 00000000..6a07a917 --- /dev/null +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/app/text/TextStyles.kt @@ -0,0 +1,29 @@ +package io.noties.adapt.ui.app.text + +import android.widget.TextView +import io.noties.adapt.ui.LayoutParams +import io.noties.adapt.ui.ViewElement +import io.noties.adapt.ui.ViewFactoryConstants +import io.noties.adapt.ui.element.ElementStyle +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +object TextStyles + + +fun textStyle( + block: ViewFactoryConstants.(ViewElement) -> Unit +) = TextStylesProperty(block) + + +class TextStylesProperty( + private val block: ViewFactoryConstants.(ViewElement) -> Unit +): ReadOnlyProperty> { + override fun getValue( + thisRef: TextStyles, + property: KProperty<*> + ): ElementStyle { + return ElementStyle(block) + } +} + diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/element/ElementStyle.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/element/ElementStyle.kt index df1b4a20..42d04a12 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/element/ElementStyle.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/element/ElementStyle.kt @@ -11,6 +11,10 @@ fun ViewElement.style( style.block.invoke(ViewFactoryConstants.Impl, it) } +fun ViewElement.style( + block: ElementStyle.Companion.() -> ElementStyle +) = style(block(ElementStyle)) + class ElementStyle private constructor( val block: ViewFactoryConstants.(ViewElement<@UnsafeVariance V, @UnsafeVariance LP>) -> Unit ) { @@ -20,15 +24,15 @@ class ElementStyle private constructor( ) = ElementStyle(block) fun generic( - block: ViewFactoryConstants.(ViewElement) -> Unit + block: ViewFactoryConstants.(ViewElement) -> Unit ) = ElementStyle(block) fun view( - block: ViewFactoryConstants.(ViewElement) -> Unit + block: ViewFactoryConstants.(ViewElement) -> Unit ) = ElementStyle(block) fun layout( - block: ViewFactoryConstants.(ViewElement) -> Unit + block: ViewFactoryConstants.(ViewElement) -> Unit ) = ElementStyle(block) fun viewLayout( diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/element/Image.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/element/Image.kt index a4cd1bfa..0987c514 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/element/Image.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/element/Image.kt @@ -4,6 +4,7 @@ import android.content.res.ColorStateList import android.graphics.Bitmap import android.graphics.PorterDuff import android.graphics.drawable.Drawable +import android.view.View import android.widget.ImageView import android.widget.ImageView.ScaleType import androidx.annotation.ColorInt @@ -11,6 +12,8 @@ import androidx.annotation.DrawableRes import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewElement import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.app.color.Colors +import io.noties.adapt.ui.app.color.ColorsBuilder @Suppress("FunctionName") fun ViewFactory.Image(): ViewElement = @@ -88,6 +91,10 @@ fun ViewElement.imageTint( mode: PorterDuff.Mode? = null ): ViewElement = imageTint(ColorStateList.valueOf(color), mode) +inline fun ViewElement.imageTint( + block: ColorsBuilder +) = imageTint(block(Colors)) + /** * Null value for the `mode` argument would not set it, otherwise tint value becomes * cleared according to the documentation. diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/element/Item.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/element/Item.kt index b9589b5f..13e121d7 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/element/Item.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/element/Item.kt @@ -163,9 +163,7 @@ class ItemElement, LP : LayoutParams>( } private fun endTransitions(viewGroup: ViewGroup) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - TransitionManager.endTransitions(viewGroup) - } + TransitionManager.endTransitions(viewGroup) } } diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/element/Text.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/element/Text.kt index 1c6526bc..76aee2a0 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/element/Text.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/element/Text.kt @@ -18,8 +18,14 @@ import androidx.annotation.StringRes import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewElement import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.app.color.Colors +import io.noties.adapt.ui.app.color.ColorsBuilder +import io.noties.adapt.ui.app.text.TextSizes +import io.noties.adapt.ui.app.text.TextSizesBuilder +import io.noties.adapt.ui.app.text.TextStyles import io.noties.adapt.ui.gradient.Gradient import io.noties.adapt.ui.util.Gravity +import io.noties.adapt.ui.util.GravityBuilder import io.noties.adapt.ui.util.ImeOptions import io.noties.adapt.ui.util.InputType import io.noties.adapt.ui.util.TextWatcherHideIfEmpty @@ -44,6 +50,20 @@ fun ViewElement.textSize( it.setTextSize(TypedValue.COMPLEX_UNIT_SP, size.toFloat()) } +/** + * Text size + * ```kotlin + * Text() + * .textSize { body } + * ``` + * @see TextView.setTextSize + * @see textSize + * @see TextSizes + */ +inline fun ViewElement.textSize( + builder: TextSizesBuilder, +) = textSize(builder(TextSizes)) + /** * @see textColor(android.content.res.ColorStateList) */ @@ -53,6 +73,18 @@ fun ViewElement.textColor( it.setTextColor(color) } +/** + * Text color + * ```kotlin + * Text() + * .textColor { main } + * ``` + * @see Colors + */ +inline fun ViewElement.textColor( + builder: ColorsBuilder +) = textColor(builder(Colors)) + /** * @see io.noties.adapt.ui.util.ColorStateListBuilder * @see TextView.setTextColor @@ -119,6 +151,18 @@ fun ViewElement.textGravity( it.gravity = gravity.value } +/** + * Text gravity + * ```kotlin + * Text() + * .textGravity { center.vertical } + * ``` + * @see Gravity + */ +inline fun ViewElement.textGravity( + builder: GravityBuilder +) = textGravity(builder(Gravity)) + /** * Typeface * @see TextView.setTypeface @@ -138,6 +182,11 @@ fun ViewElement.textTypeface( it.setTypeface(typeface, typefaceStyle.value) } +fun ViewElement.textTypeface( + typeface: Typeface? = null, + builder: TypefaceStyle.Companion.() -> TypefaceStyle +) = textTypeface(typeface, builder(TypefaceStyle)) + /** * Makes text typeface style **bold**. * NB! if current style already is _italic_, the resulting style would be BOLD_ITALIC @@ -236,6 +285,18 @@ fun ViewElement.textHintColor( it.setHintTextColor(color) } +/** + * Hint text color + * ```kotlin + * Text() + * .textHintColor { placeholder } + * ``` + * @see Colors + */ +inline fun ViewElement.textHintColor( + builder: ColorsBuilder +) = textHintColor(builder(Colors)) + /** * Hint text color * @see TextView.setHintTextColor @@ -280,7 +341,6 @@ fun ViewElement.textSingleLine( /** * HyphenationFrequency */ -@RequiresApi(Build.VERSION_CODES.M) @JvmInline value class HyphenationFrequency(val value: Int) { companion object { @@ -293,7 +353,6 @@ value class HyphenationFrequency(val value: Int) { /** * @see TextView.setHyphenationFrequency */ -@RequiresApi(Build.VERSION_CODES.M) fun ViewElement.textHyphenationFrequency( hyphenationFrequency: HyphenationFrequency ): ViewElement = onView { @@ -304,7 +363,6 @@ fun ViewElement.textHyphenationFrequenc * BreakStrategy * @see TextView.setBreakStrategy */ -@RequiresApi(Build.VERSION_CODES.M) @JvmInline value class BreakStrategy(val value: Int) { companion object { @@ -317,7 +375,6 @@ value class BreakStrategy(val value: Int) { /** * @see TextView.setBreakStrategy */ -@RequiresApi(Build.VERSION_CODES.M) fun ViewElement.textBreakStrategy( breakStrategy: BreakStrategy ): ViewElement = onView { @@ -444,4 +501,8 @@ fun ViewElement.textImeOptions( } } } -} \ No newline at end of file +} + +inline fun ViewElement.textStyle( + builder: TextStyles.() -> ElementStyle +) = style(builder(TextStyles)) \ No newline at end of file diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/preview/AdaptUIPreviewLayout.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/preview/AdaptUIPreviewLayout.kt index 53d6e64b..04416370 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/preview/AdaptUIPreviewLayout.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/preview/AdaptUIPreviewLayout.kt @@ -4,12 +4,15 @@ import android.content.Context import android.util.AttributeSet import android.widget.FrameLayout import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.app.App /** * @since $UNRELEASED; */ abstract class AdaptUIPreviewLayout(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) { init { + PreviewApplication.install(context) + layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) initialize(this) diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/preview/PreviewApplication.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/preview/PreviewApplication.kt new file mode 100644 index 00000000..c810c0fa --- /dev/null +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/preview/PreviewApplication.kt @@ -0,0 +1,18 @@ +package io.noties.adapt.ui.preview + +import android.app.Application +import android.content.Context +import io.noties.adapt.ui.app.App + +open class PreviewApplication(context: Context): Application() { + + companion object { + fun install(context: Context) { + App.shared = PreviewApplication(context) + } + } + + init { + attachBaseContext(context) + } +} \ No newline at end of file diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/shape/AssetShape.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/shape/AssetShape.kt index fde4f0cb..7f13509f 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/shape/AssetShape.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/shape/AssetShape.kt @@ -9,6 +9,8 @@ import android.graphics.Rect import android.graphics.drawable.Drawable import android.os.SystemClock import androidx.annotation.ColorInt +import io.noties.adapt.ui.app.color.Colors +import io.noties.adapt.ui.app.color.ColorsBuilder import kotlin.math.roundToInt // NB! it discards received paint (so, no fill, nor stroke would function) @@ -52,6 +54,15 @@ class AssetShape( drawable = drawable.mutate().also { it.setTint(color) } } + /** + * ```kotlin + * Asset { + * tint { main } + * } + * ``` + */ + inline fun tint(builder: ColorsBuilder) = tint(builder(Colors)) + fun tint(colorStateList: ColorStateList) = this.apply { drawable = drawable.mutate().also { it.setTintList(colorStateList) } } diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/shape/CommonTextPaintData.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/shape/CommonTextPaintData.kt index 996ce402..9d3815a8 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/shape/CommonTextPaintData.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/shape/CommonTextPaintData.kt @@ -3,6 +3,10 @@ package io.noties.adapt.ui.shape import android.graphics.Typeface import android.text.TextPaint import androidx.annotation.ColorInt +import io.noties.adapt.ui.app.color.Colors +import io.noties.adapt.ui.app.color.ColorsBuilder +import io.noties.adapt.ui.app.text.TextSizes +import io.noties.adapt.ui.app.text.TextSizesBuilder import io.noties.adapt.ui.util.dip interface CommonTextPaintData { @@ -85,6 +89,12 @@ interface CommonTextPaintDataSetter : CommonTextPain textSize: Int? ) = (this as THIS).also { it.textSize = textSize } + fun textSize( + builder: TextSizesBuilder + ) = (this as THIS).also { + it.textSize = builder(TextSizes) + } + /** * Set text color * @see TextPaint.setColor @@ -93,6 +103,12 @@ interface CommonTextPaintDataSetter : CommonTextPain @ColorInt textColor: Int? ) = (this as THIS).also { it.textColor = textColor } + fun textColor( + builder: ColorsBuilder + ) = (this as THIS).also { + it.textColor = builder(Colors) + } + /** * Set text Typeface * @see TextPaint.setTypeface diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/shape/LabelShape.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/shape/LabelShape.kt index ac84c39d..350934c6 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/shape/LabelShape.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/shape/LabelShape.kt @@ -6,6 +6,7 @@ import android.graphics.Rect import android.graphics.Typeface import android.text.TextPaint import io.noties.adapt.ui.util.Gravity +import io.noties.adapt.ui.util.GravityBuilder interface BaseLabelShapeData : CommonTextPaintData { var text: String? @@ -28,6 +29,12 @@ interface BaseLabelShapeDataSetter : BaseLabelShapeDa it.textGravity = textGravity } + fun textGravity( + builder: GravityBuilder + ) = (this as THIS).also { + it.textGravity = builder(Gravity) + } + fun textRotation( angle: Float, centerX: Float? = null, diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/shape/Shape.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/shape/Shape.kt index ffc2ebf1..b3dea024 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/shape/Shape.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/shape/Shape.kt @@ -12,8 +12,11 @@ import android.graphics.Shader import android.view.View import androidx.annotation.ColorInt import androidx.annotation.FloatRange +import io.noties.adapt.ui.app.color.Colors +import io.noties.adapt.ui.app.color.ColorsBuilder import io.noties.adapt.ui.gradient.Gradient import io.noties.adapt.ui.util.Gravity +import io.noties.adapt.ui.util.GravityBuilder import io.noties.adapt.ui.util.dip import io.noties.adapt.ui.util.toHexString import kotlin.math.roundToInt @@ -88,6 +91,10 @@ abstract class Shape : ShapeFactory { this.gravity = gravity } + fun gravity(builder: GravityBuilder) = this.also { + gravity(builder(Gravity)) + } + /** * Rotation. * By default rotates around bounds centerX and centerY @@ -232,6 +239,10 @@ abstract class Shape : ShapeFactory { } } + fun fill(builder: ColorsBuilder) = this.also { + fill(builder(Colors)) + } + fun fill(gradient: Gradient?): Shape = this.also { this.fill = (fill ?: Fill()).apply { this.gradient = gradient diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/shape/TextShape.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/shape/TextShape.kt index b8a96591..41b6d359 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/shape/TextShape.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/shape/TextShape.kt @@ -16,6 +16,7 @@ import io.noties.adapt.ui.element.HyphenationFrequency import io.noties.adapt.ui.element.JustificationMode import io.noties.adapt.ui.gradient.Gradient import io.noties.adapt.ui.util.Gravity +import io.noties.adapt.ui.util.GravityBuilder import io.noties.adapt.ui.util.dip import kotlin.math.roundToInt @@ -61,6 +62,12 @@ interface BaseTextShapeDataSetter : BaseTextShapeData it.textGravity = textGravity } + fun textGravity( + builder: GravityBuilder + ) = (this as THIS).also { + this.textGravity = builder(Gravity) + } + /** * Rotate text relative to its bounds (bounds that actual text content takes). Accepts * relative values - 0F..1F where x=0F is left-most position y=1F is bottom-most position. diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/util/ColorStateListBuilder.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/util/ColorStateListBuilder.kt index 3778feda..757b0b85 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/util/ColorStateListBuilder.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/util/ColorStateListBuilder.kt @@ -48,6 +48,9 @@ class ColorStateListBuilder { fun setSelected(@ColorInt color: Int) = set(DrawableState.selected, color) fun setChecked(@ColorInt color: Int) = set(DrawableState.checked, color) + // TODO: version with Colors.() -> Int + // setEnabled { white } + fun setDefault(@ColorInt color: Int) = set(emptySet(), color) @Deprecated( diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/util/Gravity.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/util/Gravity.kt index 194135c5..19c12bb9 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/util/Gravity.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/util/Gravity.kt @@ -88,4 +88,6 @@ open class Gravity(@GravityInt val value: Int) { private fun checkVertical(expected: Int): Boolean { return expected == (value and VERTICAL_GRAVITY_MASK) } -} \ No newline at end of file +} + +typealias GravityBuilder = Gravity.Companion.() -> Gravity \ No newline at end of file diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/util/RequireNamedParameters.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/util/RequireNamedParameters.kt new file mode 100644 index 00000000..bc7c98b3 --- /dev/null +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/util/RequireNamedParameters.kt @@ -0,0 +1,3 @@ +package io.noties.adapt.ui.util + +class RequireNamedParameters private constructor() \ No newline at end of file diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/util/ViewUtil.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/util/ViewUtil.kt index 14a70202..cdef3df1 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/util/ViewUtil.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/util/ViewUtil.kt @@ -2,6 +2,8 @@ package io.noties.adapt.ui.util import android.view.View import android.view.ViewTreeObserver.OnPreDrawListener +import io.noties.adapt.ui.LayoutParams +import io.noties.adapt.ui.ViewElement fun V.onPreDrawOnce(block: (V) -> Unit) { val view = this @@ -41,4 +43,14 @@ fun V.onDetachedOnce(block: (V) -> Unit) { view.removeOnAttachStateChangeListener(this) } }) -} \ No newline at end of file +} + +/** + * Turn a view into `ViewElement` + * ```kotlin + * val text: TextView = /* obtain view */ + * text.element + * .textSize { body } + * ``` + */ +val V.element: ViewElement get() = ViewElement.create(this) \ No newline at end of file diff --git a/adapt-ui/src/test/java/io/noties/adapt/ui/ViewElement_ExtensionsCast_Test.kt b/adapt-ui/src/test/java/io/noties/adapt/ui/ViewElement_ExtensionsCast_Test.kt index ee228631..0daa9972 100644 --- a/adapt-ui/src/test/java/io/noties/adapt/ui/ViewElement_ExtensionsCast_Test.kt +++ b/adapt-ui/src/test/java/io/noties/adapt/ui/ViewElement_ExtensionsCast_Test.kt @@ -19,10 +19,10 @@ import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.eq -import org.mockito.Mockito.`when` import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.robolectric.RobolectricTestRunner import org.robolectric.RuntimeEnvironment import org.robolectric.annotation.Config @@ -139,7 +139,7 @@ class ViewElement_ExtensionsCast_Test { assertFalse(element.isInitialized) element.ifCastView(EditText::class) { - it.background(12) + it.backgroundColor(12) } element.init(RuntimeEnvironment.getApplication()) diff --git a/adapt-ui/src/test/java/io/noties/adapt/ui/ViewElement_Extensions_Test.kt b/adapt-ui/src/test/java/io/noties/adapt/ui/ViewElement_Extensions_Test.kt index 0ac93bd5..ed3708be 100644 --- a/adapt-ui/src/test/java/io/noties/adapt/ui/ViewElement_Extensions_Test.kt +++ b/adapt-ui/src/test/java/io/noties/adapt/ui/ViewElement_Extensions_Test.kt @@ -24,7 +24,6 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers -import org.mockito.Mockito.`when` import org.mockito.Mockito.any import org.mockito.Mockito.anyFloat import org.mockito.Mockito.anyInt @@ -33,8 +32,8 @@ import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.kotlin.argumentCaptor -import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever import org.mockito.verification.VerificationMode @@ -459,7 +458,7 @@ class ViewElement_Extensions_Test { fun `background - color`() { val input = 0xFFff0000.toInt() newElement() - .background(input) + .backgroundColor(input) .renderView { verify(this).setBackgroundColor(eq(input)) } @@ -1073,7 +1072,8 @@ class ViewElement_Extensions_Test { if (input) { verify(this).isEnabled = org.mockito.kotlin.eq(false) } else { - verify(this, org.mockito.kotlin.never()).isEnabled = org.mockito.kotlin.any() + verify(this, org.mockito.kotlin.never()).isEnabled = + org.mockito.kotlin.any() } } } @@ -1134,7 +1134,8 @@ class ViewElement_Extensions_Test { .renderView { val listener = kotlin.run { - val captor = ArgumentCaptor.forClass(ViewTreeObserver.OnPreDrawListener::class.java) + val captor = + ArgumentCaptor.forClass(ViewTreeObserver.OnPreDrawListener::class.java) org.mockito.kotlin.verify(observer).addOnPreDrawListener(captor.capture()) captor.value } @@ -1147,7 +1148,7 @@ class ViewElement_Extensions_Test { verify(this, never()).pivotX = anyFloat() } - if (y != null){ + if (y != null) { verify(this).pivotY = eq(y * h) } else { verify(this, never()).pivotY = anyFloat() diff --git a/adapt-ui/src/test/java/io/noties/adapt/ui/ViewFactory_Test.kt b/adapt-ui/src/test/java/io/noties/adapt/ui/ViewFactory_Test.kt index 5ab253bd..65348a26 100644 --- a/adapt-ui/src/test/java/io/noties/adapt/ui/ViewFactory_Test.kt +++ b/adapt-ui/src/test/java/io/noties/adapt/ui/ViewFactory_Test.kt @@ -15,7 +15,6 @@ import io.noties.adapt.ui.element.View import io.noties.adapt.ui.element.ZStack import io.noties.adapt.ui.testutil.assertDensity import io.noties.adapt.ui.testutil.mockt -import org.junit.Assert import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull @@ -207,7 +206,7 @@ class ViewFactory_Test { val padding = 101 val element = newElement() - .background(background) + .backgroundColor(background) .padding(padding) val lp = FrameLayout.LayoutParams(123, LayoutParams.MATCH_PARENT) diff --git a/adapt-ui/src/test/java/io/noties/adapt/ui/element/ElementStyleTest.kt b/adapt-ui/src/test/java/io/noties/adapt/ui/element/ElementStyleTest.kt index 096a65c4..6d2b0080 100644 --- a/adapt-ui/src/test/java/io/noties/adapt/ui/element/ElementStyleTest.kt +++ b/adapt-ui/src/test/java/io/noties/adapt/ui/element/ElementStyleTest.kt @@ -9,6 +9,7 @@ import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewElement import io.noties.adapt.ui.ViewFactoryConstants import io.noties.adapt.ui.background +import io.noties.adapt.ui.backgroundColor import io.noties.adapt.ui.newElement import io.noties.adapt.ui.newElementOfType import io.noties.adapt.ui.newElementOfTypeLayout @@ -88,8 +89,8 @@ class ElementStyleTest { @Test fun `style overrides previous values`() { newElement() - .background(1) - .style(ElementStyle.generic { it.background(2) }) + .backgroundColor(1) + .style(ElementStyle.generic { it.backgroundColor(2) }) .renderView { val captor = ArgumentCaptor.forClass(Int::class.java) verify(this, times(2)).setBackgroundColor(captor.capture()) @@ -103,8 +104,8 @@ class ElementStyleTest { @Test fun `style value overridden`() { newElement() - .style(ElementStyle.generic { it.background(3) }) - .background(4) + .style(ElementStyle.generic { it.backgroundColor(3) }) + .backgroundColor(4) .renderView { val captor = ArgumentCaptor.forClass(Int::class.java) verify(this, times(2)).setBackgroundColor(captor.capture()) diff --git a/build.gradle b/build.gradle index 5a6b1ee4..abfa480d 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ allprojects { } group = 'io.noties' - version = '5.0.0' + version = '5.1.0-SNAPSHOT' tasks.withType(Javadoc) { enabled(false) @@ -29,14 +29,14 @@ task clean(type: Delete) { } wrapper { - gradleVersion '7.3.3' + gradleVersion '7.4' distributionType 'all' } ext { config = [ - 'min-sdk' : 21, + 'min-sdk' : 23, 'target-sdk' : 33, 'build-tools': '33.0.2' ] diff --git a/sample/src/main/java/io/noties/adapt/sample/MainActivity.kt b/sample/src/main/java/io/noties/adapt/sample/MainActivity.kt index 9555fb32..9e795a21 100644 --- a/sample/src/main/java/io/noties/adapt/sample/MainActivity.kt +++ b/sample/src/main/java/io/noties/adapt/sample/MainActivity.kt @@ -5,6 +5,7 @@ import android.os.Bundle import android.view.View import android.view.animation.AnimationUtils import android.widget.ViewAnimator +import io.noties.adapt.sample.explore.TextSizesExplore import io.noties.adapt.sample.ui.OnBackPressedListenerFrameLayout import io.noties.adapt.sample.util.SampleUtil @@ -17,6 +18,8 @@ class MainActivity : Activity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + TextSizesExplore.hey() + onBackPressedListenerFrameLayout = findViewById(R.id.on_back_pressed_listener) viewAnimator = findViewById(R.id.view_animator) diff --git a/sample/src/main/java/io/noties/adapt/sample/SampleView.kt b/sample/src/main/java/io/noties/adapt/sample/SampleView.kt index 4f49a05d..b9cdfc42 100644 --- a/sample/src/main/java/io/noties/adapt/sample/SampleView.kt +++ b/sample/src/main/java/io/noties/adapt/sample/SampleView.kt @@ -15,7 +15,9 @@ import android.widget.TextView import io.noties.adapt.Adapt import io.noties.adapt.Item import io.noties.adapt.sample.items.ControlItem -import io.noties.adapt.sample.samples.adaptui.Colors +import io.noties.adapt.sample.ui.color.black +import io.noties.adapt.sample.ui.color.white +import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.sample.util.SampleUtil import io.noties.adapt.sample.util.html import io.noties.adapt.ui.ViewElement diff --git a/sample/src/main/java/io/noties/adapt/sample/explore/ExploreAdaptUIBinding.kt b/sample/src/main/java/io/noties/adapt/sample/explore/ExploreAdaptUIBinding.kt index 3eef9f0c..aa262f8d 100644 --- a/sample/src/main/java/io/noties/adapt/sample/explore/ExploreAdaptUIBinding.kt +++ b/sample/src/main/java/io/noties/adapt/sample/explore/ExploreAdaptUIBinding.kt @@ -8,6 +8,7 @@ import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewElement import io.noties.adapt.ui.ViewFactory import io.noties.adapt.ui.background +import io.noties.adapt.ui.backgroundColor import io.noties.adapt.ui.element.Text import io.noties.adapt.ui.element.textColor import io.noties.adapt.ui.item.ElementItemNoRef @@ -153,7 +154,7 @@ class ExploreAdaptUIBinding { // .textStylePrimary() .bind(color) { Debug.i("color:$it") - background(it) + backgroundColor(it) } } } diff --git a/sample/src/main/java/io/noties/adapt/sample/explore/ExplorePreviewDrawBounds.kt b/sample/src/main/java/io/noties/adapt/sample/explore/ExplorePreviewDrawBounds.kt index 3f1b2c07..b9f1dd9a 100644 --- a/sample/src/main/java/io/noties/adapt/sample/explore/ExplorePreviewDrawBounds.kt +++ b/sample/src/main/java/io/noties/adapt/sample/explore/ExplorePreviewDrawBounds.kt @@ -3,12 +3,12 @@ package io.noties.adapt.sample.explore import android.view.View import android.view.ViewGroup import androidx.annotation.ColorInt -import io.noties.adapt.sample.samples.adaptui.Colors +import io.noties.adapt.sample.ui.color.black import io.noties.adapt.sample.util.children import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewElement +import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.shape.Label -import io.noties.adapt.ui.shape.Rectangle import io.noties.adapt.ui.shape.RectangleShape object ExplorePreviewDrawBounds { diff --git a/sample/src/main/java/io/noties/adapt/sample/explore/TextSizesExplore.kt b/sample/src/main/java/io/noties/adapt/sample/explore/TextSizesExplore.kt new file mode 100644 index 00000000..f1556072 --- /dev/null +++ b/sample/src/main/java/io/noties/adapt/sample/explore/TextSizesExplore.kt @@ -0,0 +1,28 @@ +package io.noties.adapt.sample.explore + +import android.graphics.drawable.ColorDrawable +import io.noties.adapt.sample.R +import io.noties.adapt.sample.ui.color.accent +import io.noties.adapt.ui.app.App +import io.noties.adapt.ui.shape.AssetShape +import io.noties.debug.Debug + +object TextSizesExplore { + fun hey() { + val id = R.dimen.some_scaled_dimen + val resources = App.shared.resources + val density = resources.displayMetrics.density + val scaledDensity = resources.displayMetrics.scaledDensity + // all are the same :explode: + val dimension = resources.getDimension(id) +// val offset = resources.getDimensionPixelOffset(id) +// val size = resources.getDimensionPixelSize(id) + Debug.i("dimension:$dimension dp:${dimension / density} sp:${dimension / scaledDensity}") + } + + fun asset() { + val shape = AssetShape(ColorDrawable()) { + tint { accent } + } + } +} \ No newline at end of file diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIAccessibilitySample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIAccessibilitySample.kt index 4766437e..4d9debbb 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIAccessibilitySample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIAccessibilitySample.kt @@ -9,6 +9,8 @@ import android.view.ViewGroup import io.noties.adapt.sample.R import io.noties.adapt.sample.SampleView import io.noties.adapt.sample.annotation.AdaptSample +import io.noties.adapt.sample.ui.color.orange +import io.noties.adapt.sample.ui.color.primary import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.ViewElement @@ -16,6 +18,7 @@ import io.noties.adapt.ui.ViewFactory import io.noties.adapt.ui.accessibilityDescription import io.noties.adapt.ui.accessibilityLabelFor import io.noties.adapt.ui.accessibilityTraversalBefore +import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.background import io.noties.adapt.ui.element.HStack import io.noties.adapt.ui.element.Spacer diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUICastSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUICastSample.kt index 981bc2d0..c09ae48a 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUICastSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUICastSample.kt @@ -11,12 +11,18 @@ import android.widget.TextView import io.noties.adapt.sample.R import io.noties.adapt.sample.SampleView import io.noties.adapt.sample.annotation.AdaptSample +import io.noties.adapt.sample.ui.color.black +import io.noties.adapt.sample.ui.color.orange +import io.noties.adapt.sample.ui.color.primary +import io.noties.adapt.sample.ui.color.white import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewElement import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.background +import io.noties.adapt.ui.backgroundColor import io.noties.adapt.ui.castLayout import io.noties.adapt.ui.castView import io.noties.adapt.ui.element.Element @@ -26,9 +32,9 @@ import io.noties.adapt.ui.element.VStack import io.noties.adapt.ui.element.ZStack import io.noties.adapt.ui.element.text import io.noties.adapt.ui.element.textColor -import io.noties.adapt.ui.element.textFont import io.noties.adapt.ui.element.textGravity import io.noties.adapt.ui.element.textSize +import io.noties.adapt.ui.element.textTypeface import io.noties.adapt.ui.ifCastLayout import io.noties.adapt.ui.ifCastView import io.noties.adapt.ui.layout @@ -97,7 +103,7 @@ class AdaptUICastSample : SampleView() { // very helpful as it won't point to the actual // call there this happened - Element { CheckBox(it) as TextView } + Element { CheckBox(it) } .text("Unsafe cast (success)") .textSize(16) .padding(16) @@ -113,6 +119,7 @@ class AdaptUICastSample : SampleView() { .also { element -> element.onClick { try { + @Suppress("UNCHECKED_CAST") (element as ViewElement).render { it.checked(true) } @@ -130,7 +137,10 @@ class AdaptUICastSample : SampleView() { private fun ViewFactory.Title(title: String) = Text(title) .textColor(Colors.black) .textSize(21) - .textFont(fontStyle = Typeface.BOLD) + .textTypeface { bold } + .textTypeface(Typeface.DEFAULT_BOLD) { + bold + } @Suppress("FunctionName") private fun ViewFactory.Button(title: String) = Text(title) @@ -180,13 +190,13 @@ class AdaptUICastSample : SampleView() { element.ifCastView(CheckBox::class) { it.checked(true) .layout(FILL, 128) - .background(Colors.orange) + .backgroundColor { orange } }.render() } else { element.castView(CheckBox::class) .checked(true) .layout(FILL, 128) - .background(Colors.orange) + .backgroundColor { orange } .render() } } @@ -220,13 +230,14 @@ class AdaptUICastSample : SampleView() { .onClick { if (isIf) { element.ifCastLayout(LinearLayout.LayoutParams::class) { - it.background(Colors.orange) + it + .backgroundColor { orange } .layout(FILL, 128) }.render() } else { element.castLayout(LinearLayout.LayoutParams::class) - .background(Colors.orange) + .backgroundColor { orange } .layout(FILL, 128) .render() } diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIClipSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIClipSample.kt index 0d3c71df..b6462cc7 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIClipSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIClipSample.kt @@ -8,11 +8,15 @@ import android.widget.LinearLayout import io.noties.adapt.sample.R import io.noties.adapt.sample.SampleView import io.noties.adapt.sample.annotation.AdaptSample +import io.noties.adapt.sample.ui.color.black +import io.noties.adapt.sample.ui.color.orange +import io.noties.adapt.sample.ui.color.white import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewElement import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.background import io.noties.adapt.ui.clipToOutline import io.noties.adapt.ui.element.HStack diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIDrawableStateSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIDrawableStateSample.kt index a9ad2a16..1e5d6ca1 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIDrawableStateSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIDrawableStateSample.kt @@ -8,10 +8,16 @@ import android.widget.LinearLayout import androidx.annotation.RequiresApi import io.noties.adapt.sample.SampleView import io.noties.adapt.sample.annotation.AdaptSample +import io.noties.adapt.sample.ui.color.accent +import io.noties.adapt.sample.ui.color.black +import io.noties.adapt.sample.ui.color.orange +import io.noties.adapt.sample.ui.color.primary +import io.noties.adapt.sample.ui.color.white import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.background import io.noties.adapt.ui.clipToOutline import io.noties.adapt.ui.element.HStack @@ -20,10 +26,10 @@ import io.noties.adapt.ui.element.VStack import io.noties.adapt.ui.element.View import io.noties.adapt.ui.element.textAllCaps import io.noties.adapt.ui.element.textColor -import io.noties.adapt.ui.element.textFont import io.noties.adapt.ui.element.textGravity import io.noties.adapt.ui.element.textSingleLine import io.noties.adapt.ui.element.textSize +import io.noties.adapt.ui.element.textTypeface import io.noties.adapt.ui.focusable import io.noties.adapt.ui.foregroundDefaultSelectable import io.noties.adapt.ui.ifAvailable @@ -165,7 +171,7 @@ class AdaptUIDrawableStateSample : AdaptUISampleView() { label: String ) = Text(label) .textSize(21) - .textFont(Typeface.MONOSPACE) + .textTypeface(Typeface.MONOSPACE) .textAllCaps() .textColor(Colors.white) .textSingleLine() @@ -235,7 +241,7 @@ class AdaptUIDrawableStateSample : AdaptUISampleView() { textSize(18) textStrikethrough() textGravity(Gravity.leading.bottom) - textColor(Colors.accent) + textColor { accent } padding(8) } } diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIDynamicItemSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIDynamicItemSample.kt index e3a0bf17..ed1be2f1 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIDynamicItemSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIDynamicItemSample.kt @@ -7,10 +7,15 @@ import android.view.ViewGroup import android.widget.TextView import io.noties.adapt.sample.SampleView import io.noties.adapt.sample.annotation.AdaptSample +import io.noties.adapt.sample.ui.color.accent +import io.noties.adapt.sample.ui.color.black +import io.noties.adapt.sample.ui.color.orange +import io.noties.adapt.sample.ui.color.white import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.background import io.noties.adapt.ui.clipToOutline import io.noties.adapt.ui.element.HStack @@ -124,15 +129,15 @@ class AdaptUIDynamicItemSample : AdaptUISampleView() { Text() .reference(ref::letterView) .layout(96, 96) - .textGravity(Gravity.center) - .textColor(Colors.white) + .textGravity { center } + .textColor { white } .textBold() .textSize(24) Text() .reference(ref::textView) - .layoutGravity(Gravity.center.vertical) + .layoutGravity { center.vertical } .layoutMargin(leading = 8) - .textColor(Colors.black) + .textColor { black } .textSize(17) }.indent() .background(base.copy()) diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIElementItem.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIElementItem.kt index 5602e151..fc46d18d 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIElementItem.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIElementItem.kt @@ -7,7 +7,9 @@ import android.widget.TextView import io.noties.adapt.Item import io.noties.adapt.preview.AdaptPreviewLayout import io.noties.adapt.sample.App +import io.noties.adapt.sample.ui.color.black import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.background import io.noties.adapt.ui.element.Text import io.noties.adapt.ui.element.VStack @@ -17,7 +19,6 @@ import io.noties.adapt.ui.element.textSize import io.noties.adapt.ui.item.ElementItem import io.noties.adapt.ui.padding import io.noties.adapt.ui.reference -import io.noties.adapt.ui.shape.RoundedRectangle import io.noties.adapt.ui.shape.RoundedRectangleShape import io.noties.adapt.ui.util.Gravity import io.noties.adapt.ui.util.createLayoutParams @@ -33,7 +34,7 @@ class AdaptUIElementItem(val text: String) : Text() // already SP .textSize(16) - .textColor(Colors.black) + .textColor { black } .textGravity(Gravity.center) // values are already DP .padding(vertical = 24, horizontal = 16) diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIElementLayoutSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIElementLayoutSample.kt index c6bab0d6..4f600fa2 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIElementLayoutSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIElementLayoutSample.kt @@ -8,10 +8,12 @@ import android.view.ViewGroup import io.noties.adapt.sample.R import io.noties.adapt.sample.SampleView import io.noties.adapt.sample.annotation.AdaptSample +import io.noties.adapt.sample.ui.color.black import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.background import io.noties.adapt.ui.element.HStack import io.noties.adapt.ui.element.Text @@ -20,9 +22,10 @@ import io.noties.adapt.ui.element.VStack import io.noties.adapt.ui.element.View import io.noties.adapt.ui.element.ZStack import io.noties.adapt.ui.element.text +import io.noties.adapt.ui.element.textBold import io.noties.adapt.ui.element.textColor -import io.noties.adapt.ui.element.textFont import io.noties.adapt.ui.element.textSize +import io.noties.adapt.ui.element.textTypeface import io.noties.adapt.ui.layout import io.noties.adapt.ui.layoutFill import io.noties.adapt.ui.layoutGravity @@ -65,9 +68,9 @@ class AdaptUIElementLayoutSample : SampleView() { private fun ViewFactory.Header(text: String) { Text(text) .text(text) - .textFont(Typeface.DEFAULT_BOLD) + .textBold() .textSize(16) - .textColor(Colors.black) + .textColor { black } .padding(horizontal = 16, vertical = 8) } diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIExtensionsSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIExtensionsSample.kt index 78542a67..7d756714 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIExtensionsSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIExtensionsSample.kt @@ -1,7 +1,6 @@ package io.noties.adapt.sample.samples.adaptui import android.content.Context -import android.graphics.Typeface import android.util.AttributeSet import android.view.View import android.view.ViewGroup @@ -13,17 +12,20 @@ import io.noties.adapt.sample.annotation.AdaptSample import io.noties.adapt.sample.samples.adaptui.AdaptUIExtensionsSample.TextStyles.heyHey import io.noties.adapt.sample.samples.adaptui.AdaptUIExtensionsSample.TextStyles.heyHo import io.noties.adapt.sample.samples.adaptui.AdaptUIExtensionsSample.TextStyles.textStylePrimary +import io.noties.adapt.sample.ui.color.accent +import io.noties.adapt.sample.ui.color.orange +import io.noties.adapt.sample.ui.color.primary import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewElement import io.noties.adapt.ui.ViewFactory -import io.noties.adapt.ui.background +import io.noties.adapt.ui.backgroundColor import io.noties.adapt.ui.element.Text import io.noties.adapt.ui.element.VScroll import io.noties.adapt.ui.element.VStack +import io.noties.adapt.ui.element.textBold import io.noties.adapt.ui.element.textColor -import io.noties.adapt.ui.element.textFont import io.noties.adapt.ui.element.textGravity import io.noties.adapt.ui.element.textSize import io.noties.adapt.ui.layout @@ -54,7 +56,7 @@ class AdaptUIExtensionsSample : SampleView() { Text("heyHo") .heyHo() // after this no longer ViewElement // only generic functions are available to all views and all layout params - .background(Colors.orange) + .backgroundColor { orange } } } } @@ -65,19 +67,19 @@ class AdaptUIExtensionsSample : SampleView() { // becomes available to all subclasses of TextView with any LayoutParams fun ViewElement.textStylePrimary() = this .textSize(16) - .textColor(Colors.primary) + .textColor { primary } .textGravity(Gravity.top) - .textFont(fontStyle = Typeface.BOLD) + .textBold() // becomes available to only TextView elements (no subclasses would be allowed, // no AppCompatTextView, EditText, no Button, no CheckBox, etc) fun ViewElement.textStyleSecondary() = this .textSize(17) - .textColor(Colors.primary) + .textColor { primary } // becomes available to only TextView inside a LinearLayout fun ViewElement.textStyleAdditional() = this - .textColor(Colors.primary) + .textColor { primary } .textGravity(Gravity.center) .layoutWeight(1F) @@ -85,7 +87,7 @@ class AdaptUIExtensionsSample : SampleView() { // Extension also does not need to return ViewElement, but in this case // no further calls for customization would be available fun ViewElement<*, *>.heyHey() { - background(Colors.primary) + backgroundColor { primary } padding(48) } @@ -99,7 +101,7 @@ class AdaptUIExtensionsSample : SampleView() { // it is possible to return this, but this would have types erased and thus // type information missing fun ViewElement<*, *>.heyHo() = this - .background(Colors.accent) + .backgroundColor { accent } } } diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIGradientSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIGradientSample.kt index 1a952252..bb55056f 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIGradientSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIGradientSample.kt @@ -6,11 +6,17 @@ import android.view.ViewGroup import io.noties.adapt.Item import io.noties.adapt.sample.SampleView import io.noties.adapt.sample.annotation.AdaptSample +import io.noties.adapt.sample.ui.color.accent +import io.noties.adapt.sample.ui.color.black +import io.noties.adapt.sample.ui.color.orange +import io.noties.adapt.sample.ui.color.primary +import io.noties.adapt.sample.ui.color.yellow import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewFactory import io.noties.adapt.ui.adaptView +import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.background import io.noties.adapt.ui.element.HStack import io.noties.adapt.ui.element.Text diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIInflateSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIInflateSample.kt index 650df7f1..387c1fa7 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIInflateSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIInflateSample.kt @@ -7,11 +7,16 @@ import android.widget.LinearLayout import io.noties.adapt.sample.R import io.noties.adapt.sample.SampleView import io.noties.adapt.sample.annotation.AdaptSample +import io.noties.adapt.sample.ui.color.accent +import io.noties.adapt.sample.ui.color.orange +import io.noties.adapt.sample.ui.color.yellow import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.background +import io.noties.adapt.ui.backgroundColor import io.noties.adapt.ui.castView import io.noties.adapt.ui.element.Inflated import io.noties.adapt.ui.element.VStack @@ -65,9 +70,9 @@ class AdaptUIInflateSample : AdaptUISampleView() { // it.background(Colors.black) // } .layoutFill() - .background(Colors.yellow) + .backgroundColor { yellow } .ifCastView(LinearLayout::class) { - it.background(Colors.accent) + it.backgroundColor { accent } } .castView(LinearLayout::class) .stackGravity(Gravity.center.vertical) diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIItemSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIItemSample.kt index 35cb0a77..5cc7dff4 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIItemSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIItemSample.kt @@ -9,10 +9,13 @@ import io.noties.adapt.sample.R import io.noties.adapt.sample.SampleView import io.noties.adapt.sample.annotation.AdaptSample import io.noties.adapt.sample.items.CardItem +import io.noties.adapt.sample.ui.color.black +import io.noties.adapt.sample.ui.color.orange import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.ViewFactory import io.noties.adapt.ui.adaptView +import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.background import io.noties.adapt.ui.element.Text import io.noties.adapt.ui.element.VStack @@ -43,7 +46,7 @@ class AdaptUIItemSample : SampleView() { Text("This is just some inline text") .textSize(16) - .textColor(Colors.black) + .textColor { black } .padding(16) View() diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIItemsSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIItemsSample.kt index 614521c1..79319767 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIItemsSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIItemsSample.kt @@ -9,10 +9,13 @@ import io.noties.adapt.Item import io.noties.adapt.sample.R import io.noties.adapt.sample.SampleView import io.noties.adapt.sample.annotation.AdaptSample +import io.noties.adapt.sample.ui.color.black +import io.noties.adapt.sample.ui.color.orange import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.ViewFactory import io.noties.adapt.ui.adaptViewGroup +import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.background import io.noties.adapt.ui.element.VScroll import io.noties.adapt.ui.element.VStack diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUILazySample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUILazySample.kt index 62cbe471..2260ef7c 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUILazySample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUILazySample.kt @@ -8,10 +8,13 @@ import android.view.ViewGroup import io.noties.adapt.sample.R import io.noties.adapt.sample.SampleView import io.noties.adapt.sample.annotation.AdaptSample +import io.noties.adapt.sample.ui.color.accent +import io.noties.adapt.sample.ui.color.black +import io.noties.adapt.sample.ui.color.orange import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.ViewFactory -import io.noties.adapt.ui.background +import io.noties.adapt.ui.backgroundColor import io.noties.adapt.ui.element.Lazy import io.noties.adapt.ui.element.Text import io.noties.adapt.ui.element.VStack @@ -37,7 +40,7 @@ class AdaptUILazySample : SampleView() { Text("Some text at top") .textSize(24) - .textColor(Colors.black) + .textColor { black } .padding(horizontal = 16) .padding(top = 24, bottom = 8) @@ -45,13 +48,13 @@ class AdaptUILazySample : SampleView() { // layout params of parent can be used Text("layout_weight:1") - .background(Colors.orange) + .backgroundColor { orange } .padding(16) .layoutWeight(1F) // multiple views can be _lazy_ -> all would be added to parent Text("another layout_weight:1") - .background(Colors.accent) + .backgroundColor { accent } .padding(16) .layoutWeight(1F) } diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUILinearGradientSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUILinearGradientSample.kt index 4d88a0a8..a8b21899 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUILinearGradientSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUILinearGradientSample.kt @@ -15,10 +15,14 @@ import android.os.Looper import android.util.AttributeSet import io.noties.adapt.sample.SampleView import io.noties.adapt.sample.annotation.AdaptSample +import io.noties.adapt.sample.ui.color.accent +import io.noties.adapt.sample.ui.color.black +import io.noties.adapt.sample.ui.color.orange import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.background import io.noties.adapt.ui.element.View import io.noties.adapt.ui.element.ZStack diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIOverlaySample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIOverlaySample.kt index 3c71dc52..bd2fd712 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIOverlaySample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIOverlaySample.kt @@ -7,11 +7,16 @@ import androidx.annotation.ColorInt import io.noties.adapt.sample.R import io.noties.adapt.sample.SampleView import io.noties.adapt.sample.annotation.AdaptSample +import io.noties.adapt.sample.ui.color.black +import io.noties.adapt.sample.ui.color.orange +import io.noties.adapt.sample.ui.color.primary import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.background +import io.noties.adapt.ui.backgroundColor import io.noties.adapt.ui.element.HStack import io.noties.adapt.ui.element.Image import io.noties.adapt.ui.element.Text @@ -30,7 +35,6 @@ import io.noties.adapt.ui.overlayView import io.noties.adapt.ui.padding import io.noties.adapt.ui.shape.Circle import io.noties.adapt.ui.util.Gravity -import io.noties.adapt.ui.util.hex import io.noties.adapt.ui.util.withAlphaComponent @AdaptSample( @@ -84,8 +88,8 @@ class AdaptUIOverlaySample : AdaptUISampleView() { .padding(16) .textGravity(Gravity.center) .textSize(21) - .textColor(Colors.black) - .background(backgroundColor.withAlphaComponent(0.2F)) + .textColor { black } + .backgroundColor(backgroundColor.withAlphaComponent(0.2F)) @Suppress("unused") private val Colors.overlay: Int get() = hex("#f00") diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIPagerSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIPagerSample.kt index 2b2997a3..81f32157 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIPagerSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIPagerSample.kt @@ -11,10 +11,14 @@ import android.widget.TextView import io.noties.adapt.sample.R import io.noties.adapt.sample.SampleView import io.noties.adapt.sample.annotation.AdaptSample +import io.noties.adapt.sample.ui.color.black +import io.noties.adapt.sample.ui.color.orange +import io.noties.adapt.sample.ui.color.white import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.ViewElement import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.background import io.noties.adapt.ui.element.HStack import io.noties.adapt.ui.element.Image diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIPivotSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIPivotSample.kt index 08e3947a..e695f40b 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIPivotSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIPivotSample.kt @@ -10,11 +10,15 @@ import android.util.AttributeSet import android.view.ViewGroup.MarginLayoutParams import io.noties.adapt.sample.SampleView import io.noties.adapt.sample.annotation.AdaptSample +import io.noties.adapt.sample.ui.color.black +import io.noties.adapt.sample.ui.color.yellow import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewFactory -import io.noties.adapt.ui.background +import io.noties.adapt.ui.app.color.Colors +import io.noties.adapt.ui.app.color.hex +import io.noties.adapt.ui.backgroundColor import io.noties.adapt.ui.element.Text import io.noties.adapt.ui.element.VStack import io.noties.adapt.ui.element.textColor @@ -33,7 +37,6 @@ import io.noties.adapt.ui.pivot import io.noties.adapt.ui.pivotRelative import io.noties.adapt.ui.util.Gravity import io.noties.adapt.ui.util.dip -import io.noties.adapt.ui.util.hex @AdaptSample( id = "20230715160110", @@ -48,7 +51,7 @@ class AdaptUIPivotSample : AdaptUISampleView() { Text("Click to rotate around pivot point.\nLong click to scale up or down") .textGravity(Gravity.center.horizontal) .textSize(16) - .textColor(Colors.black) + .textColor { black } .padding(16) // Default pivot values (should be at view center) @@ -76,10 +79,10 @@ class AdaptUIPivotSample : AdaptUISampleView() { @Suppress("FunctionName") private fun ViewFactory.PivotElement() = Text("PIVOT") - .textColor(Colors.black) + .textColor { black } .textSize(16) .layoutWrap() - .background(Colors.yellow) + .backgroundColor { yellow } .layoutMargin(16) .padding(16) .also { el -> @@ -149,9 +152,7 @@ class AdaptUIPivotSample : AdaptUISampleView() { } } -@Suppress("unused") -private val Colors.pivot: Int - get() = hex("#f00") +private val Colors.pivot by hex("#f00") @Preview @Suppress("ClassName", "unused") diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIRelativeSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIRelativeSample.kt index c141ac40..c3ad6a64 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIRelativeSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIRelativeSample.kt @@ -13,11 +13,16 @@ import io.noties.adapt.sample.explore.ExploreRelative.Relative import io.noties.adapt.sample.explore.ExploreRelative.layoutAlignParent import io.noties.adapt.sample.explore.ExploreRelative.layoutCenter import io.noties.adapt.sample.explore.ExploreRelative.layoutPosition +import io.noties.adapt.sample.ui.color.accent +import io.noties.adapt.sample.ui.color.black +import io.noties.adapt.sample.ui.color.orange +import io.noties.adapt.sample.ui.color.primary import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewFactory -import io.noties.adapt.ui.background +import io.noties.adapt.ui.app.color.Colors +import io.noties.adapt.ui.backgroundColor import io.noties.adapt.ui.element.Text import io.noties.adapt.ui.layoutFill import io.noties.adapt.ui.layoutWrap @@ -68,19 +73,19 @@ class AdaptUIRelativeSample : AdaptUISampleView() { val el = Text("alignParent{leading,top}") .padding(16) - .background(Colors.orange) + .backgroundColor { orange } .layoutWrap() .layoutAlignParent(leading = true, top = true) Text("below") .padding(16) - .background(Colors.accent) + .backgroundColor { accent } .layoutPosition(below = el) .layoutCenter(horizontal = true) Text("alignTrailing") .padding(16) - .background(Colors.primary) + .backgroundColor { primary } .layoutPosition(toTrailing = el) }.layoutFill() diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUISample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUISample.kt index 63093835..d1c9ed3c 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUISample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUISample.kt @@ -4,7 +4,6 @@ package io.noties.adapt.sample.samples.adaptui import android.content.Context import android.graphics.Color -import android.graphics.Typeface import android.util.AttributeSet import android.view.View import android.view.ViewGroup @@ -22,6 +21,7 @@ import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewElement import io.noties.adapt.ui.ViewFactory import io.noties.adapt.ui.background +import io.noties.adapt.ui.backgroundColor import io.noties.adapt.ui.clipChildren import io.noties.adapt.ui.clipToPadding import io.noties.adapt.ui.element.Element @@ -35,8 +35,8 @@ import io.noties.adapt.ui.element.View import io.noties.adapt.ui.element.ZStack import io.noties.adapt.ui.element.scrollFillViewPort import io.noties.adapt.ui.element.textAllCaps +import io.noties.adapt.ui.element.textBold import io.noties.adapt.ui.element.textColor -import io.noties.adapt.ui.element.textFont import io.noties.adapt.ui.element.textGravity import io.noties.adapt.ui.element.textHideIfEmpty import io.noties.adapt.ui.element.textSize @@ -63,7 +63,6 @@ import io.noties.adapt.ui.shape.OvalShape import io.noties.adapt.ui.shape.Rectangle import io.noties.adapt.ui.shape.RectangleShape import io.noties.adapt.ui.shape.RoundedRectangle -import io.noties.adapt.ui.shape.RoundedRectangleShape import io.noties.adapt.ui.shape.Shape import io.noties.adapt.ui.shape.StatefulShape import io.noties.adapt.ui.shape.copy @@ -288,7 +287,7 @@ class AdaptUISample : SampleView() { }) Element(::Button) .textSize(17) - .textFont(null, Typeface.BOLD) + .textBold() .reference(ref::textView) .reference(ref::textViewNullable) .also { ref.textViewElement = it } @@ -319,7 +318,7 @@ class AdaptUISample : SampleView() { paragraph() .layout(FILL, WRAP) - .background(Color.RED) + .backgroundColor(Color.RED) .layoutMargin(8) HStack { @@ -328,7 +327,7 @@ class AdaptUISample : SampleView() { Spacer() View() .layout(1, 16) - .background(Color.GREEN) + .backgroundColor(Color.GREEN) Spacer() Text() .reference(ref::valueView) @@ -355,7 +354,7 @@ class AdaptUISample : SampleView() { private fun ViewFactory.square(color: Int) { View() .layout(128, 128) - .background(color) + .backgroundColor(color) .layoutMargin(horizontal = 2) } @@ -366,7 +365,7 @@ class AdaptUISample : SampleView() { .textSize(12) .textColor(Color.WHITE) .padding(16) - .background(Color.RED) + .backgroundColor(Color.RED) // this does not return anything, it is valid, but no further customization // would be available @@ -376,7 +375,7 @@ class AdaptUISample : SampleView() { .textColor(Color.RED) .textGravity(Gravity.trailing) .padding(12) - .background(Color.BLACK) + .backgroundColor(Color.BLACK) } // this would allow configuring layout params of ViewGroup, @@ -387,7 +386,7 @@ class AdaptUISample : SampleView() { .textSize(48) .padding(24) .textColor(Color.GRAY) - .background(Color.CYAN) + .backgroundColor(Color.CYAN) override fun bind(holder: Holder) { with(holder.ref) { @@ -403,7 +402,7 @@ class AdaptUISample : SampleView() { Text("This is button") .textColor(Color.WHITE) .textGravity(Gravity.center) - .textFont(fontStyle = Typeface.BOLD) + .textBold() .textSize(16) .padding(horizontal = 16, vertical = 8) .background(background) @@ -476,7 +475,7 @@ class AdaptUISample : SampleView() { VStack { Text("First line") .textSize(24) - .textFont(fontStyle = Typeface.BOLD) + .textBold() Text("Second line") .textSize(16) }.background(background) @@ -487,7 +486,7 @@ class AdaptUISample : SampleView() { }.clipToPadding(false) .clipChildren(false) .padding(16) - .background(0x10000000) + .backgroundColor(0x10000000) } private val background = StatefulShape.drawable { @@ -519,7 +518,7 @@ class AdaptUISample : SampleView() { VStack { Text("The title") .textSize(21) - .textFont(fontStyle = Typeface.BOLD) + .textBold() .textColor(Color.BLACK) Text("350") .textSize(16) @@ -535,7 +534,7 @@ class AdaptUISample : SampleView() { setActivated(Color.BLUE) setDefault(Color.WHITE) }) - .textFont(fontStyle = Typeface.BOLD) + .textBold() .padding(horizontal = 24, vertical = 8) .onView { it.background = toggleDrawable @@ -545,11 +544,7 @@ class AdaptUISample : SampleView() { } }.padding(8) .padding(bottom = 12) - .pressable(RoundedRectangleShape(12) { - stroke(Color.BLACK, 2) - fill(Color.WHITE) - padding(1) - }) + .pressable() .onClick { Debug.i("pressable clicked!") } @@ -573,7 +568,6 @@ class AdaptUISample : SampleView() { } private fun ViewElement.pressable( - shape: Shape ): ViewElement = onView { view -> val distance = 6 diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIShapeSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIShapeSample.kt index 91233719..bcce82f1 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIShapeSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIShapeSample.kt @@ -15,10 +15,15 @@ import io.noties.adapt.sample.R import io.noties.adapt.sample.SampleView import io.noties.adapt.sample.annotation.AdaptSample import io.noties.adapt.sample.explore.ExploreShapePath +import io.noties.adapt.sample.ui.color.accent +import io.noties.adapt.sample.ui.color.black +import io.noties.adapt.sample.ui.color.orange +import io.noties.adapt.sample.ui.color.primary import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.background import io.noties.adapt.ui.element.HStack import io.noties.adapt.ui.element.VScroll diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIShapeShadowSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIShapeShadowSample.kt index a721da0a..70da828e 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIShapeShadowSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIShapeShadowSample.kt @@ -1,7 +1,6 @@ package io.noties.adapt.sample.samples.adaptui import android.content.Context -import android.graphics.Typeface import android.os.Build import android.util.AttributeSet import android.view.View @@ -12,11 +11,17 @@ import io.noties.adapt.recyclerview.AdaptRecyclerView import io.noties.adapt.sample.R import io.noties.adapt.sample.SampleView import io.noties.adapt.sample.annotation.AdaptSample +import io.noties.adapt.sample.ui.color.accent +import io.noties.adapt.sample.ui.color.black +import io.noties.adapt.sample.ui.color.orange +import io.noties.adapt.sample.ui.color.primary +import io.noties.adapt.sample.ui.color.white import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewFactory import io.noties.adapt.ui.adaptRecyclerView +import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.background import io.noties.adapt.ui.clipToOutline import io.noties.adapt.ui.element.Recycler @@ -26,8 +31,8 @@ import io.noties.adapt.ui.element.View import io.noties.adapt.ui.element.ZStack import io.noties.adapt.ui.element.recyclerGridLayoutManager import io.noties.adapt.ui.element.textAutoSize +import io.noties.adapt.ui.element.textBold import io.noties.adapt.ui.element.textColor -import io.noties.adapt.ui.element.textFont import io.noties.adapt.ui.element.textSize import io.noties.adapt.ui.foregroundDefaultSelectable import io.noties.adapt.ui.ifAvailable @@ -230,7 +235,7 @@ class AdaptUIShapeShadowSample : AdaptUISampleView() { Text("The title of the card!") .textSize(20) .textColor(Colors.orange) - .textFont(fontStyle = Typeface.BOLD) + .textBold() .translation(x = -16, y = -16) Text("The text on top is clipped, so no content would exit the card area") diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIShapeTextSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIShapeTextSample.kt index 1b8af08f..0528bb1d 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIShapeTextSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIShapeTextSample.kt @@ -7,11 +7,18 @@ import android.util.AttributeSet import androidx.annotation.RequiresApi import io.noties.adapt.sample.SampleView import io.noties.adapt.sample.annotation.AdaptSample +import io.noties.adapt.sample.ui.color.accent +import io.noties.adapt.sample.ui.color.black +import io.noties.adapt.sample.ui.color.orange +import io.noties.adapt.sample.ui.color.primary +import io.noties.adapt.sample.ui.color.white +import io.noties.adapt.sample.ui.color.yellow import io.noties.adapt.sample.util.HtmlUtil import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.background import io.noties.adapt.ui.element.Text import io.noties.adapt.ui.element.VStack @@ -52,7 +59,7 @@ class AdaptUIShapeTextSample : AdaptUISampleView() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { Text("!!!Text shape is available only since O (26)") .textSize(18) - .textColor(Colors.black) + .textColor { black } .layoutWrap() .layoutGravity(Gravity.center) return@VStack @@ -76,8 +83,8 @@ class AdaptUIShapeTextSample : AdaptUISampleView() { Text { text("Hello there! ".repeat(10)) textSize(21) - textColor(Colors.white) - textGravity(Gravity.center) + textColor { white } + textGravity { center } textMaxLines(4) textShadow(4, color = Colors.orange) textBold() @@ -86,7 +93,7 @@ class AdaptUIShapeTextSample : AdaptUISampleView() { textUnderline() textStrikethrough() - fill(Colors.yellow) + fill { yellow } // sizeRelative(0.5F, 0.5F, gravity = Gravity.center) padding(24) @@ -106,7 +113,7 @@ class AdaptUIShapeTextSample : AdaptUISampleView() { } Circle { - fill(Colors.accent) + fill { accent } } } } diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIStickySample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIStickySample.kt index 4deb608f..497a0a7a 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIStickySample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIStickySample.kt @@ -7,12 +7,15 @@ import android.view.View import android.view.ViewGroup import io.noties.adapt.sample.SampleView import io.noties.adapt.sample.annotation.AdaptSample +import io.noties.adapt.sample.ui.color.black +import io.noties.adapt.sample.ui.color.orange +import io.noties.adapt.sample.ui.color.white import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewFactory import io.noties.adapt.ui.alpha -import io.noties.adapt.ui.background +import io.noties.adapt.ui.backgroundColor import io.noties.adapt.ui.element.Text import io.noties.adapt.ui.element.VScroll import io.noties.adapt.ui.element.textColor @@ -22,9 +25,9 @@ import io.noties.adapt.ui.layoutFill import io.noties.adapt.ui.onClick import io.noties.adapt.ui.onElementView import io.noties.adapt.ui.padding -import io.noties.adapt.ui.widget.VStackReverseDrawingOrder import io.noties.adapt.ui.sticky.stickyVerticalScrollContainer import io.noties.adapt.ui.sticky.stickyView +import io.noties.adapt.ui.widget.VStackReverseDrawingOrder import io.noties.debug.Debug @AdaptSample( @@ -82,8 +85,8 @@ class AdaptUIStickySample : AdaptUISampleView() { private fun ViewFactory.Sticky(text: String) = Text(text) .padding(16) .alpha(0.75F) - .background(Colors.black) - .textColor(Colors.white) + .backgroundColor { black } + .textColor { white } .ifAvailable(Build.VERSION_CODES.M) { it.foregroundDefaultSelectable() } @@ -93,7 +96,7 @@ class AdaptUIStickySample : AdaptUISampleView() { @Suppress("FunctionName") private fun ViewFactory.Regular(text: String) = Text(text) .padding(8) - .background(Colors.orange) + .backgroundColor { orange } } @Preview diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUITextSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUITextSample.kt index a1477b66..cbb950fe 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUITextSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUITextSample.kt @@ -14,9 +14,13 @@ import io.noties.adapt.sample.explore.ExploreAutofill.autofillEnabled import io.noties.adapt.sample.explore.ExploreAutofill.autofillHint import io.noties.adapt.sample.explore.ExploreAutofill.autofillRequestOnFocusWhenEmpty import io.noties.adapt.sample.explore.ExplorePreviewDrawBounds.previewDrawBounds +import io.noties.adapt.sample.ui.color.black +import io.noties.adapt.sample.ui.color.orange +import io.noties.adapt.sample.ui.color.primary import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.background import io.noties.adapt.ui.element.BreakStrategy import io.noties.adapt.ui.element.HyphenationFrequency @@ -83,7 +87,7 @@ class AdaptUITextSample : SampleView() { private fun ViewFactory.MyTextInput() { TextInput(InputType.text) .textSize(16) - .textColor(Colors.black) + .textColor { black } .textHint("Some phone!") // .textInputType(ExploreEditorInfo.InputType.text.uri.noSuggestions.capWords) // .textImeOptions(ExploreEditorInfo.ImeOptions.actionGo.noExactUi) diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIViewGroupSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIViewGroupSample.kt index e4546aa4..7a131172 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIViewGroupSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIViewGroupSample.kt @@ -7,10 +7,14 @@ import android.view.ViewGroup import io.noties.adapt.sample.R import io.noties.adapt.sample.SampleView import io.noties.adapt.sample.annotation.AdaptSample +import io.noties.adapt.sample.ui.color.black +import io.noties.adapt.sample.ui.color.orange +import io.noties.adapt.sample.ui.color.white import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.background import io.noties.adapt.ui.element.Text import io.noties.adapt.ui.element.VStack @@ -80,7 +84,7 @@ class AdaptUIViewGroupSample : SampleView() { Text("CLICK ME") .padding(horizontal = 16, vertical = 8) - .textColor(Colors.white) + .textColor { white } .background(StatefulShape.drawable { val base = RectangleShape { fill(Colors.orange) diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIWebSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIWebSample.kt index a0b4ed28..22405979 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIWebSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIWebSample.kt @@ -9,11 +9,15 @@ import android.widget.FrameLayout import android.widget.Toast import io.noties.adapt.sample.SampleView import io.noties.adapt.sample.annotation.AdaptSample +import io.noties.adapt.sample.ui.color.accent +import io.noties.adapt.sample.ui.color.black +import io.noties.adapt.sample.ui.color.orange +import io.noties.adapt.sample.ui.color.primary import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewFactory -import io.noties.adapt.ui.background +import io.noties.adapt.ui.backgroundColor import io.noties.adapt.ui.element.Progress import io.noties.adapt.ui.element.Text import io.noties.adapt.ui.element.ZStack @@ -54,17 +58,17 @@ class AdaptUIWebSample : AdaptUISampleView() { ZStack { Text("WebView inflation failed for the first time, please stand-by") .textSize(16) - .textColor(Colors.black) + .textColor { black } .layoutWrap() .layoutGravity(Gravity.center) .padding(16) }.layoutFill() - .background(Colors.orange) + .backgroundColor { orange } }.layoutFill() .webOnElementReady { // process created web view - it.background(Colors.accent) + it.backgroundColor { accent } } val progress = Progress() @@ -110,7 +114,7 @@ class AdaptUIWebSample : AdaptUISampleView() { ViewFactory.addChildren(webViewPlaceholder) { Text("Failed to inflate WebView") .textSize(21) - .textColor(Colors.primary) + .textColor { primary } .layoutWrap() .layoutGravity(Gravity.center) .padding(16) diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUiShapeLabelSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUiShapeLabelSample.kt index 5939cb94..0a975390 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUiShapeLabelSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUiShapeLabelSample.kt @@ -4,10 +4,14 @@ import android.content.Context import android.util.AttributeSet import io.noties.adapt.sample.SampleView import io.noties.adapt.sample.annotation.AdaptSample +import io.noties.adapt.sample.ui.color.accent +import io.noties.adapt.sample.ui.color.black +import io.noties.adapt.sample.ui.color.orange import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.background import io.noties.adapt.ui.element.VStack import io.noties.adapt.ui.element.View @@ -33,7 +37,7 @@ class AdaptUiShapeLabelSample : AdaptUISampleView() { LabelShape { text("Hello there! \uD83D\uDE18") .textSize(20) - .textColor(Colors.black) + .textColor { black } .textGravity(Gravity.center) .textBold() // textRotation(45F, 1F, 0F) diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/Colors.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/Colors.kt deleted file mode 100644 index 67c78667..00000000 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/Colors.kt +++ /dev/null @@ -1,23 +0,0 @@ -package io.noties.adapt.sample.samples.adaptui - -import android.os.Build -import androidx.annotation.ColorRes -import io.noties.adapt.sample.App -import io.noties.adapt.sample.R - -object Colors { - val white: Int = get(R.color.white) - val black: Int = get(R.color.black) - val orange: Int = get(R.color.orange) - val primary: Int = get(R.color.primary) - val accent: Int = get(R.color.accent) - val yellow: Int = get(R.color.yellow) - - private fun get(@ColorRes colorResId: Int): Int = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - App.shared.getColor(colorResId) - } else { - @Suppress("DEPRECATION") - App.shared.resources.getColor(colorResId) - } -} \ No newline at end of file diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/flex/AdaptUIFlexInteractiveSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/flex/AdaptUIFlexInteractiveSample.kt index c1523273..74a49527 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/flex/AdaptUIFlexInteractiveSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/flex/AdaptUIFlexInteractiveSample.kt @@ -21,11 +21,16 @@ import io.noties.adapt.sample.App import io.noties.adapt.sample.R import io.noties.adapt.sample.SampleView import io.noties.adapt.sample.annotation.AdaptSample -import io.noties.adapt.sample.samples.adaptui.Colors import io.noties.adapt.sample.samples.adaptui.SeekBar import io.noties.adapt.sample.samples.adaptui.seekBarOnChanged import io.noties.adapt.sample.samples.adaptui.seekBarTint import io.noties.adapt.sample.samples.adaptui.seekBarValue +import io.noties.adapt.sample.ui.color.accent +import io.noties.adapt.sample.ui.color.black +import io.noties.adapt.sample.ui.color.orange +import io.noties.adapt.sample.ui.color.primary +import io.noties.adapt.sample.ui.color.white +import io.noties.adapt.sample.ui.color.yellow import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.sample.util.children @@ -33,6 +38,7 @@ import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewElement import io.noties.adapt.ui.ViewFactory import io.noties.adapt.ui.activated +import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.background import io.noties.adapt.ui.backgroundDefaultSelectable import io.noties.adapt.ui.clipToOutline @@ -44,10 +50,12 @@ import io.noties.adapt.ui.element.VStack import io.noties.adapt.ui.element.View import io.noties.adapt.ui.element.ZStack import io.noties.adapt.ui.element.text +import io.noties.adapt.ui.element.textBold import io.noties.adapt.ui.element.textColor import io.noties.adapt.ui.element.textFont import io.noties.adapt.ui.element.textGravity import io.noties.adapt.ui.element.textSize +import io.noties.adapt.ui.element.textTypeface import io.noties.adapt.ui.flex.AlignContent import io.noties.adapt.ui.flex.AlignItems import io.noties.adapt.ui.flex.AlignSelf @@ -139,9 +147,7 @@ class AdaptUIFlexInteractiveSample : SampleView() { private fun ViewElement.renderInTransition(block: (ViewElement) -> Unit) { val group = (view as? ViewGroup) ?: (view.parent as ViewGroup) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - TransitionManager.endTransitions(group) - } + TransitionManager.endTransitions(group) block(this) TransitionManager.beginDelayedTransition(group) render() @@ -151,8 +157,8 @@ class AdaptUIFlexInteractiveSample : SampleView() { private fun ViewFactory.FlexItem(text: String): ViewElement = Text(text) .textSize(16) - .textColor(Colors.white) - .textFont(Typeface.DEFAULT_BOLD) + .textColor { white } + .textBold() .padding(horizontal = 16, vertical = 8) .background { RoundedRectangle(8) { @@ -222,7 +228,7 @@ class AdaptUIFlexInteractiveSample : SampleView() { .ifAvailable(Build.VERSION_CODES.M) { it.onView { it.thumbTintList = ColorStateListBuilder.create { - set(android.R.attr.state_checked, Colors.white) + setChecked(Colors.white) setDefault(hex("#505a64")) } } @@ -295,6 +301,7 @@ class AdaptUIFlexInteractiveSample : SampleView() { val base = AssetShape(drawable) { size(s, s, Gravity.center.bottom) tint(Colors.black.withAlphaComponent(0.2F)) + tint { accent } } add(base) add(base.copy { @@ -466,7 +473,7 @@ class AdaptUIFlexInteractiveSample : SampleView() { fun ViewFactory.TextEntry(isMinus: Boolean) = Text(if (isMinus) "-" else "+") .padding(horizontal = 8) - .textFont(Typeface.MONOSPACE) + .textTypeface(Typeface.MONOSPACE) .textColor(Colors.black) .textSize(20) .background(CornersShape { @@ -525,7 +532,6 @@ class AdaptUIFlexInteractiveSample : SampleView() { fun View.flexLP(): FlexboxLayout.LayoutParams = layoutParams as FlexboxLayout.LayoutParams - @SuppressLint("SetTextI18n") fun Ref.updateGrow(own: Float, total: Float) { val ownValue = own.roundToInt() if (ownValue == 0) { @@ -533,6 +539,7 @@ class AdaptUIFlexInteractiveSample : SampleView() { growIndicator.text = "0" } else { view.isActivated = true + @SuppressLint("SetTextI18n") growIndicator.text = "$ownValue / ${total.roundToInt()}" } } @@ -710,6 +717,7 @@ class AdaptUIFlexInteractiveSample : SampleView() { .layoutMargin(top = 2) .onView { tv -> (tv.parent as View).onGlobalLayout { + @SuppressLint("SetTextI18n") tv.text = "Actual size: ${it.width.pxToDip}" } } @@ -826,7 +834,7 @@ class AdaptUIFlexInteractiveSample : SampleView() { Text(name) .textSize(24) .textColor(Colors.primary) - .textFont(Typeface.DEFAULT_BOLD) + .textBold() .padding(horizontal = 16) .padding(top = 24, bottom = 8) @@ -900,7 +908,7 @@ class AdaptUIFlexInteractiveSample : SampleView() { ) = Text("$name:") .textSize(16) .textColor(if (isActive) Colors.yellow else Colors.black) - .textFont(Typeface.DEFAULT_BOLD) + .textBold() @Suppress("FunctionName") private fun ViewFactory.DropDown( diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/recyclerview/RecyclerViewSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/recyclerview/RecyclerViewSample.kt index 064a3fbe..19ea96c8 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/recyclerview/RecyclerViewSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/recyclerview/RecyclerViewSample.kt @@ -9,7 +9,7 @@ import io.noties.adapt.recyclerview.AdaptRecyclerView import io.noties.adapt.sample.R import io.noties.adapt.sample.SampleView import io.noties.adapt.sample.annotation.AdaptSample -import io.noties.adapt.sample.samples.adaptui.Colors +import io.noties.adapt.sample.ui.color.black import io.noties.adapt.ui.ViewFactory import io.noties.adapt.ui.element.Text import io.noties.adapt.ui.element.textColor @@ -50,7 +50,7 @@ class RecyclerViewSample : SampleView() { override fun ViewFactory.body() { Text(text) .textSize(24) - .textColor(Colors.black) + .textColor { black } .textGravity(Gravity.center) .onLayoutParams { Debug.e("lp:$it") diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseBasic2.kt b/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseBasic2.kt index 6c18eb7c..c43c0aca 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseBasic2.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseBasic2.kt @@ -9,6 +9,7 @@ import io.noties.adapt.sample.samples.adaptui.AdaptUISampleView import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewFactory import io.noties.adapt.ui.background +import io.noties.adapt.ui.backgroundColor import io.noties.adapt.ui.element.HScroll import io.noties.adapt.ui.element.HStack import io.noties.adapt.ui.element.Text @@ -43,19 +44,19 @@ class AdaptUIShowcaseBasic2 : AdaptUISampleView() { HStack(Gravity.center) { Text("1") .padding(72, 48) - .background(Color.RED) + .backgroundColor(Color.RED) Text("2") .padding(72, 32) - .background(Color.GREEN) + .backgroundColor(Color.GREEN) Text("3") .padding(72, 24) - .background(Color.BLUE) + .backgroundColor(Color.BLUE) } } Text("This will fill") .layout(FILL, 0, 1F) - .background(Color.YELLOW) + .backgroundColor(Color.YELLOW) } }.layoutFill() // fillViewPort diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseCircleAvatar.kt b/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseCircleAvatar.kt index a2a63976..b471609c 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseCircleAvatar.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseCircleAvatar.kt @@ -9,7 +9,8 @@ import io.noties.adapt.sample.R import io.noties.adapt.sample.annotation.AdaptSample import io.noties.adapt.sample.samples.adaptui.AdaptUISamplePreview import io.noties.adapt.sample.samples.adaptui.AdaptUISampleView -import io.noties.adapt.sample.samples.adaptui.Colors +import io.noties.adapt.sample.ui.color.black +import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewFactory import io.noties.adapt.ui.background diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseImage.kt b/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseImage.kt index 11f18ca0..4a86d1d9 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseImage.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseImage.kt @@ -8,11 +8,13 @@ import io.noties.adapt.sample.R import io.noties.adapt.sample.SampleView import io.noties.adapt.sample.annotation.AdaptSample import io.noties.adapt.sample.samples.adaptui.AdaptUISampleView -import io.noties.adapt.sample.samples.adaptui.Colors +import io.noties.adapt.sample.ui.color.black +import io.noties.adapt.sample.ui.color.orange import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.background import io.noties.adapt.ui.element.Image import io.noties.adapt.ui.element.VStack @@ -42,7 +44,7 @@ class AdaptUIShowcaseImage : AdaptUISampleView() { // by default FIT_CENTER is used as scaleType Image(R.drawable.ic_search_24) .layout(64, 64) - .imageTint(Colors.black) + .imageTint { black } .background(RectangleShape().stroke(Colors.black)) Image(R.drawable.ic_close_24) diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseItem.kt b/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseItem.kt index 37a3ea5e..0598420d 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseItem.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseItem.kt @@ -10,7 +10,7 @@ import io.noties.adapt.listview.AdaptListView import io.noties.adapt.sample.SampleView import io.noties.adapt.sample.annotation.AdaptSample import io.noties.adapt.sample.samples.adaptui.AdaptUISampleView -import io.noties.adapt.sample.samples.adaptui.Colors +import io.noties.adapt.sample.ui.color.black import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.LayoutParams @@ -120,7 +120,7 @@ class AdaptUIShowcaseItem : AdaptUISampleView() { Text() .reference(ref::textView2) .textSize(17) - .textColor(Colors.black) + .textColor { black } .padding(16, 12) .onView { Debug.e(it) diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcasePager.kt b/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcasePager.kt index 55089f69..98f991f1 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcasePager.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcasePager.kt @@ -6,12 +6,16 @@ import androidx.annotation.ColorInt import io.noties.adapt.sample.SampleView import io.noties.adapt.sample.annotation.AdaptSample import io.noties.adapt.sample.samples.adaptui.AdaptUISampleView -import io.noties.adapt.sample.samples.adaptui.Colors +import io.noties.adapt.sample.ui.color.accent +import io.noties.adapt.sample.ui.color.orange +import io.noties.adapt.sample.ui.color.primary +import io.noties.adapt.sample.ui.color.white import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewFactory import io.noties.adapt.ui.alpha +import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.background import io.noties.adapt.ui.element.Pager import io.noties.adapt.ui.element.Text diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseReference.kt b/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseReference.kt index 0665cd5f..1064fc29 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseReference.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseReference.kt @@ -9,6 +9,7 @@ import io.noties.adapt.sample.samples.adaptui.AdaptUISampleView import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewFactory import io.noties.adapt.ui.background +import io.noties.adapt.ui.backgroundColor import io.noties.adapt.ui.element.Text import io.noties.adapt.ui.element.VScrollStack import io.noties.adapt.ui.element.View @@ -45,7 +46,7 @@ class AdaptUIShowcaseReference : AdaptUISampleView() { // NB! element is of View type, not TextView, // so `.text*` functions are not available - ifElement.background(Color.GRAY) + ifElement.backgroundColor(Color.GRAY) // reference a collection of elements (already added to parent) val elements = (0 until 100) diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseShape2.kt b/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseShape2.kt index 07cf2755..684a739a 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseShape2.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseShape2.kt @@ -6,7 +6,7 @@ import android.util.AttributeSet import io.noties.adapt.sample.annotation.AdaptSample import io.noties.adapt.sample.samples.adaptui.AdaptUISamplePreview import io.noties.adapt.sample.samples.adaptui.AdaptUISampleView -import io.noties.adapt.sample.samples.adaptui.Colors +import io.noties.adapt.sample.ui.color.black import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewFactory import io.noties.adapt.ui.background @@ -47,7 +47,7 @@ class AdaptUIShowcaseShape2 : AdaptUISampleView() { Capsule { // configuration can be done here - fill(Colors.black) + fill { black } sizeRelative(1F, 0.1F, Gravity.bottom) padding(16) } diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseShape3.kt b/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseShape3.kt index 3001783e..38122270 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseShape3.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseShape3.kt @@ -7,9 +7,11 @@ import android.util.AttributeSet import io.noties.adapt.sample.annotation.AdaptSample import io.noties.adapt.sample.samples.adaptui.AdaptUISamplePreview import io.noties.adapt.sample.samples.adaptui.AdaptUISampleView -import io.noties.adapt.sample.samples.adaptui.Colors +import io.noties.adapt.sample.ui.color.black +import io.noties.adapt.sample.ui.color.orange import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.background import io.noties.adapt.ui.element.VStack import io.noties.adapt.ui.element.View diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseShapeDrawable.kt b/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseShapeDrawable.kt index 6ed8f276..386a1a7a 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseShapeDrawable.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseShapeDrawable.kt @@ -5,7 +5,8 @@ import android.util.AttributeSet import io.noties.adapt.sample.annotation.AdaptSample import io.noties.adapt.sample.samples.adaptui.AdaptUISamplePreview import io.noties.adapt.sample.samples.adaptui.AdaptUISampleView -import io.noties.adapt.sample.samples.adaptui.Colors +import io.noties.adapt.sample.ui.color.black +import io.noties.adapt.sample.ui.color.orange import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewFactory import io.noties.adapt.ui.background @@ -37,12 +38,12 @@ class AdaptUIShowcaseShapeDrawable : AdaptUISampleView() { // by default circle is using Gravity.center val hotspot = CircleShape() .size(radius * 2, radius * 2, Gravity.leading.top) - .fill(Colors.orange) + .fill { orange } // hidden until pressed event is received .hidden(true) val drawable = ShapeDrawable { Rectangle { - fill(Colors.black) + fill { black } add(hotspot) } }.stateful { diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseStyle.kt b/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseStyle.kt index cf1a6425..b73d07f1 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseStyle.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseStyle.kt @@ -12,6 +12,7 @@ import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewElement import io.noties.adapt.ui.ViewFactory import io.noties.adapt.ui.background +import io.noties.adapt.ui.backgroundColor import io.noties.adapt.ui.element.ElementStyle import io.noties.adapt.ui.element.HStack import io.noties.adapt.ui.element.Text @@ -67,7 +68,7 @@ class AdaptUIShowcaseStyle : AdaptUISampleView() { } private val backgrounded = ElementStyle.generic { - it.background(Color.GRAY) + it.backgroundColor(Color.GRAY) .padding(16) } diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseWeb.kt b/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseWeb.kt index ed0eb864..247436a8 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseWeb.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseWeb.kt @@ -7,7 +7,8 @@ import android.webkit.WebViewClient import io.noties.adapt.sample.SampleView import io.noties.adapt.sample.annotation.AdaptSample import io.noties.adapt.sample.samples.adaptui.AdaptUISampleView -import io.noties.adapt.sample.samples.adaptui.Colors +import io.noties.adapt.sample.ui.color.black +import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.LayoutParams diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/viewpager/ViewPagerSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/viewpager/ViewPagerSample.kt index 9474e1f7..8f52c726 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/viewpager/ViewPagerSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/viewpager/ViewPagerSample.kt @@ -23,6 +23,7 @@ import io.noties.adapt.ui.element.VStack import io.noties.adapt.ui.element.View import io.noties.adapt.ui.element.ViewPagerOnPageChangeListener import io.noties.adapt.ui.element.pagerOnPageChangedListener +import io.noties.adapt.ui.element.textBold import io.noties.adapt.ui.element.textColor import io.noties.adapt.ui.element.textFont import io.noties.adapt.ui.element.textGravity @@ -149,7 +150,7 @@ class ViewPagerSample : SampleView() { Text() .textSize(21) .textColor(Color.BLACK) - .textFont(fontStyle = Typeface.BOLD) + .textBold() .layoutMargin(leading = 8) .reference(ref::textView) .reference(ref::textElement) diff --git a/sample/src/main/java/io/noties/adapt/sample/ui/color/Color.kt b/sample/src/main/java/io/noties/adapt/sample/ui/color/Color.kt new file mode 100644 index 00000000..53e09a05 --- /dev/null +++ b/sample/src/main/java/io/noties/adapt/sample/ui/color/Color.kt @@ -0,0 +1,12 @@ +package io.noties.adapt.sample.ui.color + +import io.noties.adapt.sample.R +import io.noties.adapt.ui.app.color.Colors +import io.noties.adapt.ui.app.color.resColor + +val Colors.white by resColor(R.color.white) +val Colors.black by resColor(R.color.black) +val Colors.orange by resColor(R.color.orange) +val Colors.primary by resColor(R.color.primary) +val Colors.accent by resColor(R.color.accent) +val Colors.yellow by resColor(R.color.yellow) \ No newline at end of file diff --git a/sample/src/main/res/values/dimens.xml b/sample/src/main/res/values/dimens.xml index 4b400271..5509984b 100644 --- a/sample/src/main/res/values/dimens.xml +++ b/sample/src/main/res/values/dimens.xml @@ -8,4 +8,6 @@ 8dip 16dip + 17sp + \ No newline at end of file From c86b3c41d901e2eb6fbab92aa79d343f0f1cc5cd Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Mon, 26 Feb 2024 02:58:30 +0100 Subject: [PATCH 03/61] Small fixes --- adapt-ui/src/main/java/io/noties/adapt/ui/app/color/Colors.kt | 2 ++ sample/build.gradle | 1 + .../noties/adapt/sample/samples/adaptui/AdaptUIClipSample.kt | 3 ++- .../noties/adapt/sample/samples/adaptui/AdaptUIPagerSample.kt | 3 ++- .../noties/adapt/sample/samples/adaptui/AdaptUIShapeSample.kt | 1 + 5 files changed, 8 insertions(+), 2 deletions(-) diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/app/color/Colors.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/app/color/Colors.kt index b0b2dfdd..faed180d 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/app/color/Colors.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/app/color/Colors.kt @@ -38,6 +38,8 @@ object Colors { blue ) + operator fun invoke(@ColorRes resId: Int) = res(resId) + // hm, if we create a-new, activity might be not initialized and thus // we might use context from previous activity, or just application val context: Context get() = (App.topMostActivity ?: App.shared) diff --git a/sample/build.gradle b/sample/build.gradle index 6a363c9f..be27c409 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -7,6 +7,7 @@ final def gitBranch = { }.memoize() android { + namespace 'io.noties.adapt.sample' compileSdkVersion config['target-sdk'] buildToolsVersion config['build-tools'] diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIClipSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIClipSample.kt index b6462cc7..166c081a 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIClipSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIClipSample.kt @@ -18,6 +18,7 @@ import io.noties.adapt.ui.ViewElement import io.noties.adapt.ui.ViewFactory import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.background +import io.noties.adapt.ui.backgroundColor import io.noties.adapt.ui.clipToOutline import io.noties.adapt.ui.element.HStack import io.noties.adapt.ui.element.Image @@ -110,7 +111,7 @@ class AdaptUIClipSample : AdaptUISampleView() { .clipToOutline() } }.padding(8) - .background(Colors.black) + .backgroundColor { black } .layout(FILL, WRAP) } diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIPagerSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIPagerSample.kt index 81f32157..e8bd310f 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIPagerSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIPagerSample.kt @@ -20,6 +20,7 @@ import io.noties.adapt.ui.ViewElement import io.noties.adapt.ui.ViewFactory import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.background +import io.noties.adapt.ui.backgroundColor import io.noties.adapt.ui.element.HStack import io.noties.adapt.ui.element.Image import io.noties.adapt.ui.element.Pager @@ -261,7 +262,7 @@ class AdaptUIPagerSample : SampleView() { .textColor(Color.RED) .textGravity(Gravity.center) .layoutFill() - .background(color) + .backgroundColor(color) }.pagerPageWidthRatio(pageWidthRatio) } diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIShapeSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIShapeSample.kt index bcce82f1..ca63f202 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIShapeSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIShapeSample.kt @@ -538,6 +538,7 @@ class AdaptUIShapeSample : SampleView() { } // in order to invalidate a shape we would need to reference ShapeDrawable + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") lateinit var drawable: ShapeDrawable View() From b2a55e98d0e00ab4954e77a75df67e98eaf9e8da Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Mon, 26 Feb 2024 03:04:38 +0100 Subject: [PATCH 04/61] Colors, change to operator get --- adapt-ui/src/main/java/io/noties/adapt/ui/app/color/Colors.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/app/color/Colors.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/app/color/Colors.kt index faed180d..905deaa4 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/app/color/Colors.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/app/color/Colors.kt @@ -38,7 +38,7 @@ object Colors { blue ) - operator fun invoke(@ColorRes resId: Int) = res(resId) + operator fun get(@ColorRes resId: Int) = res(resId) // hm, if we create a-new, activity might be not initialized and thus // we might use context from previous activity, or just application From c4fbd4a32bbea638e7b0c4354f96574e94630f46 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Tue, 27 Feb 2024 02:35:13 +0100 Subject: [PATCH 05/61] Update provider authority --- adapt-ui/src/main/AndroidManifest.xml | 2 +- .../java/io/noties/adapt/ui/app/style/ViewStyles.kt | 1 - .../java/io/noties/adapt/ui/element/ElementStyle.kt | 7 ++++--- .../main/java/io/noties/adapt/preview/Preview.java | 11 +++++++++++ 4 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 adapt/src/main/java/io/noties/adapt/preview/Preview.java diff --git a/adapt-ui/src/main/AndroidManifest.xml b/adapt-ui/src/main/AndroidManifest.xml index f8bf69b8..5f18fbac 100644 --- a/adapt-ui/src/main/AndroidManifest.xml +++ b/adapt-ui/src/main/AndroidManifest.xml @@ -3,7 +3,7 @@ \ No newline at end of file diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/app/style/ViewStyles.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/app/style/ViewStyles.kt index f6d5ec40..ba3870b0 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/app/style/ViewStyles.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/app/style/ViewStyles.kt @@ -10,7 +10,6 @@ import kotlin.reflect.KProperty object ViewStyles - fun style( block: ViewFactoryConstants.(ViewElement) -> Unit ) = ViewStylesProperty(block) diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/element/ElementStyle.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/element/ElementStyle.kt index 42d04a12..bb8b4e59 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/element/ElementStyle.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/element/ElementStyle.kt @@ -4,6 +4,7 @@ import android.view.View import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewElement import io.noties.adapt.ui.ViewFactoryConstants +import io.noties.adapt.ui.app.style.ViewStyles fun ViewElement.style( style: ElementStyle @@ -11,9 +12,9 @@ fun ViewElement.style( style.block.invoke(ViewFactoryConstants.Impl, it) } -fun ViewElement.style( - block: ElementStyle.Companion.() -> ElementStyle -) = style(block(ElementStyle)) +fun ViewElement.style( + builder: ViewStyles.() -> ElementStyle +) = style(builder(ViewStyles)) class ElementStyle private constructor( val block: ViewFactoryConstants.(ViewElement<@UnsafeVariance V, @UnsafeVariance LP>) -> Unit diff --git a/adapt/src/main/java/io/noties/adapt/preview/Preview.java b/adapt/src/main/java/io/noties/adapt/preview/Preview.java new file mode 100644 index 00000000..c3eb5b59 --- /dev/null +++ b/adapt/src/main/java/io/noties/adapt/preview/Preview.java @@ -0,0 +1,11 @@ +package io.noties.adapt.preview; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Preview { +} From e76701780fe4b9b98cd136acdc2db9a7d57d9bf7 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Fri, 15 Mar 2024 19:07:54 +0100 Subject: [PATCH 06/61] Working with explore modal layout --- .../noties/adapt/ui/ViewElement+Extensions.kt | 13 + .../main/java/io/noties/adapt/ui/app/App.kt | 3 + .../io/noties/adapt/ui/util/ResourcesUtil.kt | 4 +- .../adapt/viewpager/AdaptViewPager.java | 6 + sample/src/main/AndroidManifest.xml | 4 + ...wBounds.kt => ExploreOverlayDrawBounds.kt} | 11 +- .../sample/explore/ExploreScrollIfScrolls.kt | 451 ++++++++++++++++++ .../adapt/sample/explore/PreviewInActivity.kt | 45 ++ .../samples/adaptui/AdaptUITextSample.kt | 4 +- sample/src/main/res/values/ids.xml | 2 + 10 files changed, 533 insertions(+), 10 deletions(-) rename sample/src/main/java/io/noties/adapt/sample/explore/{ExplorePreviewDrawBounds.kt => ExploreOverlayDrawBounds.kt} (78%) create mode 100644 sample/src/main/java/io/noties/adapt/sample/explore/ExploreScrollIfScrolls.kt create mode 100644 sample/src/main/java/io/noties/adapt/sample/explore/PreviewInActivity.kt diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/ViewElement+Extensions.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/ViewElement+Extensions.kt index 9e57d4ab..d748d261 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/ViewElement+Extensions.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/ViewElement+Extensions.kt @@ -455,6 +455,15 @@ fun ViewElement.scrollBarStyle( it.scrollBarStyle = scrollBarStyle } +/** + * Scroll bars + * @see View.setHorizontalScrollBarEnabled + * @see View.setVerticalScrollBarEnabled + */ +fun ViewElement.scrollBarsEnabled( + value: Boolean +) = scrollBarsEnabled(horizontal = value, vertical = value) + /** * Scroll bars * @see View.setHorizontalScrollBarEnabled @@ -734,3 +743,7 @@ fun ViewElement.pivotRelative( value: Float ) = pivotRelative(value, value) +fun ViewElement.requestFocus() = onView { + it.requestFocus() +} + diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/app/App.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/app/App.kt index a04ab391..d0f55295 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/app/App.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/app/App.kt @@ -3,6 +3,7 @@ package io.noties.adapt.ui.app import android.annotation.SuppressLint import android.app.Activity import android.app.Application +import android.content.Context object App { var shared: Application @@ -16,6 +17,8 @@ object App { val topMostActivity: Activity? get() = topMostActivityListener.topMostActivity + val context: Context get() = topMostActivity ?: shared + private var _shared: Application? = null @SuppressLint("StaticFieldLeak") diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/util/ResourcesUtil.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/util/ResourcesUtil.kt index cc101c44..4fe99e1a 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/util/ResourcesUtil.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/util/ResourcesUtil.kt @@ -54,7 +54,7 @@ fun Resources.createAttributeSet( null } -internal fun resolveDrawableAttr(context: Context, @AttrRes attr: Int): Drawable? { +fun resolveDrawableAttr(context: Context, @AttrRes attr: Int): Drawable? { val array = context.obtainStyledAttributes(intArrayOf(attr)) try { return array.getDrawable(0) @@ -68,7 +68,7 @@ internal fun resolveDrawableAttr(context: Context, @AttrRes attr: Int): Drawable return null } -internal fun resolveDefaultSelectableDrawable(context: Context): Drawable? = resolveDrawableAttr( +fun resolveDefaultSelectableDrawable(context: Context): Drawable? = resolveDrawableAttr( context, android.R.attr.selectableItemBackground ) \ No newline at end of file diff --git a/adapt/src/main/java/io/noties/adapt/viewpager/AdaptViewPager.java b/adapt/src/main/java/io/noties/adapt/viewpager/AdaptViewPager.java index eab73e1c..5a09dceb 100644 --- a/adapt/src/main/java/io/noties/adapt/viewpager/AdaptViewPager.java +++ b/adapt/src/main/java/io/noties/adapt/viewpager/AdaptViewPager.java @@ -122,6 +122,12 @@ public void notifyItemChanged(@NonNull Item item) { @Nullable public View findViewForAdapterPosition(int position) { + // it is possible that this method might be called when there is no data yet + // with `0` item as position + if (position < 0 || position >= items.size()) { + return null; + } + //noinspection rawtypes final Item item = items.get(position); diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 2a1b8fca..136daee9 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -25,6 +25,10 @@ + + diff --git a/sample/src/main/java/io/noties/adapt/sample/explore/ExplorePreviewDrawBounds.kt b/sample/src/main/java/io/noties/adapt/sample/explore/ExploreOverlayDrawBounds.kt similarity index 78% rename from sample/src/main/java/io/noties/adapt/sample/explore/ExplorePreviewDrawBounds.kt rename to sample/src/main/java/io/noties/adapt/sample/explore/ExploreOverlayDrawBounds.kt index b9f1dd9a..a764aaf7 100644 --- a/sample/src/main/java/io/noties/adapt/sample/explore/ExplorePreviewDrawBounds.kt +++ b/sample/src/main/java/io/noties/adapt/sample/explore/ExploreOverlayDrawBounds.kt @@ -11,12 +11,14 @@ import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.shape.Label import io.noties.adapt.ui.shape.RectangleShape -object ExplorePreviewDrawBounds { - // TODO: this can be an extension for all views - fun ViewElement.previewDrawBounds() = onView { view -> +// Renamed, as `preview` might be considered to be used only in actual layout-preview +// `overlayDrawBounds` (which could be used with `ifPreview { it.overlayDrawBounds() }` +object ExploreOverlayDrawBounds { + fun ViewElement.overlayDrawBounds() = onView { view -> fun process(view: View) { PreviewBoundsDrawable(view, Colors.black) if (view is ViewGroup) { + // can we also check the level? so, we could nest wrap children view.children.forEach { process(it) } } } @@ -24,15 +26,12 @@ object ExplorePreviewDrawBounds { } private class PreviewBoundsDrawable( -// val root: ViewGroup, val view: View, @ColorInt val color: Int ) { private val drawable = RectangleShape { -// padding(1) stroke(color, 1) -// Rectangle { stroke(color, 1) } Label(view::class.java.simpleName) .textSize(11) .textColor(color) diff --git a/sample/src/main/java/io/noties/adapt/sample/explore/ExploreScrollIfScrolls.kt b/sample/src/main/java/io/noties/adapt/sample/explore/ExploreScrollIfScrolls.kt new file mode 100644 index 00000000..f89c834b --- /dev/null +++ b/sample/src/main/java/io/noties/adapt/sample/explore/ExploreScrollIfScrolls.kt @@ -0,0 +1,451 @@ +package io.noties.adapt.sample.explore + +import android.annotation.SuppressLint +import android.content.Context +import android.transition.TransitionManager +import android.util.AttributeSet +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.LinearLayout +import android.widget.ScrollView +import android.widget.TextView +import io.noties.adapt.sample.R +import io.noties.adapt.sample.explore.ExploreOverlayDrawBounds.overlayDrawBounds +import io.noties.adapt.sample.explore.ExploreScrollIfScrolls.VStackContentMeasuredLast +import io.noties.adapt.sample.explore.ExploreScrollIfScrolls.WrapContentOrScroll +import io.noties.adapt.sample.explore.ExploreScrollIfScrolls.stackContentMeasureLast +import io.noties.adapt.sample.ui.color.accent +import io.noties.adapt.sample.util.children +import io.noties.adapt.ui.LayoutParams +import io.noties.adapt.ui.ViewElement +import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.background +import io.noties.adapt.ui.backgroundColor +import io.noties.adapt.ui.clipToOutline +import io.noties.adapt.ui.element.Element +import io.noties.adapt.ui.element.ElementGroup +import io.noties.adapt.ui.element.HStack +import io.noties.adapt.ui.element.Image +import io.noties.adapt.ui.element.Text +import io.noties.adapt.ui.element.VScroll +import io.noties.adapt.ui.element.VStack +import io.noties.adapt.ui.element.ZStack +import io.noties.adapt.ui.element.textGravity +import io.noties.adapt.ui.element.textSize +import io.noties.adapt.ui.foregroundDefaultSelectable +import io.noties.adapt.ui.indent +import io.noties.adapt.ui.layout +import io.noties.adapt.ui.layoutFill +import io.noties.adapt.ui.layoutGravity +import io.noties.adapt.ui.layoutMargin +import io.noties.adapt.ui.onClick +import io.noties.adapt.ui.padding +import io.noties.adapt.ui.preview.AdaptUIPreviewLayout +import io.noties.adapt.ui.reference +import io.noties.adapt.ui.scrollBarsEnabled +import io.noties.adapt.ui.shape.RoundedRectangle +import io.noties.adapt.ui.transitionGroup +import io.noties.adapt.ui.util.withAlphaComponent +import io.noties.debug.Debug +import kotlin.math.roundToInt + +// actually, what we really want is to have a wrap-content view, +// but which might scroll if content exceeds available height. +// The question now is: how to determine available height? +// 1. what if we additionally measure parent to understand its maximum height? then how to fallback? +object ExploreScrollIfScrolls { + + @Suppress("FunctionName") + fun ViewFactory.WrapContentOrScroll( + scrollStyle: (ViewElement) -> Unit = {}, + child: ViewFactory.() -> ViewElement + ) = Element( + provider = { WrapHeightOrScroll(it, scrollStyle, child) } + ) + + @SuppressLint("ViewConstructor") + class WrapHeightOrScroll( + context: Context, + private val scrollStyle: (ViewElement) -> Unit, + child: ViewFactory.() -> ViewElement + ) : FrameLayout(context) { + + private val content: View + + private var callbacks: Runnable? = null + + init { + content = ViewFactory( + context, + this + ).let { child.invoke(it) } + .also { it.init(context) } + .also { + if (it.view.layoutParams == null) { + it.view.layoutParams = + LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) + } + } + .also { it.render() } + .also { + addView(it.view) + } + .view + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + val width = MeasureSpec.getSize(widthMeasureSpec) + val height = MeasureSpec.getSize(heightMeasureSpec) + Debug.i("height:$height spec:${MeasureSpec.toString(heightMeasureSpec)}") + + val child = content + + // unspecified and without padding (yet)! + val childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) + child.measure( + MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + childHeightSpec + ) + + val childMeasuredHeight = child.measuredHeight + + // TODO: do we need to adjust for padding in order to properly layout child? + val isOverflow = childMeasuredHeight > (height - paddingTop - paddingBottom) + Debug.i("child.measured:${childMeasuredHeight} isOverflow:$isOverflow") + + if (child.parent != this) { + // additionally measure wrapper content + getChildAt(0).measure( + MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY) + ) + } + + if (!isOverflow) { + setMeasuredDimension( + width, + childMeasuredHeight + paddingTop + paddingBottom + ) + + unwrapContentFromScrollView() + + } else { + setMeasuredDimension( + width, + height + ) + + wrapContentInScrollView(height) + } + } + + private fun wrapContentInScrollView(height: Int) { + removeWrapUnwrapCallbacks() + + Debug.i("parent.willWrap:${content.parent == this}") + + // cannot do anything until parent is present + val parent = content.parent as? ViewGroup ?: return + if (parent != this) { + // assume already wrapped + return + } + + postWrapUnwrapCallbacks { + // remove from this layout + removeAllViews() + + val view = ViewFactory.newView(this) + .layoutParams(LayoutParams(LayoutParams.MATCH_PARENT, height)) + .create { + VScroll { + VStack { + Element { content } + } + }.indent() + .also(scrollStyle) + } + + Debug.i("view:$view") + + addView(view) + } + } + + private fun unwrapContentFromScrollView() { + removeWrapUnwrapCallbacks() + + Debug.i("parent.willUnwrap:${content.parent != this}") + + val parent = content.parent as? ViewGroup ?: return + if (parent == this) { + // assume already unwrapped + return + } + + postWrapUnwrapCallbacks { + parent.removeAllViews() + + removeAllViews() + + addView(content) + } + } + + private fun removeWrapUnwrapCallbacks() { + callbacks?.also { removeCallbacks(it) } + } + + private fun postWrapUnwrapCallbacks(callbacks: Runnable) { + this.callbacks = callbacks + post(callbacks) + } + + override fun canScrollVertically(direction: Int): Boolean { + // if contains scroll-view -> pass to it, else false + val scrollView = getChildAt(0) as? ScrollView ?: return false + return scrollView.canScrollVertically(direction) + } + } + + @Suppress("FunctionName") + fun ViewFactory.VStackContentMeasuredLast( + children: ViewFactory.() -> Unit + ) = ElementGroup( + provider = { ContentHeightIsMeasuredLast(it) }, + children = children + ) + + fun ViewElement.stackContentMeasureLast() = + onLayoutParams { + it.isContent = true + } + + // no padding? + class ContentHeightIsMeasuredLast(context: Context) : LinearLayout(context) { + + init { + orientation = VERTICAL + } + + class LayoutParams : LinearLayout.LayoutParams { + var isContent: Boolean = false + + constructor(c: Context, attrs: AttributeSet?) : super(c, attrs) + constructor(width: Int, height: Int) : super(width, height) + constructor(width: Int, height: Int, weight: Float) : super(width, height, weight) + constructor(p: ViewGroup.LayoutParams) : super(p) + constructor(source: MarginLayoutParams) : super(source) + constructor(source: LinearLayout.LayoutParams) : super(source) + } + + private companion object { + val View.isContentView: Boolean get() { + return true == (this.layoutParams as? LayoutParams)?.isContent + } + } + + override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams { + return LayoutParams(context, attrs) + } + + override fun generateDefaultLayoutParams(): LayoutParams { + return LayoutParams( + io.noties.adapt.ui.LayoutParams.MATCH_PARENT, + io.noties.adapt.ui.LayoutParams.WRAP_CONTENT + ) + } + + override fun generateLayoutParams(lp: ViewGroup.LayoutParams): LayoutParams { + return LayoutParams(lp) + } + + override fun checkLayoutParams(p: ViewGroup.LayoutParams): Boolean { + return p is LayoutParams + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + // if content is not found -> just super + + val (contentChildren, children) = children + .partition { it.isContentView } + + if (contentChildren.isEmpty()) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + } + +// val content = +// children.firstOrNull { true == (it.layoutParams as? LayoutParams)?.isContent } +// if (content == null) { +// super.onMeasure(widthMeasureSpec, heightMeasureSpec) +// return +// } + + val width = MeasureSpec.getSize(widthMeasureSpec) + val height = MeasureSpec.getSize(heightMeasureSpec) + + var availableHeight = height - paddingTop - paddingBottom + + fun measureChild(child: View) { + measureChild( + child, + childWidthMeasureSpec(width, child), + childHeightMeasureSpec( + availableHeight - child.heightLayoutMargins() + ) + ) + availableHeight -= (child.measuredHeight + child.heightLayoutMargins()) + } + + children.forEach { measureChild(it) } + contentChildren.forEach { measureChild(it) } + +// children +// .withIndex() +// .sortedWith(ChildrenContentComparator) +// .map { getChildAt(it.index) } +// .forEach { child -> +// measureChild( +// child, +// childWidthMeasureSpec(width, child), +// childHeightMeasureSpec( +// availableHeight - child.heightLayoutMargins() +// ) +// ) +// availableHeight -= (child.measuredHeight + child.heightLayoutMargins()) +// } + +// for (child in children) { +// if (true == (child.layoutParams as? LayoutParams)?.isContent) { +// continue +// } +// measureChild( +// child, +// childWidthMeasureSpec(width, child), +// childHeightMeasureSpec( +// availableHeight - child.heightLayoutMargins() +// ) +//// MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.AT_MOST) +// ) +//// child.measure( +//// MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), +//// MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.AT_MOST) +//// ) +// +// availableHeight -= (child.measuredHeight + child.heightLayoutMargins()) +// } + + // maybe just sort them and process in a single loop? +// content.measure( +// childWidthMeasureSpec(width, content), +// childHeightMeasureSpec( +// availableHeight - content.heightLayoutMargins() +// ) +// ) +// availableHeight -= (content.measuredHeight + content.heightLayoutMargins()) + + // okay here: + // do we just use maximum dimensions + // or do we check if wrap-content is requested and then use the height used by the children? + + setMeasuredDimension( + width, + height - availableHeight + ) + } + + private fun childWidthMeasureSpec(parentWidth: Int, child: View): Int { + return MeasureSpec.makeMeasureSpec( + (parentWidth - paddingLeft - paddingRight - child.widthLayoutMargins()), + MeasureSpec.EXACTLY + ) + } + + private fun childHeightMeasureSpec(height: Int): Int { + return MeasureSpec.makeMeasureSpec( + height, + MeasureSpec.EXACTLY + ) + } + + private fun View.widthLayoutMargins(): Int { + val lp = this.layoutParams as? LayoutParams + return if (lp != null) { + lp.leftMargin + lp.rightMargin + } else 0 + } + + private fun View.heightLayoutMargins(): Int { + val lp = this.layoutParams as? LayoutParams + return if (lp != null) { + lp.topMargin + lp.bottomMargin + } else 0 + } + } +} + +private class PreviewExploreScrollIfScrolls(context: Context, attrs: AttributeSet?) : + AdaptUIPreviewLayout(context, attrs) { + + lateinit var text: TextView + + override fun ViewFactory.body() { + ZStack { + VStackContentMeasuredLast { + + HStack { + Image(R.drawable.ic_arrow_back_ios_24_white) + .layout(56, 56) + }.layout(FILL, 56) + .backgroundColor { accent } + + WrapContentOrScroll( + scrollStyle = { + it.scrollBarsEnabled(false) + } + ) { + Text("Content, click me to generate new") + .layout(FILL, WRAP) + // this one is going to be erased after wrap/unwrap + .layoutGravity { center.vertical } + .textSize { 17 } + .padding(top = 24, bottom = 36) + .padding(horizontal = 16) + .backgroundColor { accent.withAlphaComponent(0.2F) } + .reference(::text) + }.stackContentMeasureLast() + + Text("Click me!") + .layout(FILL, WRAP) + .textSize(17) + .textGravity { center.horizontal } + .padding(12) + .background { + RoundedRectangle(12) { + fill { accent } + } + } + .foregroundDefaultSelectable() + .clipToOutline() + .layoutMargin(16) + .onClick { + // from 0 to 100 + val count = (20 * Math.random()).roundToInt() + val base = + "This is the text to replicate, let it take some place, some amount of space we need to cover." + val result = (0 until count) + .joinToString("\n\n") { "[$it/$count] $base" } + TransitionManager.beginDelayedTransition(text.parent.parent as ViewGroup) + text.text = result + } + }.indent() + .layoutGravity { center.bottom } + .backgroundColor { accent.withAlphaComponent(0.3F) } + .layoutMargin(top = 128) + .layoutMargin(horizontal = 16) + .transitionGroup() + }.layoutFill() + .overlayDrawBounds() + } +} \ No newline at end of file diff --git a/sample/src/main/java/io/noties/adapt/sample/explore/PreviewInActivity.kt b/sample/src/main/java/io/noties/adapt/sample/explore/PreviewInActivity.kt new file mode 100644 index 00000000..fb886575 --- /dev/null +++ b/sample/src/main/java/io/noties/adapt/sample/explore/PreviewInActivity.kt @@ -0,0 +1,45 @@ +package io.noties.adapt.sample.explore + +import android.app.Activity +import android.content.Context +import android.os.Bundle +import android.util.AttributeSet +import android.view.View +import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.element.Element +import io.noties.adapt.ui.element.ZStack +import io.noties.adapt.ui.layoutFill + +class PreviewInActivity: Activity() { + + private companion object { + const val ARG_PREVIEW_CLASS = "pc" + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val type = try { + intent.getStringExtra(ARG_PREVIEW_CLASS) + .also { println("ARG:'$it'") } + ?.let { Class.forName(it) } + ?.let { it.getConstructor(Context::class.java, AttributeSet::class.java) } + ?.let { + it.newInstance(this, null) as View + } + } catch (t: Throwable) { + t.printStackTrace(System.err) + null + } + + println("type:'$type'") + + if (type != null) { + setContentView(ViewFactory.createView(this) { + ZStack { + Element { type } + }.layoutFill() + }) + } + } +} \ No newline at end of file diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUITextSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUITextSample.kt index cbb950fe..ab8da5b2 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUITextSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUITextSample.kt @@ -13,7 +13,7 @@ import io.noties.adapt.sample.explore.ExploreAutofill import io.noties.adapt.sample.explore.ExploreAutofill.autofillEnabled import io.noties.adapt.sample.explore.ExploreAutofill.autofillHint import io.noties.adapt.sample.explore.ExploreAutofill.autofillRequestOnFocusWhenEmpty -import io.noties.adapt.sample.explore.ExplorePreviewDrawBounds.previewDrawBounds +import io.noties.adapt.sample.explore.ExploreOverlayDrawBounds.overlayDrawBounds import io.noties.adapt.sample.ui.color.black import io.noties.adapt.sample.ui.color.orange import io.noties.adapt.sample.ui.color.primary @@ -78,7 +78,7 @@ class AdaptUITextSample : SampleView() { MyText() } }.layoutFill() - .previewDrawBounds() + .overlayDrawBounds() } (view as ViewGroup).addView(child) } diff --git a/sample/src/main/res/values/ids.xml b/sample/src/main/res/values/ids.xml index 688d9265..85a7bbf1 100644 --- a/sample/src/main/res/values/ids.xml +++ b/sample/src/main/res/values/ids.xml @@ -7,4 +7,6 @@ + + \ No newline at end of file From d52b7636dbaf5b58b0757fe21575be409ce116f6 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Sun, 31 Mar 2024 13:17:46 +0200 Subject: [PATCH 07/61] Fix compilation and tests --- .../java/io/noties/adapt/ui/ViewFactory.kt | 8 +- .../adapt/ui/element/PagerWrapContent.kt | 17 + .../java/io/noties/adapt/ui/element/Text.kt | 41 +- .../ui/element/VStackContentMeasuredLast.kt | 26 + .../ui/element/VStackReverseDrawingOrder.kt | 24 + .../java/io/noties/adapt/ui/element/Web.kt | 68 +++ .../noties/adapt/ui/element/ZStackSquare.kt | 17 + .../ui/element/ZStackWrapHeightOrScroll.kt | 20 + .../io/noties/adapt/ui/util/ActivityUtil.kt | 35 ++ .../io/noties/adapt/ui/util/ImeOptions.kt | 200 ++++++-- .../java/io/noties/adapt/ui/util/InputType.kt | 6 +- .../adapt/ui/util/OnScrollChangedListener.kt | 63 +-- .../java/io/noties/adapt/ui/util/ViewUtil.kt | 24 +- .../ui/widget/AdaptViewPagerWrapContent.kt | 18 +- .../widget/FrameLayoutWrapHeightOrScroll.kt | 155 ++++++ .../io/noties/adapt/ui/widget/LazyView.kt | 2 + .../widget/LinearLayoutContentMeasuredLast.kt | 143 ++++++ .../widget/LinearLayoutReverseDrawingOrder.kt | 27 +- .../adapt/ui/widget/SquareFrameLayout.kt | 12 +- .../widget/{Web.kt => WebViewPlaceholder.kt} | 59 --- .../io/noties/adapt/ui/element/Text_Test.kt | 96 ++-- .../noties/adapt/ui/util/ImeOptions_Test.kt | 108 +++-- .../ui/widget/WebViewPlaceholder_Test.kt | 5 + sample/build.gradle | 3 + sample/samples.json | 38 ++ sample/src/main/AndroidManifest.xml | 6 +- .../main/java/io/noties/adapt/sample/App.kt | 3 + .../sample/explore/ExploreModalLayout.kt | 364 ++++++++++++++ .../sample/explore/ExploreScrollIfScrolls.kt | 451 ------------------ .../adaptui/ActivitySetContentUISample.kt | 61 +++ .../ActivitySetContentUISampleActivity.kt | 37 ++ .../samples/adaptui/AdaptUIGradientSample.kt | 2 +- .../adaptui/AdaptUIScrollIfScrollsSample.kt | 119 +++++ .../samples/adaptui/AdaptUIStickySample.kt | 2 +- .../adaptui/AdaptUITextInputTypeSample.kt | 48 ++ .../samples/adaptui/AdaptUITextSample.kt | 30 +- .../samples/adaptui/AdaptUIWebSample.kt | 8 +- .../AdaptUiTextInputImeOptionsSample.kt | 79 +++ .../flex/AdaptUIFlexInteractiveSample.kt | 1 - .../samples/showcase/AdaptUIShowcaseWeb.kt | 14 +- .../samples/viewpager/ViewPagerSample.kt | 4 +- 41 files changed, 1653 insertions(+), 791 deletions(-) create mode 100644 adapt-ui/src/main/java/io/noties/adapt/ui/element/PagerWrapContent.kt create mode 100644 adapt-ui/src/main/java/io/noties/adapt/ui/element/VStackContentMeasuredLast.kt create mode 100644 adapt-ui/src/main/java/io/noties/adapt/ui/element/VStackReverseDrawingOrder.kt create mode 100644 adapt-ui/src/main/java/io/noties/adapt/ui/element/Web.kt create mode 100644 adapt-ui/src/main/java/io/noties/adapt/ui/element/ZStackSquare.kt create mode 100644 adapt-ui/src/main/java/io/noties/adapt/ui/element/ZStackWrapHeightOrScroll.kt create mode 100644 adapt-ui/src/main/java/io/noties/adapt/ui/util/ActivityUtil.kt create mode 100644 adapt-ui/src/main/java/io/noties/adapt/ui/widget/FrameLayoutWrapHeightOrScroll.kt create mode 100644 adapt-ui/src/main/java/io/noties/adapt/ui/widget/LinearLayoutContentMeasuredLast.kt rename adapt-ui/src/main/java/io/noties/adapt/ui/widget/{Web.kt => WebViewPlaceholder.kt} (73%) create mode 100644 sample/src/main/java/io/noties/adapt/sample/explore/ExploreModalLayout.kt delete mode 100644 sample/src/main/java/io/noties/adapt/sample/explore/ExploreScrollIfScrolls.kt create mode 100644 sample/src/main/java/io/noties/adapt/sample/samples/adaptui/ActivitySetContentUISample.kt create mode 100644 sample/src/main/java/io/noties/adapt/sample/samples/adaptui/ActivitySetContentUISampleActivity.kt create mode 100644 sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIScrollIfScrollsSample.kt create mode 100644 sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUITextInputTypeSample.kt create mode 100644 sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUiTextInputImeOptionsSample.kt diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/ViewFactory.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/ViewFactory.kt index f7eab447..ebfb7ef7 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/ViewFactory.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/ViewFactory.kt @@ -51,7 +51,7 @@ class ViewFactory( fun createView( context: Context, - children: ViewFactory.(Unit) -> Unit + children: ViewFactory.() -> Unit ): View = newView(context).create(children) fun createView( @@ -131,8 +131,10 @@ class ViewFactory( ) fun create( - children: ViewFactory.(Unit) -> Unit - ): View = create(Unit, children) + children: ViewFactory.() -> Unit + ): View = create(Unit) { + children() + } fun create( ref: R, diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/element/PagerWrapContent.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/element/PagerWrapContent.kt new file mode 100644 index 00000000..1740b3d0 --- /dev/null +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/element/PagerWrapContent.kt @@ -0,0 +1,17 @@ +package io.noties.adapt.ui.element + +import io.noties.adapt.ui.LayoutParams +import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.widget.AdaptViewPagerWrapContent + +/** + * NB! in order to function ViewPager must be initialized with [io.noties.adapt.viewpager.AdaptViewPager.init] + * NB! File name differs from element\'s name to bring attention to the requirement + * to have a [ViewPager] initialized with [Adapt] instance + * + * @since $UNRELEASED; + * @see AdaptViewPagerWrapContent + */ +@Suppress("FunctionName") +fun ViewFactory.AdaptPagerWrapContent( +) = Element { AdaptViewPagerWrapContent(it) } \ No newline at end of file diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/element/Text.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/element/Text.kt index 76aee2a0..d212061c 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/element/Text.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/element/Text.kt @@ -10,7 +10,6 @@ import android.text.TextUtils import android.text.TextWatcher import android.util.TypedValue import android.view.View -import android.view.inputmethod.EditorInfo import android.widget.TextView import androidx.annotation.ColorInt import androidx.annotation.RequiresApi @@ -27,7 +26,9 @@ import io.noties.adapt.ui.gradient.Gradient import io.noties.adapt.ui.util.Gravity import io.noties.adapt.ui.util.GravityBuilder import io.noties.adapt.ui.util.ImeOptions +import io.noties.adapt.ui.util.ImeOptionsBuilder import io.noties.adapt.ui.util.InputType +import io.noties.adapt.ui.util.InputTypeBuilder import io.noties.adapt.ui.util.TextWatcherHideIfEmpty import io.noties.adapt.ui.util.TypefaceStyle import io.noties.adapt.ui.util.dip @@ -168,8 +169,9 @@ inline fun ViewElement.textGravity( * @see TextView.setTypeface * @see textTypeface */ +// cannot replaceWith with different parameters (int vs TypefaceStyle) @Suppress("DeprecatedCallableAddReplaceWith") -@Deprecated("Use `textTypeface`") +@Deprecated(message = "Use `textTypeface`") fun ViewElement.textFont( font: Typeface? = null, fontStyle: Int = Typeface.NORMAL @@ -481,28 +483,31 @@ fun ViewElement.textInputType( ) = onView { it.inputType = inputType.value } /** - * Ime Options + * InputType + * @see TextView.setInputType + */ +fun ViewElement.textInputType( + builder: InputTypeBuilder +) = onView { it.inputType = builder(InputType).value } + +/** + * Builder requires that action is specified last (terminating) + * + * ```kotlin + * Text("Hello") + * .textImeOptions { noExtractUi.noFullScreen.actionDone { /*done callback*/ } } + * ``` * @see TextView.setImeOptions * @see TextView.setOnEditorActionListener */ fun ViewElement.textImeOptions( - imeOptions: ImeOptions, - onActionListener: ((V, action: ImeOptions) -> Boolean)? = null + builder: ImeOptionsBuilder ) = onView { - it.imeOptions = imeOptions.value - - onActionListener?.also { listener -> - val action = imeOptions.value and EditorInfo.IME_MASK_ACTION - it.setOnEditorActionListener { _, actionId, _ -> - if (action != 0 && actionId != action) { - false - } else { - listener(it, ImeOptions(actionId)) - } - } - } + val (rawValue, editorAction) = builder(ImeOptions) + it.imeOptions = rawValue + it.setOnEditorActionListener(editorAction) } -inline fun ViewElement.textStyle( +fun ViewElement.textStyle( builder: TextStyles.() -> ElementStyle ) = style(builder(TextStyles)) \ No newline at end of file diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/element/VStackContentMeasuredLast.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/element/VStackContentMeasuredLast.kt new file mode 100644 index 00000000..922d4f41 --- /dev/null +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/element/VStackContentMeasuredLast.kt @@ -0,0 +1,26 @@ +package io.noties.adapt.ui.element + +import android.view.View +import io.noties.adapt.ui.LayoutParams +import io.noties.adapt.ui.ViewElement +import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.widget.LinearLayoutContentMeasuredLast + +/** + * @see LinearLayoutContentMeasuredLast + * @see stackContentMeasureLast + */ +@Suppress("FunctionName") +fun ViewFactory.VStackContentMeasuredLast( + children: ViewFactory.() -> Unit +) = ElementGroup( + provider = { LinearLayoutContentMeasuredLast(it) }, + children = children +) + + +@Suppress("FINAL_UPPER_BOUND") +fun ViewElement.stackContentMeasureLast() = + onLayoutParams { + it.isContent = true + } \ No newline at end of file diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/element/VStackReverseDrawingOrder.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/element/VStackReverseDrawingOrder.kt new file mode 100644 index 00000000..412b3b6f --- /dev/null +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/element/VStackReverseDrawingOrder.kt @@ -0,0 +1,24 @@ +package io.noties.adapt.ui.element + +import android.widget.LinearLayout +import io.noties.adapt.ui.LayoutParams +import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.util.Gravity +import io.noties.adapt.ui.widget.LinearLayoutReverseDrawingOrder + +/** + * @see LinearLayoutReverseDrawingOrder + */ +@Suppress("FunctionName") +fun ViewFactory.VStackReverseDrawingOrder( + gravity: Gravity = VStackDefaultGravity, + children: ViewFactory.() -> Unit +) = ElementGroup( + { + LinearLayoutReverseDrawingOrder(it).also { ll -> + ll.orientation = LinearLayout.VERTICAL + ll.gravity = gravity.value + } + }, + children +) \ No newline at end of file diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/element/Web.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/element/Web.kt new file mode 100644 index 00000000..5a7865c7 --- /dev/null +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/element/Web.kt @@ -0,0 +1,68 @@ +package io.noties.adapt.ui.element + +import android.content.Context +import android.webkit.WebChromeClient +import android.webkit.WebSettings +import android.webkit.WebView +import android.webkit.WebViewClient +import android.widget.FrameLayout +import io.noties.adapt.ui.LayoutParams +import io.noties.adapt.ui.ViewElement +import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.widget.WebViewPlaceholder + +/** + * @see WebViewPlaceholder + */ +@Suppress("FunctionName") +fun ViewFactory.Web( + url: String? = null, + webViewFactory: ((Context) -> WebView)? = null, + onFailedWebViewInflation: ((WebViewPlaceholder) -> Unit)? = null, + // can be used to show content whilst webView inflation is in progress + placeholderContent: (ViewFactory.() -> Unit)? = null +) = Element { + WebViewPlaceholder(it).also { wvp -> + webViewFactory?.also { factory -> wvp.webViewFactory = factory } + wvp.onFailedWebViewInflation = onFailedWebViewInflation + wvp.placeholderContent = placeholderContent + if (url != null) { + wvp.onWebViewReady { wv -> wv.loadUrl(url) } + } + } +} + +// exposed for customizations not in layout +fun ViewElement.webOnElementReady( + block: (ViewElement) -> Unit +) = onView { + it.onWebViewReady { wv -> + val element = ViewElement { wv } + element.init(wv.context) + element.render(block) + } +} + +fun ViewElement.webLoad( + url: String +) = onView { + it.onWebViewReady { wv -> wv.loadUrl(url) } +} + +fun ViewElement.webSettings( + settings: (WebSettings) -> Unit +) = onView { + it.onWebViewReady { wv -> settings(wv.settings) } +} + +fun ViewElement.webClient( + client: WebViewClient +) = onView { + it.onWebViewReady { wv -> wv.webViewClient = client } +} + +fun ViewElement.webChromeClient( + client: WebChromeClient +) = onView { + it.onWebViewReady { wv -> wv.webChromeClient = client } +} \ No newline at end of file diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/element/ZStackSquare.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/element/ZStackSquare.kt new file mode 100644 index 00000000..4f832396 --- /dev/null +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/element/ZStackSquare.kt @@ -0,0 +1,17 @@ +package io.noties.adapt.ui.element + +import android.widget.FrameLayout +import io.noties.adapt.ui.LayoutParams +import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.widget.SquareFrameLayout + +/** + * @see SquareFrameLayout + */ +@Suppress("FunctionName") +fun ViewFactory.ZStackSquare( + children: ViewFactory.() -> Unit +) = ElementGroup( + { SquareFrameLayout(it) }, + children +) \ No newline at end of file diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/element/ZStackWrapHeightOrScroll.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/element/ZStackWrapHeightOrScroll.kt new file mode 100644 index 00000000..afaab7fa --- /dev/null +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/element/ZStackWrapHeightOrScroll.kt @@ -0,0 +1,20 @@ +package io.noties.adapt.ui.element + +import android.view.View +import android.widget.FrameLayout +import android.widget.ScrollView +import io.noties.adapt.ui.LayoutParams +import io.noties.adapt.ui.ViewElement +import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.widget.FrameLayoutWrapHeightOrScroll + +/** + * @see FrameLayoutWrapHeightOrScroll + */ +@Suppress("FunctionName") +fun ViewFactory.ZStackWrapHeightOrScroll( + scrollStyle: (ViewElement) -> Unit = {}, + child: ViewFactory.() -> ViewElement +) = Element( + provider = { FrameLayoutWrapHeightOrScroll(it, scrollStyle, child) } +) \ No newline at end of file diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/util/ActivityUtil.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/util/ActivityUtil.kt new file mode 100644 index 00000000..466211a7 --- /dev/null +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/util/ActivityUtil.kt @@ -0,0 +1,35 @@ +package io.noties.adapt.ui.util + +import android.app.Activity +import android.view.View +import io.noties.adapt.ui.LayoutParams +import io.noties.adapt.ui.ViewFactory + +/** + * ```kotlin + * fun onCreate(sis: Bundle?) { + * super.onCreate(sis) + * + * setContentUI { + * // .layout(FILL, FILL) by default + * ZStack { + * Text("Hello!") + * } + * } + * } + * ``` + */ +fun Activity.setContentUI( + block: ViewFactory.() -> Unit +): View { + // just in case supplied activity would be used as a reference + val activity = this + return ViewFactory.newView(this) + // MATCH|MATCH is used by default (if supplied view has it -> they would not be overriding + // supplied values) + .layoutParams(LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)) + .create(block) + .also { + setContentView(it) + } +} \ No newline at end of file diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/util/ImeOptions.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/util/ImeOptions.kt index dc60d24d..9e70c244 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/util/ImeOptions.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/util/ImeOptions.kt @@ -1,55 +1,191 @@ package io.noties.adapt.ui.util import android.os.Build -import android.view.inputmethod.EditorInfo +import android.view.KeyEvent +import android.view.inputmethod.EditorInfo.IME_ACTION_DONE +import android.view.inputmethod.EditorInfo.IME_ACTION_GO +import android.view.inputmethod.EditorInfo.IME_ACTION_NEXT +import android.view.inputmethod.EditorInfo.IME_ACTION_NONE +import android.view.inputmethod.EditorInfo.IME_ACTION_PREVIOUS +import android.view.inputmethod.EditorInfo.IME_ACTION_SEARCH +import android.view.inputmethod.EditorInfo.IME_ACTION_SEND +import android.view.inputmethod.EditorInfo.IME_ACTION_UNSPECIFIED +import android.view.inputmethod.EditorInfo.IME_FLAG_FORCE_ASCII +import android.view.inputmethod.EditorInfo.IME_FLAG_NAVIGATE_NEXT +import android.view.inputmethod.EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS +import android.view.inputmethod.EditorInfo.IME_FLAG_NO_ENTER_ACTION +import android.view.inputmethod.EditorInfo.IME_FLAG_NO_EXTRACT_UI +import android.view.inputmethod.EditorInfo.IME_FLAG_NO_FULLSCREEN +import android.view.inputmethod.EditorInfo.IME_MASK_ACTION +import android.view.inputmethod.EditorInfo.IME_NULL +import android.widget.TextView import androidx.annotation.RequiresApi -abstract class ImeOptionsBase(val value: Int) { +open class ImeOptions( + open val rawValue: Int, + open val editorAction: TextView.OnEditorActionListener? = null +) { + operator fun component1(): Int = rawValue + operator fun component2(): TextView.OnEditorActionListener? = editorAction - val none: ImeOptions get() = ImeOptions(EditorInfo.IME_NULL) + override fun toString(): String { + val rawValue = toImeOptionsString(rawValue) + val editorValue = editorAction + ?.let { ", " } ?: "" + return "ImeOptions($rawValue$editorValue)" + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is ImeOptions) return false + + return rawValue == other.rawValue + } - val actionUnspecified: ImeOptions get() = ImeOptions(clearActionValue or EditorInfo.IME_ACTION_UNSPECIFIED) - val actionNone: ImeOptions get() = ImeOptions(clearActionValue or EditorInfo.IME_ACTION_NONE) - val actionGo: ImeOptions get() = ImeOptions(clearActionValue or EditorInfo.IME_ACTION_GO) - val actionSearch: ImeOptions get() = ImeOptions(clearActionValue or EditorInfo.IME_ACTION_SEARCH) - val actionSend: ImeOptions get() = ImeOptions(clearActionValue or EditorInfo.IME_ACTION_SEND) - val actionNext: ImeOptions get() = ImeOptions(clearActionValue or EditorInfo.IME_ACTION_NEXT) - val actionDone: ImeOptions get() = ImeOptions(clearActionValue or EditorInfo.IME_ACTION_DONE) - val actionPrevious: ImeOptions get() = ImeOptions(clearActionValue or EditorInfo.IME_ACTION_PREVIOUS) + override fun hashCode(): Int { + return rawValue + } - @get:RequiresApi(Build.VERSION_CODES.O) - val noPersonalizedLearning: ImeOptions - get() = ImeOptions(value or EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING) + companion object : ContinueImeOptions - val noFullScreen: ImeOptions get() = ImeOptions(value or EditorInfo.IME_FLAG_NO_FULLSCREEN) - val noExactUi: ImeOptions get() = ImeOptions(value or EditorInfo.IME_FLAG_NO_EXTRACT_UI) - val noEnterAction: ImeOptions get() = ImeOptions(value or EditorInfo.IME_FLAG_NO_ENTER_ACTION) + interface TerminatingImeOptions { + val rawValue: Int - val navigatePrevious: ImeOptions get() = ImeOptions(value or EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS) - val navigateNext: ImeOptions get() = ImeOptions(value or EditorInfo.IME_FLAG_NAVIGATE_NEXT) + val none: ImeOptions get() = ImeOptions(IME_NULL) - val forceAscii: ImeOptions get() = ImeOptions(value or EditorInfo.IME_FLAG_FORCE_ASCII) + // typealias cannot be local and requires an import, thus `(() -> Unit)?` - private val clearActionValue: Int get() = value and EditorInfo.IME_MASK_ACTION.inv() + fun actionGo(editorAction: (() -> Unit)? = null) = action(IME_ACTION_GO, editorAction) - override fun toString(): String { - return "ImeOptions(value=$value)" + fun actionSearch(editorAction: (() -> Unit)? = null) = + action(IME_ACTION_SEARCH, editorAction) + + fun actionSend(editorAction: (() -> Unit)? = null) = action(IME_ACTION_SEND, editorAction) + + fun actionNext(editorAction: (() -> Unit)? = null) = action(IME_ACTION_NEXT, editorAction) + + fun actionDone(editorAction: (() -> Unit)? = null) = action(IME_ACTION_DONE, editorAction) + + fun actionPrevious(editorAction: (() -> Unit)? = null) = + action(IME_ACTION_PREVIOUS, editorAction) + + // `there is no available action` according to the documentation, no need + // to register an editor-listener + val actionNone: ImeOptions get() = ImeOptions(rawValueWithAction(IME_ACTION_NONE)) + + // does not indicate any specific action and lets IME come up with own + // should not register listener + val actionUnspecified: ImeOptions + get() = ImeOptions( + rawValueWithAction( + IME_ACTION_UNSPECIFIED + ) + ) + + // Allows specifying raw Ime flags and action + fun raw(rawValue: Int, editorAction: TextView.OnEditorActionListener? = null) = + ImeOptions(rawValue, editorAction) + + // clear action bits from current `rawValue` and merge it with requested action + private fun rawValueWithAction(action: Int): Int = + (rawValue and IME_MASK_ACTION.inv()) or action + + // this way editor-action is also added when action is specified + private fun action(action: Int, editorAction: (() -> Unit)?) = ImeOptions( + // this value must be merged with represent the whole value + rawValueWithAction(action), + // action should only receive ACTION_ID (cleared of all other flags) + // this must be exact value compared with `==` + ActionListener( + action and IME_MASK_ACTION, + editorAction + ) + ) } - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is ImeOptions) return false + internal interface StatelessTerminatingImeOptions : TerminatingImeOptions { + override val rawValue: Int get() = 0 + } - if (value != other.value) return false + internal interface ContinueImeOptions : StatelessTerminatingImeOptions { + val noFullScreen get() = ContinueImeOptionsImpl(rawValue or IME_FLAG_NO_FULLSCREEN) + val noExtractUi get() = ContinueImeOptionsImpl(rawValue or IME_FLAG_NO_EXTRACT_UI) + val noEnterAction get() = ContinueImeOptionsImpl(rawValue or IME_FLAG_NO_ENTER_ACTION) + val navigatePrevious get() = ContinueImeOptionsImpl(rawValue or IME_FLAG_NAVIGATE_PREVIOUS) + val navigateNext get() = ContinueImeOptionsImpl(rawValue or IME_FLAG_NAVIGATE_NEXT) + val forceAscii get() = ContinueImeOptionsImpl(rawValue or IME_FLAG_FORCE_ASCII) - return true + val noPersonalizedLearning + @RequiresApi(Build.VERSION_CODES.O) + get() = ContinueImeOptionsImpl(rawValue or IME_FLAG_NO_PERSONALIZED_LEARNING) } - override fun hashCode(): Int { - return value + // the options that could be configured further until terminating option + // NB! must not specify editor-action (which can be specified only by terminating + // actions) + class ContinueImeOptionsImpl(override val rawValue: Int) : ImeOptions(rawValue), ContinueImeOptions + + private class ActionListener( + val actionId: Int, + val action: (() -> Unit)? + ) : TextView.OnEditorActionListener { + override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean { + // check the supplied action + return if (actionId == this.actionId && + (event == null || event.action == KeyEvent.ACTION_UP) + ) { + action?.invoke() + true + } else { + false + } + } } + + } -class ImeOptions(value: Int) : ImeOptionsBase(value) { - companion object : ImeOptionsBase(EditorInfo.IME_NULL) +typealias ImeOptionsBuilder = ImeOptions.Companion.() -> ImeOptions + +private val IME_FLAG_NO_PERSONALIZED_LEARNING = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + android.view.inputmethod.EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING + } else { + // taken from Android source code, fallback + 0x1000000 + } + +internal fun toImeOptionsString(rawValue: Int): String { + if (rawValue == IME_NULL) return "NULL" + val action = (rawValue and IME_MASK_ACTION) + .let { + when (it) { + IME_ACTION_UNSPECIFIED -> "ACTION_UNSPECIFIED" + IME_ACTION_GO -> "ACTION_GO" + IME_ACTION_SEARCH -> "ACTION_SEARCH" + IME_ACTION_SEND -> "ACTION_SEND" + IME_ACTION_NEXT -> "ACTION_NEXT" + IME_ACTION_DONE -> "ACTION_DONE" + IME_ACTION_PREVIOUS -> "ACTION_PREVIOUS" + IME_ACTION_NONE -> "ACTION_NONE" + else -> "$it" + } + } + val flags = listOf( + "FLAG_NO_FULLSCREEN" to IME_FLAG_NO_FULLSCREEN, + "FLAG_NO_EXTRACT_UI" to IME_FLAG_NO_EXTRACT_UI, + "FLAG_NO_ENTER_ACTION" to IME_FLAG_NO_ENTER_ACTION, + "FLAG_NAVIGATE_PREVIOUS" to IME_FLAG_NAVIGATE_PREVIOUS, + "FLAG_NAVIGATE_NEXT" to IME_FLAG_NAVIGATE_NEXT, + "FLAG_FORCE_ASCII" to IME_FLAG_FORCE_ASCII, + "FLAG_NO_PERSONALIZED_LEARNING" to IME_FLAG_NO_PERSONALIZED_LEARNING, + ) + .filter { + (rawValue or it.second) == it.second + } + .joinToString(", ") { it.first } + .takeIf { it.isNotEmpty() } + ?.let { "[$it]" } + return listOfNotNull(action, flags) + .joinToString(":") } \ No newline at end of file diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/util/InputType.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/util/InputType.kt index 60e28b12..dbf0b207 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/util/InputType.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/util/InputType.kt @@ -9,6 +9,8 @@ open class InputType(val value: Int) { val number: Number get() = Number() val phone: InputType get() = InputType(EditorInfo.TYPE_CLASS_PHONE) val dateTime: DateTime get() = DateTime() + + fun raw(value: Int): InputType = InputType(value) } class Text( @@ -82,4 +84,6 @@ open class InputType(val value: Int) { override fun hashCode(): Int { return value } -} \ No newline at end of file +} + +typealias InputTypeBuilder = InputType.Companion.() -> InputType \ No newline at end of file diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/util/OnScrollChangedListener.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/util/OnScrollChangedListener.kt index 4cb9d885..7f89fd9d 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/util/OnScrollChangedListener.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/util/OnScrollChangedListener.kt @@ -1,11 +1,8 @@ package io.noties.adapt.ui.util -import android.os.Build import android.view.View -import android.view.View.OnAttachStateChangeListener import android.view.View.OnScrollChangeListener import android.view.ViewTreeObserver -import androidx.annotation.RequiresApi import io.noties.adapt.ui.R import java.util.concurrent.CopyOnWriteArrayList @@ -28,25 +25,15 @@ fun V.addOnScrollChangedListener( ): OnScrollChangedListenerRegistration { val view = this - // use native scroll listener (with delegate) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - val listener = OnScrollChangeListener { _, scrollX, scrollY, oldScrollX, oldScrollY -> - action(view, scrollX - oldScrollX, scrollY - oldScrollY) - } - val delegate = OnScrollChangedListenerDelegate.invoke(view) - delegate.add(listener) - return OnScrollChangedListenerRegistration { delegate.remove(listener) } + val listener = OnScrollChangeListener { _, scrollX, scrollY, oldScrollX, oldScrollY -> + action(view, scrollX - oldScrollX, scrollY - oldScrollY) } + val delegate = OnScrollChangedListenerDelegate.invoke(view) + delegate.add(listener) - val delegate = OnScrollChangedListenerDelegatePreM(view) { deltaX, deltaY -> - action(view, deltaX, deltaY) - } - return OnScrollChangedListenerRegistration { - delegate.unregister() - } + return OnScrollChangedListenerRegistration { delegate.remove(listener) } } -@RequiresApi(Build.VERSION_CODES.M) class OnScrollChangedListenerDelegate private constructor(val view: View) { private val listeners = CopyOnWriteArrayList() @@ -85,44 +72,4 @@ class OnScrollChangedListenerDelegate private constructor(val view: View) { fun remove(listener: OnScrollChangeListener) { listeners.remove(listener) } -} - -private class OnScrollChangedListenerDelegatePreM( - val view: View, - val callback: (deltaX: Int, deltaY: Int) -> Unit -) { - - private var previousX = view.scrollX - private var previousY = view.scrollY - - private val onScrollChangedListener = ViewTreeObserver.OnScrollChangedListener { - val x = view.scrollX - val y = view.scrollY - if (x != previousX || y != previousY) { - callback(x - previousX, y - previousY) - previousX = x - previousY = y - } - } - - private val onAttachStateChangeListener = object : OnAttachStateChangeListener { - override fun onViewAttachedToWindow(v: View) = Unit - override fun onViewDetachedFromWindow(v: View) { - unregister() - } - } - - init { - view.viewTreeObserver - .takeIf { it.isAlive } - ?.addOnScrollChangedListener(onScrollChangedListener) - view.addOnAttachStateChangeListener(onAttachStateChangeListener) - } - - fun unregister() { - view.viewTreeObserver - .takeIf { it.isAlive } - ?.removeOnScrollChangedListener(onScrollChangedListener) - view.removeOnAttachStateChangeListener(onAttachStateChangeListener) - } } \ No newline at end of file diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/util/ViewUtil.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/util/ViewUtil.kt index cdef3df1..bf38efcd 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/util/ViewUtil.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/util/ViewUtil.kt @@ -1,5 +1,8 @@ package io.noties.adapt.ui.util +import android.app.Activity +import android.content.Context +import android.content.ContextWrapper import android.view.View import android.view.ViewTreeObserver.OnPreDrawListener import io.noties.adapt.ui.LayoutParams @@ -53,4 +56,23 @@ fun V.onDetachedOnce(block: (V) -> Unit) { * .textSize { body } * ``` */ -val V.element: ViewElement get() = ViewElement.create(this) \ No newline at end of file +val V.element: ViewElement get() = ViewElement.create(this) + +/** + * Searches for the holding Activity + */ +val View.activity: Activity? get() { + var context: Context? = this.context + while (context != null) { + if (context is Activity) { + return context + } + context = (context as? ContextWrapper)?.baseContext + } + return null +} + +/** + * Searches for focused view in dedicated to this view Activity + */ +val View.currentFocus: View? get() = activity?.currentFocus \ No newline at end of file diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/widget/AdaptViewPagerWrapContent.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/widget/AdaptViewPagerWrapContent.kt index ae0b88eb..5a289cdc 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/widget/AdaptViewPagerWrapContent.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/widget/AdaptViewPagerWrapContent.kt @@ -1,25 +1,13 @@ package io.noties.adapt.ui.widget import android.content.Context -import android.os.Build import android.transition.TransitionManager import android.view.ViewGroup import androidx.viewpager.widget.ViewPager -import io.noties.adapt.ui.LayoutParams -import io.noties.adapt.ui.ViewFactory -import io.noties.adapt.ui.element.Element import io.noties.adapt.viewpager.AdaptViewPager import kotlin.math.roundToInt -/** - * NB! in order to function ViewPager must be initialized with AdaptViewPager.init - * @since $UNRELEASED; - */ -@Suppress("FunctionName") -fun ViewFactory.AdaptPagerWrapContent( -) = Element { AdaptViewPagerWrapContent(it) } - -// view pager is great, if it is added to a scroll view, then the scroll view is not scrolling +// view pager is great, but if it is added to a scroll view, then the scroll view is not scrolling /** * Requires ViewPager to be initialized with Adapt - [AdaptViewPager.init] * @since $UNRELEASED; @@ -99,9 +87,7 @@ class AdaptViewPagerWrapContent(context: Context) : ViewPager(context) { class DefaultChangeHandler : ChangeHandler { override fun beforeChange(pager: AdaptViewPagerWrapContent) { val parent = pager.parent as? ViewGroup ?: return - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - TransitionManager.endTransitions(parent) - } + TransitionManager.endTransitions(parent) TransitionManager.beginDelayedTransition(parent) } diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/widget/FrameLayoutWrapHeightOrScroll.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/widget/FrameLayoutWrapHeightOrScroll.kt new file mode 100644 index 00000000..64688611 --- /dev/null +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/widget/FrameLayoutWrapHeightOrScroll.kt @@ -0,0 +1,155 @@ +package io.noties.adapt.ui.widget + +import android.annotation.SuppressLint +import android.content.Context +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.ScrollView +import io.noties.adapt.ui.ViewElement +import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.element.Element +import io.noties.adapt.ui.element.VScroll +import io.noties.adapt.ui.element.VStack +import io.noties.adapt.ui.indent + +/** + * @see io.noties.adapt.ui.element.ZStackWrapHeightOrScroll + * @see LinearLayoutContentMeasuredLast + */ +@SuppressLint("ViewConstructor") +class FrameLayoutWrapHeightOrScroll( + context: Context, + private val scrollStyle: (ViewElement) -> Unit, + child: ViewFactory.() -> ViewElement +) : FrameLayout(context) { + + private val content: View + + private var callbacks: Runnable? = null + + init { + content = ViewFactory( + context, + this + ).let { child.invoke(it) } + .also { it.init(context) } + .also { + if (it.view.layoutParams == null) { + it.view.layoutParams = + LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) + } + } + .also { it.render() } + .also { + addView(it.view) + } + .view + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + val width = MeasureSpec.getSize(widthMeasureSpec) + val height = MeasureSpec.getSize(heightMeasureSpec) + + val child = content + + // unspecified and without padding (yet)! + val childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) + child.measure( + MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + childHeightSpec + ) + + val childMeasuredHeight = child.measuredHeight + + val isOverflow = childMeasuredHeight > (height - paddingTop - paddingBottom) + + if (child.parent != this) { + // additionally measure wrapper content + getChildAt(0).measure( + MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY) + ) + } + + if (!isOverflow) { + setMeasuredDimension( + width, + childMeasuredHeight + paddingTop + paddingBottom + ) + + unwrapContentFromScrollView() + + } else { + setMeasuredDimension( + width, + height + ) + + wrapContentInScrollView(height) + } + } + + private fun wrapContentInScrollView(height: Int) { + removeWrapUnwrapCallbacks() + + // cannot do anything until parent is present + val parent = content.parent as? ViewGroup ?: return + if (parent != this) { + // assume already wrapped + return + } + + postWrapUnwrapCallbacks { + // remove from this layout + removeAllViews() + + val view = ViewFactory.newView(this) + .layoutParams(LayoutParams(LayoutParams.MATCH_PARENT, height)) + .create { + VScroll { + VStack { + Element { content } + } + }.indent() + .also(scrollStyle) + } + + addView(view) + } + } + + private fun unwrapContentFromScrollView() { + removeWrapUnwrapCallbacks() + + val parent = content.parent as? ViewGroup ?: return + if (parent == this) { + // assume already unwrapped + return + } + + postWrapUnwrapCallbacks { + parent.removeAllViews() + + removeAllViews() + + addView(content) + } + } + + private fun removeWrapUnwrapCallbacks() { + callbacks?.also { removeCallbacks(it) } + } + + private fun postWrapUnwrapCallbacks(callbacks: Runnable) { + this.callbacks = callbacks + post(callbacks) + } + + override fun canScrollVertically(direction: Int): Boolean { + // if contains scroll-view -> pass to it, else false + // why not always passing to the child? + val scrollView = getChildAt(0) as? ScrollView ?: return false + return scrollView.canScrollVertically(direction) + } +} \ No newline at end of file diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/widget/LazyView.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/widget/LazyView.kt index 26ad21fc..43cbd38d 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/widget/LazyView.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/widget/LazyView.kt @@ -13,6 +13,8 @@ import io.noties.adapt.ui.ViewFactory * and then replace self with children of the supplied factory. Multiple children * are supported. Setting visibility [View.VISIBLE] or [View.INVISIBLE] is the same * as calling [LazyView.inject] directly + * + * @see io.noties.adapt.ui.element.Lazy */ @SuppressLint("ViewConstructor") open class LazyView internal constructor( diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/widget/LinearLayoutContentMeasuredLast.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/widget/LinearLayoutContentMeasuredLast.kt new file mode 100644 index 00000000..9c8885a6 --- /dev/null +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/widget/LinearLayoutContentMeasuredLast.kt @@ -0,0 +1,143 @@ +package io.noties.adapt.ui.widget + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout + +/** + * A version of vertical `LinearLayout` that measures children based on priority. + * Normally, layout receives available height dimensions (in the form of $HEIGHT and AT_MOST + * measure-spec mode). So, in order to not exceed the available in parent height - instead + * wrap content-view into a scrollable-component (like `ScrollView`) without actually exceeding + * parent height, but receiving such event to adjust current layout. For this, this layout + * first measures all _normal_ children occupying all required height. And then passing all rest + * height to the content child(ren). This way content should not exceed available height, thus growing + * this whole view (as normally it is allowed with AT_MOST mode) won\'t happen. + * This layout is intended to be used with the [FrameLayoutWrapHeightOrScroll] which would + * wrap content into a [ScrollView] when content height is greater than available height. + * + * @see io.noties.adapt.ui.element.VStackContentMeasuredLast + * @see FrameLayoutWrapHeightOrScroll + */ +class LinearLayoutContentMeasuredLast : LinearLayout { + constructor(context: Context?) : super(context) + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) + + init { + orientation = VERTICAL + } + + class LayoutParams : LinearLayout.LayoutParams { + var isContent: Boolean = false + + constructor(c: Context, attrs: AttributeSet?) : super(c, attrs) + constructor(width: Int, height: Int) : super(width, height) + constructor(width: Int, height: Int, weight: Float) : super(width, height, weight) + constructor(p: ViewGroup.LayoutParams) : super(p) + constructor(source: MarginLayoutParams) : super(source) + constructor(source: LinearLayout.LayoutParams) : super(source) + } + + private companion object { + val View.isContentView: Boolean + get() { + return true == (this.layoutParams as? LayoutParams)?.isContent + } + + val ViewGroup.children: List + get() = (0 until childCount) + .map { getChildAt(it) } + } + + override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams { + return LayoutParams(context, attrs) + } + + override fun generateDefaultLayoutParams(): LayoutParams { + return LayoutParams( + io.noties.adapt.ui.LayoutParams.MATCH_PARENT, + io.noties.adapt.ui.LayoutParams.WRAP_CONTENT + ) + } + + override fun generateLayoutParams(lp: ViewGroup.LayoutParams): LayoutParams { + return LayoutParams(lp) + } + + override fun checkLayoutParams(p: ViewGroup.LayoutParams): Boolean { + return p is LayoutParams + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + // if content is not found -> just super + + val (contentChildren, children) = children + .partition { it.isContentView } + + if (contentChildren.isEmpty()) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + return + } + + val width = MeasureSpec.getSize(widthMeasureSpec) + val height = MeasureSpec.getSize(heightMeasureSpec) + + var availableHeight = height - paddingTop - paddingBottom + + fun measureChild(child: View) { + measureChild( + child, + childWidthMeasureSpec(width, child), + childHeightMeasureSpec( + availableHeight - child.heightLayoutMargins() + ) + ) + availableHeight -= (child.measuredHeight + child.heightLayoutMargins()) + } + + children.forEach { measureChild(it) } + contentChildren.forEach { measureChild(it) } + + setMeasuredDimension( + width, + height - availableHeight + ) + } + + private fun childWidthMeasureSpec(parentWidth: Int, child: View): Int { + return MeasureSpec.makeMeasureSpec( + (parentWidth - paddingLeft - paddingRight - child.widthLayoutMargins()), + MeasureSpec.EXACTLY + ) + } + + private fun childHeightMeasureSpec(height: Int): Int { + return MeasureSpec.makeMeasureSpec( + height, + MeasureSpec.EXACTLY + ) + } + + private fun View.widthLayoutMargins(): Int { + val lp = this.layoutParams as? LayoutParams + return if (lp != null) { + lp.leftMargin + lp.rightMargin + } else 0 + } + + private fun View.heightLayoutMargins(): Int { + val lp = this.layoutParams as? LayoutParams + return if (lp != null) { + lp.topMargin + lp.bottomMargin + } else 0 + } + + // pass scroll to content + override fun canScrollVertically(direction: Int): Boolean { + return children + .filter { it.isContentView } + .any { it.canScrollVertically(direction) } + } +} \ No newline at end of file diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/widget/LinearLayoutReverseDrawingOrder.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/widget/LinearLayoutReverseDrawingOrder.kt index acb58536..1c3d5d82 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/widget/LinearLayoutReverseDrawingOrder.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/widget/LinearLayoutReverseDrawingOrder.kt @@ -3,26 +3,15 @@ package io.noties.adapt.ui.widget import android.content.Context import android.util.AttributeSet import android.widget.LinearLayout -import io.noties.adapt.ui.LayoutParams -import io.noties.adapt.ui.ViewFactory -import io.noties.adapt.ui.element.ElementGroup -import io.noties.adapt.ui.element.VStackDefaultGravity -import io.noties.adapt.ui.util.Gravity - -@Suppress("FunctionName") -fun ViewFactory.VStackReverseDrawingOrder( - gravity: Gravity = VStackDefaultGravity, - children: ViewFactory.() -> Unit -) = ElementGroup( - { - LinearLayoutReverseDrawingOrder(it).also { ll -> - ll.orientation = LinearLayout.VERTICAL - ll.gravity = gravity.value - } - }, - children -) +/** + * Version of vertical `LinearLayout` that draws children in reverse order (from bottom to top). + * This allows _sticking_ prior views on top of others. For example, like in [io.noties.adapt.ui.sticky.StickyVerticalScroll] + * which allows sticking section-views in a scrollable list + * + * @see io.noties.adapt.ui.element.VStackReverseDrawingOrder + * + */ // a reverse linear layout would actually help keeping the top view on top open class LinearLayoutReverseDrawingOrder : LinearLayout { constructor(context: Context) : super(context) diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/widget/SquareFrameLayout.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/widget/SquareFrameLayout.kt index 7f36f20a..bc90e83b 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/widget/SquareFrameLayout.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/widget/SquareFrameLayout.kt @@ -2,20 +2,10 @@ package io.noties.adapt.ui.widget import android.content.Context import android.widget.FrameLayout -import io.noties.adapt.ui.LayoutParams -import io.noties.adapt.ui.ViewFactory -import io.noties.adapt.ui.element.ElementGroup - -@Suppress("FunctionName") -fun ViewFactory.ZStackSquare( - children: ViewFactory.() -> Unit -) = ElementGroup( - { SquareFrameLayout(it) }, - children -) /** * Square Frame layout that will use `width` as the base and assign height to be the same + * @see io.noties.adapt.ui.element.ZStackSquare */ class SquareFrameLayout(context: Context) : FrameLayout(context) { override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { diff --git a/adapt-ui/src/main/java/io/noties/adapt/ui/widget/Web.kt b/adapt-ui/src/main/java/io/noties/adapt/ui/widget/WebViewPlaceholder.kt similarity index 73% rename from adapt-ui/src/main/java/io/noties/adapt/ui/widget/Web.kt rename to adapt-ui/src/main/java/io/noties/adapt/ui/widget/WebViewPlaceholder.kt index 61f72ae7..d79dede5 100644 --- a/adapt-ui/src/main/java/io/noties/adapt/ui/widget/Web.kt +++ b/adapt-ui/src/main/java/io/noties/adapt/ui/widget/WebViewPlaceholder.kt @@ -7,16 +7,10 @@ import android.content.Context import android.content.ContextWrapper import android.os.Bundle import android.view.ViewGroup -import android.webkit.WebChromeClient -import android.webkit.WebSettings import android.webkit.WebView -import android.webkit.WebViewClient import android.widget.FrameLayout import androidx.annotation.VisibleForTesting -import io.noties.adapt.ui.LayoutParams -import io.noties.adapt.ui.ViewElement import io.noties.adapt.ui.ViewFactory -import io.noties.adapt.ui.element.Element /** * @since $UNRELEASED; @@ -173,57 +167,4 @@ open class WebViewPlaceholder(context: Context) : FrameLayout(context) { readyCallbacks.forEach { it(webView) } readyCallbacks.clear() } -} - -@Suppress("FunctionName") -fun ViewFactory.Web( - url: String? = null, - webViewFactory: ((Context) -> WebView)? = null, - onFailedWebViewInflation: ((WebViewPlaceholder) -> Unit)? = null, - // can be used to show content whilst webView inflation is in progress - placeholderContent: (ViewFactory.() -> Unit)? = null -) = Element { - WebViewPlaceholder(it).also { wvp -> - webViewFactory?.also { factory -> wvp.webViewFactory = factory } - wvp.onFailedWebViewInflation = onFailedWebViewInflation - wvp.placeholderContent = placeholderContent - if (url != null) { - wvp.onWebViewReady { wv -> wv.loadUrl(url) } - } - } -} - -// exposed for customizations not in layout -fun ViewElement.webOnElementReady( - block: (ViewElement) -> Unit -) = onView { - it.onWebViewReady { wv -> - val element = ViewElement { wv } - element.init(wv.context) - element.render(block) - } -} - -fun ViewElement.webLoad( - url: String -) = onView { - it.onWebViewReady { wv -> wv.loadUrl(url) } -} - -fun ViewElement.webSettings( - settings: (WebSettings) -> Unit -) = onView { - it.onWebViewReady { wv -> settings(wv.settings) } -} - -fun ViewElement.webClient( - client: WebViewClient -) = onView { - it.onWebViewReady { wv -> wv.webViewClient = client } -} - -fun ViewElement.webChromeClient( - client: WebChromeClient -) = onView { - it.onWebViewReady { wv -> wv.webChromeClient = client } } \ No newline at end of file diff --git a/adapt-ui/src/test/java/io/noties/adapt/ui/element/Text_Test.kt b/adapt-ui/src/test/java/io/noties/adapt/ui/element/Text_Test.kt index 13edfa1f..60598c2b 100644 --- a/adapt-ui/src/test/java/io/noties/adapt/ui/element/Text_Test.kt +++ b/adapt-ui/src/test/java/io/noties/adapt/ui/element/Text_Test.kt @@ -40,13 +40,15 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mockito.RETURNS_MOCKS -import org.mockito.Mockito.`when` import org.mockito.Mockito.mock +import org.mockito.Mockito.`when` import org.mockito.kotlin.any -import org.mockito.kotlin.eq -import org.mockito.kotlin.verify import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.eq import org.mockito.kotlin.never +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.whenever import org.robolectric.RobolectricTestRunner import org.robolectric.RuntimeEnvironment @@ -685,15 +687,15 @@ class Text_Test { fun textImeOptions() { val inputs = listOf( ImeOptions.none, - ImeOptions.actionDone.noEnterAction.noExactUi.forceAscii, - ImeOptions.navigatePrevious + ImeOptions.noEnterAction.noExtractUi.forceAscii, + ImeOptions.navigatePrevious.actionPrevious { } ) for (input in inputs) { newTextElement() - .textImeOptions(input) + .textImeOptions { input } .renderView { - verify(this).imeOptions = eq(input.value) + verify(this).imeOptions = eq(input.rawValue) } } } @@ -703,22 +705,19 @@ class Text_Test { // none triggers all // action would be delivered if it is requested - fun mockAction(): (TextView, ImeOptions) -> Boolean { - val action: (TextView, ImeOptions) -> Boolean = mockt() - whenever(action.invoke(any(), any())).thenReturn(true) - return action - } + val editorActionDone: () -> Unit = mockt() + val editorActionNext: () -> Unit = mockt() val inputs = listOf( - ImeOptions.none to mockAction(), - ImeOptions.actionDone to mockAction(), - ImeOptions.actionNext.forceAscii.noExactUi to mockAction() + ImeOptions.actionDone(editorActionDone) to editorActionDone, + ImeOptions.forceAscii.noExtractUi.actionNext(editorActionNext) to editorActionNext ) - for ((options, action) in inputs) { + for ((input, action) in inputs) { newTextElement() - .textImeOptions(options, action) + .textImeOptions { input } .renderView { + val s = input.toString() val listener = kotlin.run { val captor = ArgumentCaptor.forClass(OnEditorActionListener::class.java) @@ -726,48 +725,29 @@ class Text_Test { captor.value } - val expectedActionId = options.value and EditorInfo.IME_MASK_ACTION - if (expectedActionId == 0) { - // all events must be delivered - listOf( - ImeOptions.actionSearch, - ImeOptions.actionDone, - ImeOptions.actionSend - ).forEach { - assertEquals( - options.toString(), - true, - listener.onEditorAction(this, it.value, null) - ) - verify(action).invoke(eq(this), eq(ImeOptions(it.value))) - } - } else { - // just our action must be delivered - listOf( - ImeOptions.none, - ImeOptions.actionDone, - ImeOptions.actionSend, - ImeOptions.actionSearch, - ImeOptions.actionNext, - ImeOptions.actionGo, - ImeOptions.actionPrevious, - ImeOptions.actionUnspecified, - ) - .filter { - it.value != expectedActionId - } - .forEach { - assertEquals( - options.toString(), - false, - listener.onEditorAction(this, it.value, null) - ) - verify(action, never()).invoke(any(), any()) - } - - listener.onEditorAction(this, expectedActionId, null) - verify(action).invoke(eq(this), eq(ImeOptions(expectedActionId))) + // mock what system does with flags internally + val platformActionId = input.rawValue and EditorInfo.IME_MASK_ACTION + + // right now the action would be present, no need to deliver all events + require(platformActionId != 0) { "right now the action would be present, no need to deliver all events" } + + assertEquals( + s, + true, + listener.onEditorAction(this, platformActionId, null) + ) + verify(action, times(1)).invoke() + + // send other events that would not trigger out action + val others = listOf( + ImeOptions.actionPrevious(), + ImeOptions.actionGo() + ) + for (other in others) { + listener.onEditorAction(this, other.rawValue, null) } + // after other actions must still be 1 (as above) + verify(action, times(1)).invoke() } } } diff --git a/adapt-ui/src/test/java/io/noties/adapt/ui/util/ImeOptions_Test.kt b/adapt-ui/src/test/java/io/noties/adapt/ui/util/ImeOptions_Test.kt index 4573e7b0..2dac7ffd 100644 --- a/adapt-ui/src/test/java/io/noties/adapt/ui/util/ImeOptions_Test.kt +++ b/adapt-ui/src/test/java/io/noties/adapt/ui/util/ImeOptions_Test.kt @@ -28,56 +28,102 @@ import org.robolectric.annotation.Config class ImeOptions_Test { @Test - fun companion() { + fun nonTerminating() { val inputs = listOf( ImeOptions.none to IME_NULL, - ImeOptions.actionUnspecified to IME_ACTION_UNSPECIFIED, - ImeOptions.actionNone to IME_ACTION_NONE, - ImeOptions.actionGo to IME_ACTION_GO, - ImeOptions.actionSearch to IME_ACTION_SEARCH, - ImeOptions.actionSend to IME_ACTION_SEND, - ImeOptions.actionNext to IME_ACTION_NEXT, - ImeOptions.actionDone to IME_ACTION_DONE, - ImeOptions.actionPrevious to IME_ACTION_PREVIOUS, ImeOptions.noPersonalizedLearning to IME_FLAG_NO_PERSONALIZED_LEARNING, ImeOptions.noFullScreen to IME_FLAG_NO_FULLSCREEN, - ImeOptions.noExactUi to IME_FLAG_NO_EXTRACT_UI, + ImeOptions.noExtractUi to IME_FLAG_NO_EXTRACT_UI, ImeOptions.noEnterAction to IME_FLAG_NO_ENTER_ACTION, ImeOptions.navigatePrevious to IME_FLAG_NAVIGATE_PREVIOUS, ImeOptions.navigateNext to IME_FLAG_NAVIGATE_NEXT, ImeOptions.forceAscii to IME_FLAG_FORCE_ASCII, + ImeOptions.noPersonalizedLearning.noFullScreen to (IME_FLAG_NO_PERSONALIZED_LEARNING or IME_FLAG_NO_FULLSCREEN), + ImeOptions.noExtractUi.noEnterAction to (IME_FLAG_NO_EXTRACT_UI or IME_FLAG_NO_ENTER_ACTION), + ImeOptions.navigatePrevious.navigateNext to (IME_FLAG_NAVIGATE_PREVIOUS or IME_FLAG_NAVIGATE_NEXT), + ImeOptions.forceAscii.noPersonalizedLearning to (IME_FLAG_FORCE_ASCII or IME_FLAG_NO_PERSONALIZED_LEARNING) ) + var i = 0 + for ((ime, expected) in inputs) { - assertEquals(ime.toString(), expected, ime.value) + val s = "${i++} expected:${toImeOptionsString(expected)} ime:$ime" + + val (rawValue, editorAction) = ime + assertEquals(s, rawValue, ime.rawValue) + assertEquals(s, editorAction, ime.editorAction) + + assertEquals(s, expected, ime.rawValue) + assertNull(s, ime.editorAction) } } @Test - fun instance() { + fun terminating() { val inputs = listOf( - ImeOptions.actionDone.actionGo.actionNone.actionUnspecified.actionSearch to IME_ACTION_SEARCH, - ImeOptions.forceAscii.actionSend.actionGo.noExactUi to (IME_FLAG_FORCE_ASCII or IME_ACTION_GO or IME_FLAG_NO_EXTRACT_UI), - ImeOptions.none.none to IME_NULL, - ImeOptions.actionUnspecified.actionUnspecified to IME_ACTION_UNSPECIFIED, - ImeOptions.actionNone.actionNone to IME_ACTION_NONE, - ImeOptions.actionGo.actionGo to IME_ACTION_GO, - ImeOptions.actionSearch.actionSearch to IME_ACTION_SEARCH, - ImeOptions.actionSend.actionSend to IME_ACTION_SEND, - ImeOptions.actionNext.actionNext to IME_ACTION_NEXT, - ImeOptions.actionDone.actionDone to IME_ACTION_DONE, - ImeOptions.actionPrevious.actionPrevious to IME_ACTION_PREVIOUS, - ImeOptions.noPersonalizedLearning.noPersonalizedLearning to IME_FLAG_NO_PERSONALIZED_LEARNING, - ImeOptions.noFullScreen.noFullScreen to IME_FLAG_NO_FULLSCREEN, - ImeOptions.noExactUi.noExactUi to IME_FLAG_NO_EXTRACT_UI, - ImeOptions.noEnterAction.noEnterAction to IME_FLAG_NO_ENTER_ACTION, - ImeOptions.navigatePrevious.navigatePrevious to IME_FLAG_NAVIGATE_PREVIOUS, - ImeOptions.navigateNext.navigateNext to IME_FLAG_NAVIGATE_NEXT, - ImeOptions.forceAscii.forceAscii to IME_FLAG_FORCE_ASCII, + // action-none and action-unspecified does not register editor-action-listener + ImeOptions.actionNone to IME_ACTION_NONE, + ImeOptions.actionUnspecified to IME_ACTION_UNSPECIFIED ) for ((ime, expected) in inputs) { - assertEquals(ime.toString(), expected, ime.value) + val s = ime.toString() + + assertEquals(s, expected, ime.rawValue) + assertNull(s, ime.editorAction) + + val (rawValue, editorAction) = ime + assertEquals(s, expected, rawValue) + assertNull(s, editorAction) + } + } + + @Test + fun terminating_with_action() { + val inputs = listOf( + ImeOptions.actionGo() to IME_ACTION_GO, + ImeOptions.actionGo {} to IME_ACTION_GO, + ImeOptions.actionSearch() to IME_ACTION_SEARCH, + ImeOptions.actionSearch {} to IME_ACTION_SEARCH, + ImeOptions.actionSend() to IME_ACTION_SEND, + ImeOptions.actionSend {} to IME_ACTION_SEND, + ImeOptions.actionNext() to IME_ACTION_NEXT, + ImeOptions.actionNext {} to IME_ACTION_NEXT, + ImeOptions.actionDone() to IME_ACTION_DONE, + ImeOptions.actionDone {} to IME_ACTION_DONE, + ImeOptions.actionPrevious() to IME_ACTION_PREVIOUS, + ImeOptions.actionPrevious {} to IME_ACTION_PREVIOUS, + ) + + for ((ime, expected) in inputs) { + val s = "expected:${toImeOptionsString(expected)} ime:$ime" + + assertEquals(s, expected, ime.rawValue) + assertNotNull(s, ime.editorAction) + + val (rawValue, editorAction) = ime + assertEquals(s, expected, rawValue) + assertNotNull(s, editorAction) + } + } + + @Test + fun mix() { + val inputs = listOf( + ImeOptions.forceAscii.noPersonalizedLearning.actionGo() to (IME_FLAG_FORCE_ASCII or IME_FLAG_NO_PERSONALIZED_LEARNING or IME_ACTION_GO), + ImeOptions.noExtractUi.noFullScreen.navigateNext.actionNext() to (IME_FLAG_NO_EXTRACT_UI or IME_FLAG_NO_FULLSCREEN or IME_FLAG_NAVIGATE_NEXT or IME_ACTION_NEXT) + ) + + for ((ime, expected) in inputs) { + val s = "expected:${toImeOptionsString(expected)} ime:$ime" + + // mask with action + assertEquals(s, expected, ime.rawValue) + assertNotNull(s, ime.editorAction) + + val (rawValue, editorAction) = ime + assertEquals(s, expected, rawValue) + assertNotNull(s, editorAction) } } } \ No newline at end of file diff --git a/adapt-ui/src/test/java/io/noties/adapt/ui/widget/WebViewPlaceholder_Test.kt b/adapt-ui/src/test/java/io/noties/adapt/ui/widget/WebViewPlaceholder_Test.kt index 9b356dc8..c305ae7c 100644 --- a/adapt-ui/src/test/java/io/noties/adapt/ui/widget/WebViewPlaceholder_Test.kt +++ b/adapt-ui/src/test/java/io/noties/adapt/ui/widget/WebViewPlaceholder_Test.kt @@ -10,6 +10,11 @@ import android.widget.FrameLayout import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewElement import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.element.Web +import io.noties.adapt.ui.element.webChromeClient +import io.noties.adapt.ui.element.webClient +import io.noties.adapt.ui.element.webLoad +import io.noties.adapt.ui.element.webSettings import io.noties.adapt.ui.testutil.mockt import org.junit.Assert.* import org.junit.Test diff --git a/sample/build.gradle b/sample/build.gradle index be27c409..44dc8ddd 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -67,6 +67,9 @@ dependencies { implementation 'io.noties:debug:5.1.0' + // TODO: update to new artifact + implementation 'ru.noties:scrollable:1.3.0' + implementation deps['flexbox'] implementation 'androidx.constraintlayout:constraintlayout:2.1.4' diff --git a/sample/samples.json b/sample/samples.json index 0fb65dea..6f75716e 100644 --- a/sample/samples.json +++ b/sample/samples.json @@ -1,4 +1,42 @@ [ + { + "javaClassName": "io.noties.adapt.sample.samples.adaptui.AdaptUiTextInputImeOptionsSample", + "id": "20240331120232", + "title": "Text.textImeOptions", + "description": "", + "tags": [ + "adapt-ui" + ] + }, + { + "javaClassName": "io.noties.adapt.sample.samples.adaptui.AdaptUIScrollIfScrollsSample", + "id": "20240327011222", + "title": "AdaptUI. Scroll if content scrolls else wrap height", + "description": "", + "tags": [ + "adapt-ui", + "recipe" + ] + }, + { + "javaClassName": "io.noties.adapt.sample.samples.adaptui.AdaptUITextInputTypeSample", + "id": "20240326221901", + "title": "Text.textInputType", + "description": "", + "tags": [ + "adapt-ui" + ] + }, + { + "javaClassName": "io.noties.adapt.sample.samples.adaptui.ActivitySetContentUISample", + "id": "20240326215919", + "title": "Activity.setContentUI", + "description": "Helper function to set \u003ctt\u003eActivity\u0027s\u003c/tt\u003e content via \u003ctt\u003eAdaptUI\u003c/tt\u003e", + "tags": [ + "adapt-ui", + "util" + ] + }, { "javaClassName": "io.noties.adapt.sample.samples.showcase.AdaptUIShowcaseWeb", "id": "20230716140149", diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 136daee9..3e98e434 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -27,7 +27,11 @@ + android:exported="true" /> + + we send cancel to children/self and continue + // with dismiss + + val action = ev.action + val x = ev.rawX + val y = ev.rawY + + // TODO: bounce back + + if (MotionEvent.ACTION_DOWN == action) { + + // if scroll-child can scroll up, then no matter what is going + // to be next -> we are not going to dismiss + if (child.canScrollUp()) { + return fallback() + } + + // start new +// scroller.abortAnimation() + + touchStartX = x + touchStartY = y + + isTrackingTouch = true + isScrollEvent = false + + @Suppress("KotlinConstantConditions", "SimplifyBooleanWithConstants") + return super.dispatchTouchEvent(ev) || true + + } + + // if we already do not track it -> fallback + if (!isTrackingTouch) return fallback() + +// val detectorResult = detector.onTouchEvent(ev) + +// removeCallbacks(scrollRunnable) +// post(scrollRunnable) + + // here we also need to know if we already process the touch in logic or still evaluating it + if (action == MotionEvent.ACTION_MOVE) { + + // already detected + if (isScrollEvent) { + val startY = touchStartY ?: return fallback() + val value = y - startY + + // do not stop tracking touch scroll events, just limit the + // travel of the component + process(if (value < 0F) 0F else value) + + return true + } + + // if we have no required data -> fallback + val startX = touchStartX ?: return fallback() + val startY = touchStartY ?: return fallback() + + val diffX = abs(x - startX) + val diffY = y - startY + + // if horizontal scroll has greater value -> ignore next events + if (diffX > touchSlop) return fallback() + // scrolling to bottom + if (diffY < 0) return fallback() + + isScrollEvent = diffY >= touchSlop + + // after we have started tracking scroll event we also need to track + // fling gesture + if (isScrollEvent) { + // okay, if it is scroll event, then cancel to children event + // (they should not longer receive it) + val event = MotionEvent.obtain(ev) + try { + event.action = MotionEvent.ACTION_CANCEL + super.dispatchTouchEvent(event) + } finally { + event.recycle() + } + + return true + } + + } else if (action == MotionEvent.ACTION_CANCEL) { + return fallback() + } else if (action == MotionEvent.ACTION_UP) { + // eval current state + val isDismiss = translationY > (height / 2) + val target = if (isDismiss) height.toFloat() else 0F + clearAnimation() + animate() + .translationY(target) + .alpha(if (isDismiss) 0F else 1F) + .setDuration(250L) + .setInterpolator(DecelerateInterpolator()) + .start() + } + + // return super OR true if tracking touch here + // in case of `isScrollEvent` always return true (continue receiving it) + return result + } + + private fun scrollChild(): View? { + // take first child if present + return childCount + .takeIf { it > 0 } + ?.let { getChildAt(0) } + } + + private fun View.canScrollUp(): Boolean = canScrollVertically(-1) + + private fun process(scrollY: Float) { + Debug.i("scrollY:$scrollY") + translationY = scrollY + alpha = 1F - (scrollY / height) + } + +// private val scrollRunnable: Runnable = object: Runnable { +// override fun run() { +// if (scroller.computeScrollOffset()) { +// Debug.i("scroll currY:${scroller.currY}") +// process(scroller.currY.toFloat()) +// post(this) +// } +// } +// } + +// private class TouchDetector( +// view: ModalLayout, +// private val scroller: Scroller +// ) { +// fun onTouchEvent(ev: MotionEvent) = detector.onTouchEvent(ev) +// +// private val minFlingVelocity: Int +// private val touchSlop: Int +// +// init { +// val configuration = ViewConfiguration.get(view.context) +// minFlingVelocity = configuration.scaledMinimumFlingVelocity +// touchSlop = configuration.scaledTouchSlop +// } +// +// private val detector = GestureDetector(view.context, object: ExploreScrollSimpleListener() { +// +//// override fun onScroll( +//// e1: MotionEvent?, +//// e2: MotionEvent?, +//// distanceX: Float, +//// distanceY: Float +//// ): Boolean { +//// return false +//// } +// +// override fun onFling( +//// e1: MotionEvent, +//// e2: MotionEvent, +// velocityX: Float, +// velocityY: Float +// ): Boolean { +// +// // if small velocity or velocity x is greater than y +// if (abs(velocityY) < minFlingVelocity || abs(velocityX) > abs(velocityY)) { +// return false +// } +// +//// val height = view.height.takeIf { it > 0 } ?: return false +// +// val velocity = -velocityY.roundToInt() +// +// scroller.fling( +// 0, +// view.scrollY, +// 0, +// velocity, +// 0, +// 0, +// 0, +// Int.MAX_VALUE +// ) +// +// return scroller.computeScrollOffset().also { +// Debug.i("compute:$it final-y:${scroller.finalY}") +// } +// } +// }) +// } + } + + @Suppress("FunctionName") + fun ViewFactory.Modal( + children: ViewFactory.() -> Unit + ) = ElementGroup( + provider = { ModalLayout(it) }, + children = children + ) +} + +private class PreviewExploreScrollIfScrolls(context: Context, attrs: AttributeSet?) : + AdaptUIPreviewLayout(context, attrs) { + + lateinit var text: TextView + + override fun ViewFactory.body() { + Modal { + + VStackContentMeasuredLast { + + HStack { + Image(R.drawable.ic_arrow_back_ios_24_white) + .layout(56, 56) + }.layout(FILL, 56) + .backgroundColor { accent } + + ZStackWrapHeightOrScroll( + scrollStyle = { + it.scrollBarsEnabled(false) + } + ) { + Text("Content, click me to generate new") + .layout(FILL, WRAP) + // this one is going to be erased after wrap/unwrap + .layoutGravity { center.vertical } + .textSize { 17 } + .padding(top = 24, bottom = 36) + .padding(horizontal = 16) + .backgroundColor { accent.withAlphaComponent(0.2F) } + .reference(::text) + }.stackContentMeasureLast() + + Text("Click me!") + .layout(FILL, WRAP) + .textSize(17) + .textGravity { center.horizontal } + .padding(12) + .background { + RoundedRectangle(12) { + fill { accent } + } + } + .foregroundDefaultSelectable() + .clipToOutline() + .layoutMargin(16) + .onClick { + // from 0 to 100 + val count = (20 * Math.random()).roundToInt() + val base = + "This is the text to replicate, let it take some place, some amount of space we need to cover." + val result = (0 until count) + .joinToString("\n\n") { "[$it/$count] $base" } + TransitionManager.beginDelayedTransition(text.parent.parent as ViewGroup) + text.text = result + } + }.indent() + .layoutGravity { center.bottom } + .backgroundColor { accent.withAlphaComponent(0.3F) } + .layoutMargin(top = 128) + .layoutMargin(horizontal = 16) + .transitionGroup() + + }.layoutFill() + .overlayDrawBounds() + } +} \ No newline at end of file diff --git a/sample/src/main/java/io/noties/adapt/sample/explore/ExploreScrollIfScrolls.kt b/sample/src/main/java/io/noties/adapt/sample/explore/ExploreScrollIfScrolls.kt deleted file mode 100644 index f89c834b..00000000 --- a/sample/src/main/java/io/noties/adapt/sample/explore/ExploreScrollIfScrolls.kt +++ /dev/null @@ -1,451 +0,0 @@ -package io.noties.adapt.sample.explore - -import android.annotation.SuppressLint -import android.content.Context -import android.transition.TransitionManager -import android.util.AttributeSet -import android.view.View -import android.view.ViewGroup -import android.widget.FrameLayout -import android.widget.LinearLayout -import android.widget.ScrollView -import android.widget.TextView -import io.noties.adapt.sample.R -import io.noties.adapt.sample.explore.ExploreOverlayDrawBounds.overlayDrawBounds -import io.noties.adapt.sample.explore.ExploreScrollIfScrolls.VStackContentMeasuredLast -import io.noties.adapt.sample.explore.ExploreScrollIfScrolls.WrapContentOrScroll -import io.noties.adapt.sample.explore.ExploreScrollIfScrolls.stackContentMeasureLast -import io.noties.adapt.sample.ui.color.accent -import io.noties.adapt.sample.util.children -import io.noties.adapt.ui.LayoutParams -import io.noties.adapt.ui.ViewElement -import io.noties.adapt.ui.ViewFactory -import io.noties.adapt.ui.background -import io.noties.adapt.ui.backgroundColor -import io.noties.adapt.ui.clipToOutline -import io.noties.adapt.ui.element.Element -import io.noties.adapt.ui.element.ElementGroup -import io.noties.adapt.ui.element.HStack -import io.noties.adapt.ui.element.Image -import io.noties.adapt.ui.element.Text -import io.noties.adapt.ui.element.VScroll -import io.noties.adapt.ui.element.VStack -import io.noties.adapt.ui.element.ZStack -import io.noties.adapt.ui.element.textGravity -import io.noties.adapt.ui.element.textSize -import io.noties.adapt.ui.foregroundDefaultSelectable -import io.noties.adapt.ui.indent -import io.noties.adapt.ui.layout -import io.noties.adapt.ui.layoutFill -import io.noties.adapt.ui.layoutGravity -import io.noties.adapt.ui.layoutMargin -import io.noties.adapt.ui.onClick -import io.noties.adapt.ui.padding -import io.noties.adapt.ui.preview.AdaptUIPreviewLayout -import io.noties.adapt.ui.reference -import io.noties.adapt.ui.scrollBarsEnabled -import io.noties.adapt.ui.shape.RoundedRectangle -import io.noties.adapt.ui.transitionGroup -import io.noties.adapt.ui.util.withAlphaComponent -import io.noties.debug.Debug -import kotlin.math.roundToInt - -// actually, what we really want is to have a wrap-content view, -// but which might scroll if content exceeds available height. -// The question now is: how to determine available height? -// 1. what if we additionally measure parent to understand its maximum height? then how to fallback? -object ExploreScrollIfScrolls { - - @Suppress("FunctionName") - fun ViewFactory.WrapContentOrScroll( - scrollStyle: (ViewElement) -> Unit = {}, - child: ViewFactory.() -> ViewElement - ) = Element( - provider = { WrapHeightOrScroll(it, scrollStyle, child) } - ) - - @SuppressLint("ViewConstructor") - class WrapHeightOrScroll( - context: Context, - private val scrollStyle: (ViewElement) -> Unit, - child: ViewFactory.() -> ViewElement - ) : FrameLayout(context) { - - private val content: View - - private var callbacks: Runnable? = null - - init { - content = ViewFactory( - context, - this - ).let { child.invoke(it) } - .also { it.init(context) } - .also { - if (it.view.layoutParams == null) { - it.view.layoutParams = - LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) - } - } - .also { it.render() } - .also { - addView(it.view) - } - .view - } - - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - val width = MeasureSpec.getSize(widthMeasureSpec) - val height = MeasureSpec.getSize(heightMeasureSpec) - Debug.i("height:$height spec:${MeasureSpec.toString(heightMeasureSpec)}") - - val child = content - - // unspecified and without padding (yet)! - val childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) - child.measure( - MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), - childHeightSpec - ) - - val childMeasuredHeight = child.measuredHeight - - // TODO: do we need to adjust for padding in order to properly layout child? - val isOverflow = childMeasuredHeight > (height - paddingTop - paddingBottom) - Debug.i("child.measured:${childMeasuredHeight} isOverflow:$isOverflow") - - if (child.parent != this) { - // additionally measure wrapper content - getChildAt(0).measure( - MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY) - ) - } - - if (!isOverflow) { - setMeasuredDimension( - width, - childMeasuredHeight + paddingTop + paddingBottom - ) - - unwrapContentFromScrollView() - - } else { - setMeasuredDimension( - width, - height - ) - - wrapContentInScrollView(height) - } - } - - private fun wrapContentInScrollView(height: Int) { - removeWrapUnwrapCallbacks() - - Debug.i("parent.willWrap:${content.parent == this}") - - // cannot do anything until parent is present - val parent = content.parent as? ViewGroup ?: return - if (parent != this) { - // assume already wrapped - return - } - - postWrapUnwrapCallbacks { - // remove from this layout - removeAllViews() - - val view = ViewFactory.newView(this) - .layoutParams(LayoutParams(LayoutParams.MATCH_PARENT, height)) - .create { - VScroll { - VStack { - Element { content } - } - }.indent() - .also(scrollStyle) - } - - Debug.i("view:$view") - - addView(view) - } - } - - private fun unwrapContentFromScrollView() { - removeWrapUnwrapCallbacks() - - Debug.i("parent.willUnwrap:${content.parent != this}") - - val parent = content.parent as? ViewGroup ?: return - if (parent == this) { - // assume already unwrapped - return - } - - postWrapUnwrapCallbacks { - parent.removeAllViews() - - removeAllViews() - - addView(content) - } - } - - private fun removeWrapUnwrapCallbacks() { - callbacks?.also { removeCallbacks(it) } - } - - private fun postWrapUnwrapCallbacks(callbacks: Runnable) { - this.callbacks = callbacks - post(callbacks) - } - - override fun canScrollVertically(direction: Int): Boolean { - // if contains scroll-view -> pass to it, else false - val scrollView = getChildAt(0) as? ScrollView ?: return false - return scrollView.canScrollVertically(direction) - } - } - - @Suppress("FunctionName") - fun ViewFactory.VStackContentMeasuredLast( - children: ViewFactory.() -> Unit - ) = ElementGroup( - provider = { ContentHeightIsMeasuredLast(it) }, - children = children - ) - - fun ViewElement.stackContentMeasureLast() = - onLayoutParams { - it.isContent = true - } - - // no padding? - class ContentHeightIsMeasuredLast(context: Context) : LinearLayout(context) { - - init { - orientation = VERTICAL - } - - class LayoutParams : LinearLayout.LayoutParams { - var isContent: Boolean = false - - constructor(c: Context, attrs: AttributeSet?) : super(c, attrs) - constructor(width: Int, height: Int) : super(width, height) - constructor(width: Int, height: Int, weight: Float) : super(width, height, weight) - constructor(p: ViewGroup.LayoutParams) : super(p) - constructor(source: MarginLayoutParams) : super(source) - constructor(source: LinearLayout.LayoutParams) : super(source) - } - - private companion object { - val View.isContentView: Boolean get() { - return true == (this.layoutParams as? LayoutParams)?.isContent - } - } - - override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams { - return LayoutParams(context, attrs) - } - - override fun generateDefaultLayoutParams(): LayoutParams { - return LayoutParams( - io.noties.adapt.ui.LayoutParams.MATCH_PARENT, - io.noties.adapt.ui.LayoutParams.WRAP_CONTENT - ) - } - - override fun generateLayoutParams(lp: ViewGroup.LayoutParams): LayoutParams { - return LayoutParams(lp) - } - - override fun checkLayoutParams(p: ViewGroup.LayoutParams): Boolean { - return p is LayoutParams - } - - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - // if content is not found -> just super - - val (contentChildren, children) = children - .partition { it.isContentView } - - if (contentChildren.isEmpty()) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec) - } - -// val content = -// children.firstOrNull { true == (it.layoutParams as? LayoutParams)?.isContent } -// if (content == null) { -// super.onMeasure(widthMeasureSpec, heightMeasureSpec) -// return -// } - - val width = MeasureSpec.getSize(widthMeasureSpec) - val height = MeasureSpec.getSize(heightMeasureSpec) - - var availableHeight = height - paddingTop - paddingBottom - - fun measureChild(child: View) { - measureChild( - child, - childWidthMeasureSpec(width, child), - childHeightMeasureSpec( - availableHeight - child.heightLayoutMargins() - ) - ) - availableHeight -= (child.measuredHeight + child.heightLayoutMargins()) - } - - children.forEach { measureChild(it) } - contentChildren.forEach { measureChild(it) } - -// children -// .withIndex() -// .sortedWith(ChildrenContentComparator) -// .map { getChildAt(it.index) } -// .forEach { child -> -// measureChild( -// child, -// childWidthMeasureSpec(width, child), -// childHeightMeasureSpec( -// availableHeight - child.heightLayoutMargins() -// ) -// ) -// availableHeight -= (child.measuredHeight + child.heightLayoutMargins()) -// } - -// for (child in children) { -// if (true == (child.layoutParams as? LayoutParams)?.isContent) { -// continue -// } -// measureChild( -// child, -// childWidthMeasureSpec(width, child), -// childHeightMeasureSpec( -// availableHeight - child.heightLayoutMargins() -// ) -//// MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.AT_MOST) -// ) -//// child.measure( -//// MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), -//// MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.AT_MOST) -//// ) -// -// availableHeight -= (child.measuredHeight + child.heightLayoutMargins()) -// } - - // maybe just sort them and process in a single loop? -// content.measure( -// childWidthMeasureSpec(width, content), -// childHeightMeasureSpec( -// availableHeight - content.heightLayoutMargins() -// ) -// ) -// availableHeight -= (content.measuredHeight + content.heightLayoutMargins()) - - // okay here: - // do we just use maximum dimensions - // or do we check if wrap-content is requested and then use the height used by the children? - - setMeasuredDimension( - width, - height - availableHeight - ) - } - - private fun childWidthMeasureSpec(parentWidth: Int, child: View): Int { - return MeasureSpec.makeMeasureSpec( - (parentWidth - paddingLeft - paddingRight - child.widthLayoutMargins()), - MeasureSpec.EXACTLY - ) - } - - private fun childHeightMeasureSpec(height: Int): Int { - return MeasureSpec.makeMeasureSpec( - height, - MeasureSpec.EXACTLY - ) - } - - private fun View.widthLayoutMargins(): Int { - val lp = this.layoutParams as? LayoutParams - return if (lp != null) { - lp.leftMargin + lp.rightMargin - } else 0 - } - - private fun View.heightLayoutMargins(): Int { - val lp = this.layoutParams as? LayoutParams - return if (lp != null) { - lp.topMargin + lp.bottomMargin - } else 0 - } - } -} - -private class PreviewExploreScrollIfScrolls(context: Context, attrs: AttributeSet?) : - AdaptUIPreviewLayout(context, attrs) { - - lateinit var text: TextView - - override fun ViewFactory.body() { - ZStack { - VStackContentMeasuredLast { - - HStack { - Image(R.drawable.ic_arrow_back_ios_24_white) - .layout(56, 56) - }.layout(FILL, 56) - .backgroundColor { accent } - - WrapContentOrScroll( - scrollStyle = { - it.scrollBarsEnabled(false) - } - ) { - Text("Content, click me to generate new") - .layout(FILL, WRAP) - // this one is going to be erased after wrap/unwrap - .layoutGravity { center.vertical } - .textSize { 17 } - .padding(top = 24, bottom = 36) - .padding(horizontal = 16) - .backgroundColor { accent.withAlphaComponent(0.2F) } - .reference(::text) - }.stackContentMeasureLast() - - Text("Click me!") - .layout(FILL, WRAP) - .textSize(17) - .textGravity { center.horizontal } - .padding(12) - .background { - RoundedRectangle(12) { - fill { accent } - } - } - .foregroundDefaultSelectable() - .clipToOutline() - .layoutMargin(16) - .onClick { - // from 0 to 100 - val count = (20 * Math.random()).roundToInt() - val base = - "This is the text to replicate, let it take some place, some amount of space we need to cover." - val result = (0 until count) - .joinToString("\n\n") { "[$it/$count] $base" } - TransitionManager.beginDelayedTransition(text.parent.parent as ViewGroup) - text.text = result - } - }.indent() - .layoutGravity { center.bottom } - .backgroundColor { accent.withAlphaComponent(0.3F) } - .layoutMargin(top = 128) - .layoutMargin(horizontal = 16) - .transitionGroup() - }.layoutFill() - .overlayDrawBounds() - } -} \ No newline at end of file diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/ActivitySetContentUISample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/ActivitySetContentUISample.kt new file mode 100644 index 00000000..02000ade --- /dev/null +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/ActivitySetContentUISample.kt @@ -0,0 +1,61 @@ +package io.noties.adapt.sample.samples.adaptui + +import android.content.Context +import android.content.Intent +import android.util.AttributeSet +import io.noties.adapt.sample.annotation.AdaptSample +import io.noties.adapt.sample.ui.color.accent +import io.noties.adapt.sample.ui.color.white +import io.noties.adapt.ui.LayoutParams +import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.background +import io.noties.adapt.ui.clipToOutline +import io.noties.adapt.ui.element.Text +import io.noties.adapt.ui.element.ZStack +import io.noties.adapt.ui.element.textAllCaps +import io.noties.adapt.ui.element.textColor +import io.noties.adapt.ui.element.textSize +import io.noties.adapt.ui.foregroundDefaultSelectable +import io.noties.adapt.ui.layoutFill +import io.noties.adapt.ui.layoutGravity +import io.noties.adapt.ui.layoutMargin +import io.noties.adapt.ui.layoutWrap +import io.noties.adapt.ui.onClick +import io.noties.adapt.ui.padding +import io.noties.adapt.ui.shape.Capsule + +@AdaptSample( + id = "20240326215919", + title = "Activity.setContentUI", + description = "Helper function to set Activity's content via AdaptUI", + tags = ["adapt-ui", "util"] +) +class ActivitySetContentUISample : AdaptUISampleView() { + override fun ViewFactory.body() { + ZStack { + Text("Click me to open activity") + .layoutGravity { center } + .layoutWrap() + .layoutMargin(16) + .padding(horizontal = 12, vertical = 8) + .background { + Capsule { fill { accent } } + } + .textSize { 17 } + .textColor { white } + .textAllCaps() + .onClick { + val intent = Intent(context, ActivitySetContentUISampleActivity::class.java) + context.startActivity(intent) + } + .foregroundDefaultSelectable() + .clipToOutline() + }.layoutFill() + } +} + +private class PreviewActivitySetContentUISample(context: Context, attrs: AttributeSet?) : + AdaptUISamplePreview(context, attrs) { + override val sample: AdaptUISampleView + get() = ActivitySetContentUISample() +} \ No newline at end of file diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/ActivitySetContentUISampleActivity.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/ActivitySetContentUISampleActivity.kt new file mode 100644 index 00000000..a8a2abd7 --- /dev/null +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/ActivitySetContentUISampleActivity.kt @@ -0,0 +1,37 @@ +package io.noties.adapt.sample.samples.adaptui + +import android.app.Activity +import android.os.Bundle +import android.widget.TextView +import io.noties.adapt.sample.ui.color.black +import io.noties.adapt.sample.ui.color.white +import io.noties.adapt.ui.backgroundColor +import io.noties.adapt.ui.element.Text +import io.noties.adapt.ui.element.ZStack +import io.noties.adapt.ui.element.textColor +import io.noties.adapt.ui.element.textSize +import io.noties.adapt.ui.layoutGravity +import io.noties.adapt.ui.layoutWrap +import io.noties.adapt.ui.reference +import io.noties.adapt.ui.util.setContentUI + +class ActivitySetContentUISampleActivity : Activity() { + + lateinit var text: TextView + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContentUI { + // by default will FILL parent + ZStack { + Text("Hello here!") + .layoutWrap() + .reference(::text) + .textColor { white } + .textSize { 24 } + .layoutGravity { center } + }.backgroundColor { black } + } + } +} \ No newline at end of file diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIGradientSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIGradientSample.kt index bb55056f..3a63624f 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIGradientSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIGradientSample.kt @@ -22,6 +22,7 @@ import io.noties.adapt.ui.element.HStack import io.noties.adapt.ui.element.Text import io.noties.adapt.ui.element.VStack import io.noties.adapt.ui.element.View +import io.noties.adapt.ui.element.ZStackSquare import io.noties.adapt.ui.element.textColor import io.noties.adapt.ui.element.textSingleLine import io.noties.adapt.ui.element.textSize @@ -40,7 +41,6 @@ import io.noties.adapt.ui.shape.RoundedRectangle import io.noties.adapt.ui.shape.RoundedRectangleShape import io.noties.adapt.ui.shape.Shape import io.noties.adapt.ui.util.Gravity -import io.noties.adapt.ui.widget.ZStackSquare @AdaptSample( id = "20230517155636", diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIScrollIfScrollsSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIScrollIfScrollsSample.kt new file mode 100644 index 00000000..42943c69 --- /dev/null +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIScrollIfScrollsSample.kt @@ -0,0 +1,119 @@ +package io.noties.adapt.sample.samples.adaptui + +import android.content.Context +import android.transition.TransitionManager +import android.util.AttributeSet +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import io.noties.adapt.preview.Preview +import io.noties.adapt.sample.R +import io.noties.adapt.sample.SampleView +import io.noties.adapt.sample.annotation.AdaptSample +import io.noties.adapt.sample.ui.color.accent +import io.noties.adapt.sample.util.PreviewSampleView +import io.noties.adapt.ui.LayoutParams +import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.background +import io.noties.adapt.ui.backgroundColor +import io.noties.adapt.ui.clipToOutline +import io.noties.adapt.ui.element.HStack +import io.noties.adapt.ui.element.Image +import io.noties.adapt.ui.element.Text +import io.noties.adapt.ui.element.VStackContentMeasuredLast +import io.noties.adapt.ui.element.ZStack +import io.noties.adapt.ui.element.ZStackWrapHeightOrScroll +import io.noties.adapt.ui.element.imageScaleType +import io.noties.adapt.ui.element.stackContentMeasureLast +import io.noties.adapt.ui.element.textGravity +import io.noties.adapt.ui.element.textSize +import io.noties.adapt.ui.foregroundDefaultSelectable +import io.noties.adapt.ui.indent +import io.noties.adapt.ui.layout +import io.noties.adapt.ui.layoutFill +import io.noties.adapt.ui.layoutGravity +import io.noties.adapt.ui.layoutMargin +import io.noties.adapt.ui.onClick +import io.noties.adapt.ui.padding +import io.noties.adapt.ui.reference +import io.noties.adapt.ui.scrollBarsEnabled +import io.noties.adapt.ui.shape.RoundedRectangle +import io.noties.adapt.ui.transitionGroup +import io.noties.adapt.ui.util.withAlphaComponent +import kotlin.math.roundToInt + +@AdaptSample( + id = "20240327011222", + title = "AdaptUI. Scroll if content scrolls else wrap height", + tags = ["adapt-ui", "recipe"] +) +class AdaptUIScrollIfScrollsSample: AdaptUISampleView() { + + private lateinit var text: TextView + + override fun ViewFactory.body() { + ZStack { + + VStackContentMeasuredLast { + + HStack { + Image(R.drawable.ic_arrow_back_ios_24_white) + .layout(56, 56) + .imageScaleType(ImageView.ScaleType.CENTER_INSIDE) + }.layout(FILL, 56) + .backgroundColor { accent } + + ZStackWrapHeightOrScroll( + scrollStyle = { + it.scrollBarsEnabled(false) + } + ) { + Text("Content, click me to generate new") + .layout(FILL, WRAP) + // this one is going to be erased after wrap/unwrap + .layoutGravity { center.vertical } + .textSize { 17 } + .padding(top = 24, bottom = 36) + .padding(horizontal = 16) + .backgroundColor { accent.withAlphaComponent(0.2F) } + .reference(::text) + }.stackContentMeasureLast() + + Text("Click me!") + .layout(FILL, WRAP) + .textSize(17) + .textGravity { center.horizontal } + .padding(12) + .background { + RoundedRectangle(12) { + fill { accent } + } + } + .foregroundDefaultSelectable() + .clipToOutline() + .layoutMargin(16) + .onClick { + // from 0 to 100 + val count = (20 * Math.random()).roundToInt() + val base = + "This is the text to replicate, let it take some place, some amount of space we need to cover." + val result = (0 until count) + .joinToString("\n\n") { "[${it + 1}/$count] $base" } + TransitionManager.beginDelayedTransition(text.parent.parent as ViewGroup) + text.text = result + } + }.indent() + .layoutGravity { center } + .backgroundColor { accent.withAlphaComponent(0.3F) } + .layoutMargin(24) + + }.layoutFill() + } +} + +@Preview +private class PreviewAdaptUIScrollIfScrollsSample(context: Context, attrs: AttributeSet?) : + PreviewSampleView(context, attrs) { + override val sampleView: SampleView + get() = AdaptUIScrollIfScrollsSample() +} \ No newline at end of file diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIStickySample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIStickySample.kt index 497a0a7a..ea9f1feb 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIStickySample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIStickySample.kt @@ -18,6 +18,7 @@ import io.noties.adapt.ui.alpha import io.noties.adapt.ui.backgroundColor import io.noties.adapt.ui.element.Text import io.noties.adapt.ui.element.VScroll +import io.noties.adapt.ui.element.VStackReverseDrawingOrder import io.noties.adapt.ui.element.textColor import io.noties.adapt.ui.foregroundDefaultSelectable import io.noties.adapt.ui.ifAvailable @@ -27,7 +28,6 @@ import io.noties.adapt.ui.onElementView import io.noties.adapt.ui.padding import io.noties.adapt.ui.sticky.stickyVerticalScrollContainer import io.noties.adapt.ui.sticky.stickyView -import io.noties.adapt.ui.widget.VStackReverseDrawingOrder import io.noties.debug.Debug @AdaptSample( diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUITextInputTypeSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUITextInputTypeSample.kt new file mode 100644 index 00000000..955f52f4 --- /dev/null +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUITextInputTypeSample.kt @@ -0,0 +1,48 @@ +package io.noties.adapt.sample.samples.adaptui + +import android.content.Context +import android.util.AttributeSet +import io.noties.adapt.sample.annotation.AdaptSample +import io.noties.adapt.ui.LayoutParams +import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.element.TextInput +import io.noties.adapt.ui.element.VStack +import io.noties.adapt.ui.element.textHint +import io.noties.adapt.ui.element.textInputType +import io.noties.adapt.ui.layoutFill +import io.noties.adapt.ui.layoutMargin +import io.noties.adapt.ui.util.InputType + +@AdaptSample( + id = "20240326221901", + title = "Text.textInputType", + tags = ["adapt-ui"] +) +class AdaptUITextInputTypeSample : AdaptUISampleView() { + override fun ViewFactory.body() { + VStack { + + // specify normally + TextInput() + .textHint("Normally") + .layoutMargin(16) + .textInputType(InputType.text.password) + + // specify fluently + TextInput() + .textHint("Fluently") + .layoutMargin(16) + // no need to specify "InputType" + // (it is the receiver => `InputType.() -> InputType` + // hit `CONTROL + SPACE` (on mac) to show the list of possible values + .textInputType { text.password } + + }.layoutFill() + } +} + +private class PreviewAdaptUITextInputType(context: Context, attrs: AttributeSet?) : + AdaptUISamplePreview(context, attrs) { + override val sample: AdaptUISampleView + get() = AdaptUITextInputTypeSample() +} \ No newline at end of file diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUITextSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUITextSample.kt index ab8da5b2..cc2ee852 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUITextSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUITextSample.kt @@ -1,6 +1,7 @@ package io.noties.adapt.sample.samples.adaptui import android.content.Context +import android.graphics.Typeface import android.os.Build import android.text.TextUtils import android.util.AttributeSet @@ -34,17 +35,20 @@ import io.noties.adapt.ui.element.textAutoSize import io.noties.adapt.ui.element.textBreakStrategy import io.noties.adapt.ui.element.textColor import io.noties.adapt.ui.element.textEllipsize +import io.noties.adapt.ui.element.textFont import io.noties.adapt.ui.element.textGradient import io.noties.adapt.ui.element.textGravity import io.noties.adapt.ui.element.textHideIfEmpty import io.noties.adapt.ui.element.textHint import io.noties.adapt.ui.element.textHyphenationFrequency import io.noties.adapt.ui.element.textImeOptions +import io.noties.adapt.ui.element.textInputType import io.noties.adapt.ui.element.textMaxLines import io.noties.adapt.ui.element.textOnTextChanged import io.noties.adapt.ui.element.textSelectable import io.noties.adapt.ui.element.textShadow import io.noties.adapt.ui.element.textSize +import io.noties.adapt.ui.element.textTypeface import io.noties.adapt.ui.gradient.RadialGradient import io.noties.adapt.ui.ifAvailable import io.noties.adapt.ui.layoutFill @@ -54,7 +58,6 @@ import io.noties.adapt.ui.shape.RoundedRectangleShape import io.noties.adapt.ui.shape.StatefulShape import io.noties.adapt.ui.shape.copy import io.noties.adapt.ui.util.Gravity -import io.noties.adapt.ui.util.ImeOptions import io.noties.adapt.ui.util.InputType import io.noties.adapt.ui.util.hex import io.noties.debug.Debug @@ -89,21 +92,14 @@ class AdaptUITextSample : SampleView() { .textSize(16) .textColor { black } .textHint("Some phone!") -// .textInputType(ExploreEditorInfo.InputType.text.uri.noSuggestions.capWords) -// .textImeOptions(ExploreEditorInfo.ImeOptions.actionGo.noExactUi) - .textImeOptions(ImeOptions.actionSearch.noExactUi) { view, _ -> - val text = view.text.toString() - Debug.e("triggered action! view:'$text'") - true + .textImeOptions { + noExtractUi + .noFullScreen + .forceAscii + .actionSearch { + Debug.w("Triggered search action!") + } } -// .onView { -//// it.setImeActionLabel("WHAT", R.id.divider_overlay_drawable) -// it.setOnEditorActionListener { v, actionId, event -> -// val eq = ImeOptions(actionId) == ImeOptions.actionSearch -// Debug.i("actionId=${actionId}, event=${event} eq:$eq") -// true -// } -// } .padding(horizontal = 16, vertical = 12) .layoutMargin(horizontal = 16, vertical = 8) .background(StatefulShape.drawable { @@ -153,7 +149,11 @@ class AdaptUITextSample : SampleView() { .textHideIfEmpty() .textAllCaps() .textEllipsize(TextUtils.TruncateAt.END) + .textTypeface(Typeface.DEFAULT) { bold.italic } .textGravity(Gravity.center) + .textGravity { center } + .textInputType { text.password.multiline.autoComplete } + .textImeOptions { noExtractUi.noFullScreen.actionSearch { /*action triggered*/ } } .textSelectable() // .textSingleLine(true) .textMaxLines(1) diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIWebSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIWebSample.kt index 22405979..914d602f 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIWebSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUIWebSample.kt @@ -20,9 +20,13 @@ import io.noties.adapt.ui.ViewFactory import io.noties.adapt.ui.backgroundColor import io.noties.adapt.ui.element.Progress import io.noties.adapt.ui.element.Text +import io.noties.adapt.ui.element.Web import io.noties.adapt.ui.element.ZStack import io.noties.adapt.ui.element.textColor import io.noties.adapt.ui.element.textSize +import io.noties.adapt.ui.element.webChromeClient +import io.noties.adapt.ui.element.webClient +import io.noties.adapt.ui.element.webOnElementReady import io.noties.adapt.ui.layout import io.noties.adapt.ui.layoutFill import io.noties.adapt.ui.layoutGravity @@ -30,11 +34,7 @@ import io.noties.adapt.ui.layoutWrap import io.noties.adapt.ui.padding import io.noties.adapt.ui.util.Gravity import io.noties.adapt.ui.visible -import io.noties.adapt.ui.widget.Web import io.noties.adapt.ui.widget.WebViewPlaceholder -import io.noties.adapt.ui.widget.webChromeClient -import io.noties.adapt.ui.widget.webClient -import io.noties.adapt.ui.widget.webOnElementReady import io.noties.debug.Debug @AdaptSample( diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUiTextInputImeOptionsSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUiTextInputImeOptionsSample.kt new file mode 100644 index 00000000..fa9f0b70 --- /dev/null +++ b/sample/src/main/java/io/noties/adapt/sample/samples/adaptui/AdaptUiTextInputImeOptionsSample.kt @@ -0,0 +1,79 @@ +package io.noties.adapt.sample.samples.adaptui + +import android.content.Context +import android.util.AttributeSet +import io.noties.adapt.sample.annotation.AdaptSample +import io.noties.adapt.ui.LayoutParams +import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.element.Text +import io.noties.adapt.ui.element.TextInput +import io.noties.adapt.ui.element.VScroll +import io.noties.adapt.ui.element.VStack +import io.noties.adapt.ui.element.textColor +import io.noties.adapt.ui.element.textImeOptions +import io.noties.adapt.ui.element.textSingleLine +import io.noties.adapt.ui.element.textSize +import io.noties.adapt.ui.layoutFill +import io.noties.adapt.ui.util.ImeOptions +import io.noties.adapt.ui.util.ImeOptionsBuilder +import io.noties.debug.Debug + +@AdaptSample( + id = "20240331120232", + title = "Text.textImeOptions", + tags = ["adapt-ui"] +) +class AdaptUiTextInputImeOptionsSample: AdaptUISampleView() { + override fun ViewFactory.body() { + VScroll { + VStack { + + Ime { + actionSearch { Debug.i("search") } + } + + Ime { + actionGo { Debug.i("go") } + } + + Ime { + navigatePrevious.actionPrevious { Debug.i("previous") } + } + + Ime { + navigateNext.actionNext { Debug.i("next") } + } + + Ime { + actionSend { Debug.i("send") } + } + + Ime { + actionDone { Debug.i("done") } + } + + Ime { + noFullScreen.noExtractUi.actionUnspecified + } + + } + }.layoutFill() + } + + @Suppress("FunctionName") + private fun ViewFactory.Ime(builder: ImeOptionsBuilder) = VStack { + val ime = builder(ImeOptions) + Text(ime.toString()) + .textSize { 15 } + .textColor { hex("#ccc") } + TextInput() + .textImeOptions { ime } + .textSingleLine(true) + } +} + +private class PreviewAdaptUiTextInputImeOptionsSample(context: Context, attrs: AttributeSet?) : + AdaptUISamplePreview(context, attrs) { + override val sample: AdaptUISampleView + get() = AdaptUiTextInputImeOptionsSample() +} \ No newline at end of file diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/flex/AdaptUIFlexInteractiveSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/flex/AdaptUIFlexInteractiveSample.kt index 74a49527..e85102db 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/flex/AdaptUIFlexInteractiveSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/flex/AdaptUIFlexInteractiveSample.kt @@ -52,7 +52,6 @@ import io.noties.adapt.ui.element.ZStack import io.noties.adapt.ui.element.text import io.noties.adapt.ui.element.textBold import io.noties.adapt.ui.element.textColor -import io.noties.adapt.ui.element.textFont import io.noties.adapt.ui.element.textGravity import io.noties.adapt.ui.element.textSize import io.noties.adapt.ui.element.textTypeface diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseWeb.kt b/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseWeb.kt index 247436a8..dbbe771a 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseWeb.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/showcase/AdaptUIShowcaseWeb.kt @@ -8,13 +8,19 @@ import io.noties.adapt.sample.SampleView import io.noties.adapt.sample.annotation.AdaptSample import io.noties.adapt.sample.samples.adaptui.AdaptUISampleView import io.noties.adapt.sample.ui.color.black -import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.sample.util.Preview import io.noties.adapt.sample.util.PreviewSampleView import io.noties.adapt.ui.LayoutParams import io.noties.adapt.ui.ViewFactory +import io.noties.adapt.ui.app.color.Colors import io.noties.adapt.ui.element.Progress import io.noties.adapt.ui.element.VStack +import io.noties.adapt.ui.element.Web +import io.noties.adapt.ui.element.webChromeClient +import io.noties.adapt.ui.element.webClient +import io.noties.adapt.ui.element.webLoad +import io.noties.adapt.ui.element.webOnElementReady +import io.noties.adapt.ui.element.webSettings import io.noties.adapt.ui.layout import io.noties.adapt.ui.layoutFill import io.noties.adapt.ui.layoutGravity @@ -22,12 +28,6 @@ import io.noties.adapt.ui.overlay import io.noties.adapt.ui.shape.Circle import io.noties.adapt.ui.util.Gravity import io.noties.adapt.ui.util.withAlphaComponent -import io.noties.adapt.ui.widget.Web -import io.noties.adapt.ui.widget.webChromeClient -import io.noties.adapt.ui.widget.webClient -import io.noties.adapt.ui.widget.webLoad -import io.noties.adapt.ui.widget.webOnElementReady -import io.noties.adapt.ui.widget.webSettings @AdaptSample( id = "20230716140149", diff --git a/sample/src/main/java/io/noties/adapt/sample/samples/viewpager/ViewPagerSample.kt b/sample/src/main/java/io/noties/adapt/sample/samples/viewpager/ViewPagerSample.kt index 8f52c726..789004b0 100644 --- a/sample/src/main/java/io/noties/adapt/sample/samples/viewpager/ViewPagerSample.kt +++ b/sample/src/main/java/io/noties/adapt/sample/samples/viewpager/ViewPagerSample.kt @@ -1,7 +1,6 @@ package io.noties.adapt.sample.samples.viewpager import android.graphics.Color -import android.graphics.Typeface import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver @@ -16,6 +15,7 @@ import io.noties.adapt.ui.ViewFactory import io.noties.adapt.ui.adaptViewPager import io.noties.adapt.ui.background import io.noties.adapt.ui.clipToPadding +import io.noties.adapt.ui.element.AdaptPagerWrapContent import io.noties.adapt.ui.element.Element import io.noties.adapt.ui.element.HStack import io.noties.adapt.ui.element.Text @@ -25,7 +25,6 @@ import io.noties.adapt.ui.element.ViewPagerOnPageChangeListener import io.noties.adapt.ui.element.pagerOnPageChangedListener import io.noties.adapt.ui.element.textBold import io.noties.adapt.ui.element.textColor -import io.noties.adapt.ui.element.textFont import io.noties.adapt.ui.element.textGravity import io.noties.adapt.ui.element.textSize import io.noties.adapt.ui.elevation @@ -40,7 +39,6 @@ import io.noties.adapt.ui.shape.CornersShape import io.noties.adapt.ui.shape.RoundedRectangleShape import io.noties.adapt.ui.shape.StatefulShape import io.noties.adapt.ui.util.Gravity -import io.noties.adapt.ui.widget.AdaptPagerWrapContent import io.noties.adapt.viewgroup.TransitionChangeHandler import io.noties.debug.Debug import kotlin.math.roundToInt From 5b93416ba006cfc76a8f3c605b74d4c931128121 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Mon, 1 Apr 2024 02:51:50 +0200 Subject: [PATCH 08/61] Process value=rawValue --- .run/sample.run.xml | 14 +++-- .../noties/adapt/ui/ViewElement+Extensions.kt | 2 +- .../ui/ViewElement+ExtensionsAccessibility.kt | 30 +++++++++-- .../io/noties/adapt/ui/ViewElement+Layout.kt | 4 +- .../java/io/noties/adapt/ui/element/HStack.kt | 2 +- .../java/io/noties/adapt/ui/element/Image.kt | 1 - .../java/io/noties/adapt/ui/element/Pager.kt | 2 +- .../io/noties/adapt/ui/element/StackCommon.kt | 2 +- .../java/io/noties/adapt/ui/element/Text.kt | 54 +++++++++++++++---- .../io/noties/adapt/ui/element/TextInput.kt | 2 +- .../java/io/noties/adapt/ui/element/VStack.kt | 2 +- .../ui/element/VStackReverseDrawingOrder.kt | 2 +- .../io/noties/adapt/ui/shape/CircleShape.kt | 2 +- .../io/noties/adapt/ui/shape/LabelShape.kt | 2 +- .../java/io/noties/adapt/ui/shape/Shape.kt | 2 +- .../io/noties/adapt/ui/shape/StatefulShape.kt | 2 +- .../io/noties/adapt/ui/shape/TextShape.kt | 9 ++-- .../adapt/ui/util/ColorStateListBuilder.kt | 2 +- .../io/noties/adapt/ui/util/DrawableState.kt | 14 +++-- .../java/io/noties/adapt/ui/util/Gravity.kt | 34 +++++++----- .../java/io/noties/adapt/ui/util/InputType.kt | 46 ++++++++-------- .../java/io/noties/adapt/ui/util/ItemUtil.kt | 9 +++- .../io/noties/adapt/ui/util/TypefaceStyle.kt | 14 +++-- .../widget/FrameLayoutWrapHeightOrScroll.kt | 42 ++++++++++----- .../widget/LinearLayoutContentMeasuredLast.kt | 3 +- ...iewElement_ExtensionsAccessibility_Test.kt | 6 +-- .../ui/ViewElement_ExtensionsCast_Test.kt | 4 +- .../adapt/ui/ViewElement_Extensions_Test.kt | 4 +- .../adapt/ui/ViewElement_Layout_Test.kt | 4 +- .../io/noties/adapt/ui/element/HStack_Test.kt | 4 +- .../io/noties/adapt/ui/element/Pager_Test.kt | 2 +- .../noties/adapt/ui/element/TextInput_Test.kt | 2 +- .../io/noties/adapt/ui/element/Text_Test.kt | 18 +++---- .../io/noties/adapt/ui/element/VStack_Test.kt | 2 +- .../adapt/ui/shape/ShapeDrawable_Test.kt | 6 +-- .../adapt/ui/shape/StatefulShape_Test.kt | 4 +- .../ui/util/ColorStateListBuilder_Test.kt | 6 +-- .../adapt/ui/util/DrawableState_Test.kt | 10 ++-- .../io/noties/adapt/ui/util/Gravity_Test.kt | 8 +-- .../io/noties/adapt/ui/util/InputType_Test.kt | 2 +- .../io/noties/adapt/ui/util/ItemUtil_Test.kt | 32 ++++++++++- .../adapt/ui/util/TypefaceStyle_Test.kt | 2 +- .../adapt/sample/explore/ExploreRelative.kt | 14 ++--- 43 files changed, 280 insertions(+), 148 deletions(-) diff --git a/.run/sample.run.xml b/.run/sample.run.xml index a59193bc..c074b187 100644 --- a/.run/sample.run.xml +++ b/.run/sample.run.xml @@ -10,7 +10,7 @@