+
+
\ No newline at end of file
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/.gitignore b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/build.gradle b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/build.gradle
new file mode 100644
index 00000000..2ea51bff
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/build.gradle
@@ -0,0 +1,50 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+}
+
+android {
+ namespace 'io.github.triniwiz.splitsheetdemo'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "io.github.triniwiz.splitsheetdemo"
+ minSdk 17
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.5.1'
+ implementation 'com.google.android.material:material:1.7.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ implementation project(path: ':splitsheet')
+
+
+ def dynamicanimation_version = "1.0.0"
+ implementation("androidx.dynamicanimation:dynamicanimation:$dynamicanimation_version")
+
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.4'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
+}
\ No newline at end of file
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/proguard-rules.pro b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/androidTest/java/io/github/triniwiz/splitsheetdemo/ExampleInstrumentedTest.kt b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/androidTest/java/io/github/triniwiz/splitsheetdemo/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..0ffdd241
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/androidTest/java/io/github/triniwiz/splitsheetdemo/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package io.github.triniwiz.splitsheetdemo
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("io.github.triniwiz.splitsheetdemo", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/AndroidManifest.xml b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..d89d0197
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/AndroidManifest.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/java/io/github/triniwiz/splitsheetdemo/MainActivity.kt b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/java/io/github/triniwiz/splitsheetdemo/MainActivity.kt
new file mode 100644
index 00000000..2e6a2471
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/java/io/github/triniwiz/splitsheetdemo/MainActivity.kt
@@ -0,0 +1,72 @@
+package io.github.triniwiz.splitsheetdemo
+
+
+import android.graphics.BitmapFactory
+import android.graphics.Color
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.util.Log
+import android.view.View
+import android.widget.ImageView
+import java.net.URL
+import java.util.concurrent.Executors
+import io.github.triniwiz.splitsheet.SplitSheet
+
+class MainActivity : AppCompatActivity() {
+ lateinit var imageView: ImageView
+ lateinit var splitsheet: SplitSheet
+ private val executor = Executors.newSingleThreadExecutor()
+ private val handler = Handler(Looper.getMainLooper())
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+
+ super.onCreate(savedInstanceState)
+ val metrics = resources.displayMetrics
+ setContentView(R.layout.activity_main)
+ imageView = findViewById(R.id.imageView)
+ splitsheet = findViewById(R.id.splitSheet)
+ // splitsheet.mainViewBackgroundColor = Color.TRANSPARENT
+ splitsheet.mainViewElevation = false
+ splitsheet.displaceContent = false
+ splitsheet.showHandle = false
+// splitsheet.closedSheetHeight = 50F
+// splitsheet.minimumSheetHeight = 200F
+
+
+ splitsheet.eventListener = object : SplitSheet.Events {
+ override fun event(name: String, value: Any?) {
+ Log.d("MainActivity", "event: $name $value")
+ }
+ }
+// executor.execute {
+// try {
+// val url =
+// URL("https://picsum.photos/${metrics.widthPixels}/${metrics.heightPixels}")
+// val bm = BitmapFactory.decodeStream(url.openStream())
+// runOnUiThread {
+// imageView.setImageBitmap(bm)
+// }
+// } catch (e: Exception) {
+// e.printStackTrace()
+// }
+// }
+ }
+
+ fun toggleContent(view: View) {
+ splitsheet.show(!splitsheet.showing)
+ }
+
+ fun toggleCollapse(view: View) {
+ splitsheet.displaceContent = !splitsheet.displaceContent
+ }
+
+ fun toggleHandle(view: View) {
+ splitsheet.showHandle = !splitsheet.showHandle
+ }
+
+ fun toggleScroll(view: View) {
+ splitsheet.isScrollEnabled = !splitsheet.isScrollEnabled
+ }
+}
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/java/io/github/triniwiz/splitsheetdemo/SplitSheetOld.kt b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/java/io/github/triniwiz/splitsheetdemo/SplitSheetOld.kt
new file mode 100644
index 00000000..cf2018e3
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/java/io/github/triniwiz/splitsheetdemo/SplitSheetOld.kt
@@ -0,0 +1,766 @@
+package io.github.triniwiz.splitsheetdemo
+
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.GradientDrawable
+import android.util.AttributeSet
+import android.util.SparseArray
+import android.util.TypedValue
+import android.view.*
+import android.view.ViewGroup.LayoutParams
+import android.view.animation.AccelerateDecelerateInterpolator
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.animation.doOnEnd
+import androidx.core.widget.NestedScrollView
+import kotlin.math.abs
+import kotlin.math.roundToInt
+import kotlin.math.roundToLong
+
+class SplitSheetOld : NestedScrollView {
+
+ constructor(context: Context) : super(context)
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
+
+ constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(
+ context,
+ attrs,
+ defStyle
+ )
+
+ var mainView: View? = null
+ var sheetView: View? = null
+
+ /// If true, `mainViewController` will shift up as the sheet is shown.
+ var displaceContent = true
+
+ /// Show a grabber handle.
+ var showHandle = true
+
+ /// The minimum sheet height.
+ var minimumSheetHeight = 400F
+
+ /// Enforce a sheet height which always shows when in "not showing" state
+ var closedSheetHeight = 90F
+
+ /// When the sheet is shown and dragged within this limit, the sheet will bounce back.
+ var snappingDistance = 150F
+
+ /// How long the show/hide animation takes.
+ var animationDuration = 0.4F
+
+ /// If swiping up to show the sheet is allowed or not.
+ var swipeUpToShowAllowed = true
+
+
+ /// The current state of the sheet. `true` if shown, `false` if hidden.
+ /// Observable with Combine.
+ var showing = false
+
+ var isScrollEnabled = true
+ set(value) {
+ containerView.requestDisallowInterceptTouchEvent(!value)
+ field = value
+ }
+
+ private val containerView = ConstraintLayout(context)
+
+ private val mainContainerView = LinearLayout(context)
+
+ private val sheetContainerView = LinearLayout(context)
+
+ private var handleView: View? = null
+
+ interface Events {
+ fun event(name: String, value: Any?)
+ }
+
+ var eventListener: Events? = null
+
+ val metrics = resources.displayMetrics
+
+ private fun toPx(dip: Float): Float {
+ return dip * metrics.density
+ }
+
+ private fun toPx(dip: Int): Int {
+ return (dip * metrics.density).roundToInt()
+ }
+
+ private fun toDip(px: Float): Float {
+ return px / metrics.density
+ }
+
+ private fun getScrollableLength(): Int {
+ return containerView.height
+ }
+
+ private val detents =
+ arrayOf(Pair(0F, Detents.Hidden), Pair(0F, Detents.Hidden), Pair(0F, Detents.Hidden))
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ velocityTracker = VelocityTracker.obtain()
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ velocityTracker?.clear()
+ }
+
+ private enum class Detents {
+ Hidden,
+ Shown,
+ Expanded
+ }
+
+ private val interpolator = AccelerateDecelerateInterpolator()
+
+ private var startX = 0F
+
+ private var startY = 0F
+
+ private var lastDeltaX = 0
+
+ private var lastDeltaY = 0
+
+ private var lastScrollX = 0
+
+ private var lastScrollY = 0
+
+ private var viewConfiguration = ViewConfiguration.get(context)
+
+ private var velocityTracker: VelocityTracker? = null
+
+ private var minFlingVelocity = 0
+
+ private var state = Detents.Hidden
+
+ private fun setState(state: Detents) {
+ this.state = state
+ }
+
+ init {
+ minFlingVelocity = viewConfiguration.scaledMinimumFlingVelocity
+
+ mainContainerView.orientation = LinearLayout.VERTICAL
+ sheetContainerView.orientation = LinearLayout.VERTICAL
+
+ containerView.id = View.generateViewId()
+ mainContainerView.id = View.generateViewId()
+ sheetContainerView.id = View.generateViewId()
+ }
+
+ fun setup(
+ mainView: View,
+ sheetView: View
+ ) {
+ this.mainView = mainView
+ this.sheetView = sheetView
+ setup()
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+
+ val height = MeasureSpec.makeMeasureSpec(heightMeasureSpec, MeasureSpec.EXACTLY)
+
+ val params = mainContainerView.layoutParams
+ params.width =
+ MeasureSpec.makeMeasureSpec(widthMeasureSpec, MeasureSpec.EXACTLY)
+ params.height = (height - toPx(closedSheetHeight)).toInt()
+
+ sheetContainerView.minimumHeight = toPx(minimumSheetHeight).roundToInt()
+ }
+
+ private fun setup() {
+ updateShowing(false)
+
+ mainContainerView.layoutParams = ConstraintLayout.LayoutParams(-1, 0)
+ .apply {
+ topToTop = ConstraintLayout.LayoutParams.PARENT_ID
+ leftToLeft = ConstraintLayout.LayoutParams.PARENT_ID
+ rightToRight = ConstraintLayout.LayoutParams.PARENT_ID
+ bottomToTop = sheetContainerView.id
+ }
+
+ mainView?.let {
+ mainContainerView.addView(mainView)
+ }
+
+ containerView.addView(mainContainerView)
+
+ sheetContainerView.layoutParams = ConstraintLayout.LayoutParams(-1, 0)
+ .apply {
+ // topMargin = -toPx(closedSheetHeight).roundToInt()
+ leftToLeft = ConstraintLayout.LayoutParams.PARENT_ID
+ rightToRight = ConstraintLayout.LayoutParams.PARENT_ID
+ topToBottom = mainContainerView.id
+ }
+
+ sheetView?.let {
+ sheetContainerView.addView(it, -1, -2)
+ }
+
+ containerView.addView(sheetContainerView)
+
+ addView(containerView, -1, -2)
+
+ isVerticalScrollBarEnabled = false
+ overScrollMode = OVER_SCROLL_ALWAYS
+ }
+
+ enum class Position(internal val value: Int) {
+ Undefined(-1),
+ Top(0),
+ Bottom(1);
+
+ companion object {
+ internal fun fromInt(value: Int): Position? {
+ return when (value) {
+ -1 -> Undefined
+ 0 -> Top
+ 1 -> Bottom
+ else -> null
+ }
+ }
+
+ internal fun fromIntOrDefault(value: Int?, def: Position): Position {
+ return fromInt(value ?: -2) ?: def
+ }
+ }
+ }
+
+ override fun addView(child: View, index: Int, params: ViewGroup.LayoutParams) {
+
+ if (child == containerView) {
+ super.addView(child, index, params)
+ return
+ }
+
+ val lp = child.layoutParams as? LayoutParams ?: run {
+ LayoutParams(child.layoutParams ?: generateDefaultLayoutParams())
+ }
+
+ val position = lp.position
+
+ if (mainView == null && position == Position.Undefined) {
+ mainView = child
+ return
+ }
+
+ if (sheetView == null && position == Position.Undefined) {
+ sheetView = child
+ setup()
+ return
+ }
+
+ }
+
+ override fun generateLayoutParams(attrs: AttributeSet): FrameLayout.LayoutParams {
+ return LayoutParams(context, attrs)
+ }
+
+ override fun generateDefaultLayoutParams(): FrameLayout.LayoutParams {
+ return LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
+ )
+ }
+
+ override fun generateLayoutParams(p: ViewGroup.LayoutParams?): ViewGroup.LayoutParams {
+ return LayoutParams(p)
+ }
+
+ override fun checkLayoutParams(p: ViewGroup.LayoutParams?): Boolean {
+ return p is LayoutParams
+ }
+
+ companion object {
+
+ lateinit var context: Context
+
+ private fun getResourceId(context: Context, name: String, type: String): Int {
+ val id: Int
+ val packageName = context.packageName
+ id = try {
+ val className = Class.forName("$packageName.R$$type")
+ className.getDeclaredField(name.replace("res://", ""))[null] as Int
+ } catch (e: ClassNotFoundException) {
+ 0
+ } catch (e: NoSuchFieldException) {
+ 0
+ } catch (e: IllegalAccessException) {
+ 0
+ }
+ return id
+ }
+
+ private fun getAppContext(): Context {
+ return context
+ }
+ }
+
+ class LayoutParams : FrameLayout.LayoutParams {
+ internal var numericAttributes = SparseArray()
+ internal var stringAttributes = SparseArray()
+
+
+ constructor(width: Int, height: Int) : super(width, height) {
+
+ val id = getResourceId(getAppContext(), "SplitSheet_Layout_sheet_position", "styleable")
+ numericAttributes.append(id, -1F)
+ }
+
+ constructor(source: ViewGroup.LayoutParams) : super(source) {
+ if (source is LayoutParams) {
+ numericAttributes = source.numericAttributes.clone()
+ stringAttributes = source.stringAttributes.clone()
+ } else {
+ val id =
+ getResourceId(getAppContext(), "SplitSheet_Layout_sheet_position", "styleable")
+
+ numericAttributes.append(id, -1F)
+ }
+ }
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
+ try {
+ val packageName = context.packageName
+ val className = Class.forName("$packageName.R\$styleable")
+
+ val attrId = className.getDeclaredField("SplitSheet_Layout").get(null) as IntArray
+ val id =
+ getResourceId(getAppContext(), "SplitSheet_Layout_sheet_position", "styleable")
+
+ val a = context.obtainStyledAttributes(attrs, attrId)
+
+ numericAttributes.append(id, -1F)
+
+ val attributeCount = a.indexCount
+ for (i in 0 until attributeCount) {
+ val attribute = a.getIndex(i)
+ val typedValue = TypedValue()
+ a.getValue(attribute, typedValue)
+ when (typedValue.type) {
+ TypedValue.TYPE_DIMENSION -> {
+ numericAttributes.put(
+ attribute, a.getDimensionPixelSize(attribute, 0).toFloat()
+ )
+ }
+ TypedValue.TYPE_STRING -> {
+ stringAttributes.put(attribute, a.getString(attribute))
+ }
+ else -> {
+ numericAttributes.put(attribute, a.getFloat(attribute, 0f))
+ }
+ }
+ }
+
+ a.recycle()
+
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+
+ var position: Position
+ get() {
+ val id =
+ getResourceId(getAppContext(), "SplitSheet_Layout_sheet_position", "styleable")
+ return Position.fromIntOrDefault(
+ numericAttributes.get(id)
+ .roundToInt(),
+ Position.Undefined
+ )
+ }
+ set(value) {
+ val id =
+ getResourceId(getAppContext(), "SplitSheet_Layout_sheet_position", "styleable")
+ numericAttributes.put(
+ id,
+ value.value.toFloat()
+ )
+ }
+ }
+
+ private var mScrollable: Boolean = true
+
+ private var handleTouch = false
+
+ private var isDragging = false
+
+ private var didDrag = false
+
+ private var lastEvent = MotionEvent.ACTION_UP
+
+ override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
+ return when (ev.action) {
+ MotionEvent.ACTION_DOWN -> {
+
+ velocityTracker?.clear()
+
+ startX = ev.rawX
+ startY = ev.rawY
+
+ lastScrollX = this.scrollX
+ lastScrollY = this.scrollY
+
+
+ velocityTracker?.addMovement(ev)
+
+ lastEvent = MotionEvent.ACTION_DOWN
+
+ super.dispatchTouchEvent(ev)
+ }
+ MotionEvent.ACTION_MOVE -> {
+ val currentX = ev.rawX
+ val currentY = ev.rawY
+ lastDeltaX = (startX - currentX).toInt()
+ lastDeltaY = (startY - currentY).toInt()
+
+
+
+ if (lastEvent == MotionEvent.ACTION_DOWN && (lastDeltaY > 5 || lastDeltaY < -5)) {
+ lastEvent = MotionEvent.ACTION_MOVE
+ isDragging = true
+ eventListener?.event("beginDrag", null)
+ }
+
+ if (isDragging) {
+ velocityTracker?.addMovement(ev)
+ velocityTracker?.computeCurrentVelocity(1000)
+ }
+
+ super.dispatchTouchEvent(ev)
+ }
+ MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
+
+ if (!isScrollEnabled) {
+ return true
+ }
+
+ val velocity = velocityTracker?.yVelocity ?: 0F
+
+ handleTouch = handleScroll(velocity, scrollY) || super.dispatchTouchEvent(ev)
+
+ if (lastEvent == MotionEvent.ACTION_MOVE) {
+ isDragging = false
+ didDrag = true
+ }
+
+ lastEvent = ev.action
+
+ handleTouch
+ }
+ else -> super.dispatchTouchEvent(ev)
+ }
+ }
+
+ private fun updateShowing(showing: Boolean) {
+ this.showing = showing
+
+ eventListener?.event("showing", showing)
+
+ /// If `swipeUpToShowAllowed` is not enabled, prevent scrolling up when hidden.
+ if (!swipeUpToShowAllowed) {
+ mScrollable = showing
+ }
+
+ if (showHandle) {
+ addHandle()
+ } else {
+ handleView?.let {
+ removeView(it)
+ }
+ }
+ }
+
+ private fun addHandle() {
+ if (handleView != null) {
+ return
+ }
+
+ val handleContainer = LinearLayout(context)
+ handleContainer.orientation = LinearLayout.VERTICAL
+
+ val handleView = View(context)
+
+ val background = GradientDrawable()
+ background.setColor(Color.GRAY)
+ background.cornerRadius = toPx(2.5F)
+
+ handleView.background = background
+
+
+ handleContainer.addView(handleView, LinearLayout.LayoutParams(toPx(36), toPx(5)).apply {
+ gravity = Gravity.CENTER_HORIZONTAL
+ })
+
+ this.handleView = handleContainer
+
+ sheetContainerView.addView(handleContainer, 0, LinearLayout.LayoutParams(-1, toPx(5)))
+ }
+
+ private var animation: AnimatorSet? = null
+
+ private fun handleScroll(velocity: Float, scrollY: Int): Boolean {
+
+ val didFling = abs(velocity) > abs(minFlingVelocity)
+
+ val minimumSheetHeight = toPx(minimumSheetHeight)
+ val closedSheetHeight = toPx(closedSheetHeight)
+
+ /// The distance to the just-shown detent (shown, but not fully expanded).
+ val distanceToShown = minimumSheetHeight - scrollY
+
+ /// The content offset where the sheet is hidden.
+ val hiddenDetent = closedSheetHeight
+
+ /// The content offset where the sheet is *just* shown.
+ val shownDetent = minimumSheetHeight
+
+ /// The content offset where the scroll view hits the bottom.
+
+ val expandedDetent = (getScrollableLength() - height).toFloat()
+
+ detents[0] = Pair(hiddenDetent, Detents.Hidden)
+ detents[1] = Pair(shownDetent, Detents.Shown)
+ detents[2] = Pair(expandedDetent, Detents.Expanded)
+
+ if (didFling && velocity < 0) {
+
+ /// Only select smaller detents (prevents a flickering glitch.)
+ val availableDetents = detents.filter {
+ it.first < scrollY
+ }
+
+ var closestDetent = availableDetents.firstOrNull()
+
+ closestDetent?.let { closest ->
+ availableDetents.forEach {
+ if (abs(it.first - scrollY) < abs(closest.first - scrollY)) {
+ closestDetent = it
+ }
+ }
+ }
+
+ handler.postDelayed({
+ //"⬆️"
+ // smoothScrollTo(scrollX, shownDetent.roundToInt())
+
+ // doScroll(closestDetent?.second ?: state)
+
+ val newState = state
+ val state = when (state) {
+ Detents.Hidden -> hiddenDetent
+ Detents.Shown -> shownDetent
+ Detents.Expanded -> expandedDetent
+ }
+
+ smoothScrollTo(scrollX, closestDetent?.first?.roundToInt() ?: state.roundToInt())
+ updateState(closestDetent?.second ?: newState)
+
+ if (didDrag) {
+ handler.postDelayed({
+ didDrag = false
+ eventListener?.event("endDrag", null)
+ }, 40L)
+ }
+
+ }, 0L)
+ return true
+ } else if (didFling && velocity > 0) {
+
+ //"⬇️️"
+
+ val availableDetents = detents.filter { it.first < scrollY }
+
+ var closestDetent = availableDetents.firstOrNull()
+
+ closestDetent?.let { closest ->
+ availableDetents.forEach {
+ if (abs(it.first - scrollY) > abs(closest.first - scrollY)) {
+ closestDetent = it
+ }
+ }
+ }
+
+
+ handler.postDelayed({
+ // doScroll(closestDetent?.second ?: state)
+
+
+ // doScroll(closestDetent?.second ?: state)
+
+ val newState = state
+ val state = when (state) {
+ Detents.Hidden -> hiddenDetent
+ Detents.Shown -> shownDetent
+ Detents.Expanded -> expandedDetent
+ }
+
+ smoothScrollTo(scrollX, closestDetent?.first?.roundToInt() ?: state.roundToInt())
+ updateState(closestDetent?.second ?: newState)
+
+ if (didDrag) {
+ handler.postDelayed({
+ didDrag = false
+ eventListener?.event("endDrag", null)
+ }, 40L)
+ }
+
+ }, 0L)
+ return true
+ } else {
+ /// Ignore if sheet was released near the `expanded` detent.
+
+
+ val snappingDistance = toPx(this.snappingDistance)
+
+ if (distanceToShown > -snappingDistance) {
+ handler.postDelayed({
+ if (distanceToShown < snappingDistance) {
+ smoothScrollTo(scrollX, shownDetent.roundToInt())
+ updateState(Detents.Shown)
+ } else {
+ smoothScrollTo(scrollX, closedSheetHeight.roundToInt())
+ updateState(Detents.Hidden)
+ }
+
+ if (didDrag) {
+ handler.postDelayed({
+ didDrag = false
+ eventListener?.event("endDrag", null)
+ }, 40L)
+ }
+
+ }, 0L)
+ } else {
+
+ if (scrollY == 0) {
+ return false
+ }
+
+ val availableDetents = if (scrollY > 0) detents.filter { it.first < scrollY } else {
+ detents.filter { it.first > scrollY }
+ }
+
+ var closestDetent = availableDetents.firstOrNull()
+
+ closestDetent?.let { closest ->
+ availableDetents.forEach {
+ if (abs(it.first - scrollY) > abs(closest.first - scrollY)) {
+ closestDetent = it
+ }
+ }
+ }
+
+ val newState = state
+ val state = when (state) {
+ Detents.Hidden -> hiddenDetent
+ Detents.Shown -> shownDetent
+ Detents.Expanded -> expandedDetent
+ }
+
+ handler.postDelayed({
+ smoothScrollTo(scrollX, ((closestDetent?.first ?: state).roundToInt()))
+ updateState(closestDetent?.second ?: newState)
+
+ if (didDrag) {
+ handler.postDelayed({
+ didDrag = false
+ eventListener?.event("endDrag", null)
+ }, 40L)
+ }
+
+ }, 0L)
+ }
+
+ }
+ return true
+ }
+
+ private fun updateState(newState: Detents) {
+ state = newState
+ showing = newState == Detents.Shown
+
+ if (didDrag) {
+ eventListener?.event("showing", showing)
+ }
+ }
+
+ fun show(shouldShow: Boolean) {
+ if (shouldShow && state == Detents.Shown || !shouldShow && state == Detents.Hidden) {
+ return
+ }
+ animation?.cancel()
+ animation = null
+
+ val interpolator = this.interpolator
+ val minimumSheetHeight = toPx(minimumSheetHeight).roundToInt()
+ val closedSheetHeight = toPx(closedSheetHeight).roundToInt()
+
+ val toSize = if (shouldShow) {
+ minimumSheetHeight
+ } else {
+ closedSheetHeight
+ }
+
+ val scrollAnimation = ObjectAnimator.ofInt(this, "scrollY", toSize)
+ .apply {
+ addUpdateListener {
+ val value = (it.animatedValue as Int)
+ mainContainerView.translationY = value.toFloat()
+ sheetContainerView.translationY = value.toFloat()
+ }
+ }
+
+ val mainContainerViewHeight = mainContainerView.height
+ val newHeight = height - toSize
+ val heightAnimation = ObjectAnimator.ofInt(
+ mainContainerViewHeight,
+ newHeight
+ ).apply {
+ addUpdateListener {
+ val value = (it.animatedValue as Int)
+ val params = mainContainerView.layoutParams
+ params.height = value
+ mainContainerView.layoutParams = params
+ }
+ }
+
+ animation = AnimatorSet()
+ .apply {
+ playTogether(scrollAnimation, heightAnimation)
+ this.interpolator = interpolator
+ duration = (animationDuration * 1000).roundToLong()
+ doOnEnd {
+ showing = shouldShow
+ state = if (shouldShow) Detents.Shown else Detents.Hidden
+ eventListener?.event("showing", showing)
+
+ }
+ }
+
+
+ animation?.start()
+
+ }
+
+ override fun onOverScrolled(scrollX: Int, scrollY: Int, clampedX: Boolean, clampedY: Boolean) {
+ super.onOverScrolled(scrollX, scrollY, clampedX, clampedY)
+
+ mainContainerView.translationY = scrollY.toFloat()
+ sheetContainerView.translationY = scrollY.toFloat()
+
+ val params = mainContainerView.layoutParams
+ params.height = height - scrollY
+
+ mainContainerView.layoutParams = params
+ }
+
+}
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 00000000..2b068d11
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/drawable/ic_launcher_background.xml b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 00000000..07d5da9c
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/drawable/sample.jpg b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/drawable/sample.jpg
new file mode 100644
index 00000000..27a3940e
Binary files /dev/null and b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/drawable/sample.jpg differ
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/drawable/sample_image.jpg b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/drawable/sample_image.jpg
new file mode 100644
index 00000000..27a3940e
Binary files /dev/null and b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/drawable/sample_image.jpg differ
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/layout/activity_main.xml b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 00000000..80856cf9
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/layout/split_sheet.xml b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/layout/split_sheet.xml
new file mode 100644
index 00000000..102ef082
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/layout/split_sheet.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 00000000..eca70cfe
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 00000000..eca70cfe
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 00000000..c209e78e
Binary files /dev/null and b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..b2dfe3d1
Binary files /dev/null and b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 00000000..4f0f1d64
Binary files /dev/null and b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..62b611da
Binary files /dev/null and b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 00000000..948a3070
Binary files /dev/null and b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..1b9a6956
Binary files /dev/null and b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 00000000..28d4b77f
Binary files /dev/null and b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..9287f508
Binary files /dev/null and b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 00000000..aa7d6427
Binary files /dev/null and b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 00000000..9126ae37
Binary files /dev/null and b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/values-night/themes.xml b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/values-night/themes.xml
new file mode 100644
index 00000000..f0e41ac3
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/values/attr.xml b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/values/attr.xml
new file mode 100644
index 00000000..1b6b8766
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/values/attr.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/values/colors.xml b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/values/colors.xml
new file mode 100644
index 00000000..f8c6127d
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+
\ No newline at end of file
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/values/strings.xml b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/values/strings.xml
new file mode 100644
index 00000000..00690d17
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/values/strings.xml
@@ -0,0 +1,104 @@
+
+ SplitSheetDemo
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla posuere, dolor vitae blandit tempor, tellus nulla maximus turpis, eu congue nibh leo sit amet dolor. Suspendisse diam enim, posuere eget imperdiet vel, rhoncus sit amet lectus. Sed interdum feugiat magna, quis ornare urna semper nec. Aliquam vehicula velit purus, eu lacinia dolor pellentesque nec. Phasellus rhoncus ex eget arcu aliquet malesuada. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Praesent luctus commodo felis, sit amet luctus diam maximus eu. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc vitae massa eget dui feugiat hendrerit sit amet vitae turpis. Etiam ullamcorper neque eu risus congue iaculis id ac nibh.
+
+Duis laoreet fermentum massa tempor tempus. Aliquam aliquam, massa id suscipit iaculis, dui ante posuere purus, pharetra efficitur velit lectus at tortor. Vivamus posuere est ac sapien tincidunt imperdiet. Aenean in egestas dui. Cras a arcu in mi dignissim fringilla. Vestibulum vestibulum sem sed ex tempus tincidunt. Cras gravida nec ante ut pretium. Nam bibendum, eros ac tincidunt gravida, ex ipsum eleifend justo, vel imperdiet ligula justo nec leo. Etiam condimentum tincidunt nisi, cursus auctor ligula. Aenean ligula erat, tristique eu pretium id, efficitur in orci. Sed nulla lacus, finibus nec sollicitudin ut, porttitor quis purus. In consectetur fermentum sem sed consequat.
+
+Nunc euismod non eros ac sodales. Mauris laoreet bibendum nisi, vitae ornare dui molestie nec. Proin metus elit, semper in mauris vehicula, vestibulum convallis lorem. Fusce lacinia enim ligula, eget convallis massa ultrices quis. Cras gravida tristique pulvinar. Vestibulum dignissim nisi a posuere ultrices. Nullam vitae velit id leo euismod hendrerit at id libero. Sed sit amet felis facilisis, sodales risus vel, imperdiet justo. Nullam consectetur, libero vitae eleifend interdum, odio erat condimentum orci, vel posuere eros ipsum dictum eros. Nunc non sapien vel risus ultrices hendrerit. Nunc at tempor velit. Vivamus faucibus nunc sit amet dui viverra aliquam. Proin sollicitudin nunc tellus, et mattis magna imperdiet porttitor.
+
+Fusce pulvinar vel nibh et porta. Cras luctus ornare ultricies. Quisque egestas, orci id placerat ullamcorper, nisl tortor lobortis nibh, sit amet viverra diam odio nec nunc. Nunc ac consectetur dui. Pellentesque bibendum ex eu eros dignissim efficitur. Nullam non nunc dolor. Cras in eleifend justo.
+
+Aenean justo elit, ultrices porta est at, placerat faucibus lacus. Sed hendrerit ac magna eu dictum. Nunc molestie nisl ut ligula feugiat sodales. Mauris viverra vitae dui vitae convallis. Cras vitae risus euismod velit rhoncus finibus sed sit amet leo. Nullam ornare at lorem a faucibus. Nullam mollis porta odio nec bibendum. Aenean placerat suscipit nunc, cursus sagittis diam condimentum convallis. Curabitur eget nunc ut elit luctus semper. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Pellentesque placerat gravida ex, in placerat mi tincidunt ac. Vivamus purus erat, consectetur sed urna eu, tempus mollis eros. Proin a nulla in lorem sodales finibus non at sem. Aliquam sagittis viverra erat nec accumsan. Cras lobortis velit quis risus blandit pellentesque. Aenean a fermentum libero, a rutrum nunc.
+
+Sed interdum, enim at iaculis consectetur, risus risus lacinia risus, vitae auctor mi mauris at dolor. Sed mi nisi, facilisis ac maximus sit amet, imperdiet et justo. Maecenas quis rhoncus neque. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla porta vulputate risus ut iaculis. Nulla placerat dictum lacinia. Pellentesque commodo orci vitae ipsum iaculis pretium ornare et massa. Phasellus scelerisque mattis est, quis luctus enim facilisis vel. Praesent nec felis sit amet leo interdum rhoncus et et massa. Phasellus suscipit risus eu libero malesuada, ut vestibulum est feugiat. Vivamus quis pulvinar purus.
+
+Pellentesque fermentum lorem vel imperdiet dapibus. Quisque mollis lorem id risus luctus, vitae consequat diam vestibulum. Sed pulvinar in felis id elementum. Phasellus nisi diam, volutpat vitae sapien eu, egestas posuere elit. Integer interdum nisi et metus egestas faucibus. Aliquam porta sit amet ex a elementum. Nunc condimentum eros semper tortor congue, et suscipit nulla efficitur. Suspendisse sed urna dolor. Aliquam bibendum commodo ipsum, id rhoncus odio maximus ut. Maecenas bibendum commodo tempus. In hac habitasse platea dictumst. Etiam suscipit ante sit amet justo fringilla, ac consectetur est tempus. Curabitur sed libero nec velit feugiat facilisis. Pellentesque a molestie nisi, quis fringilla dolor. Maecenas et ipsum ut tortor hendrerit accumsan eu ut mauris. Ut tempus interdum lacus, non tincidunt nulla ultricies tincidunt.
+
+Aliquam hendrerit lacus vel libero sagittis ullamcorper. Suspendisse nec mauris malesuada, sodales quam id, facilisis elit. Morbi imperdiet metus enim, ac efficitur ligula molestie sit amet. Nullam euismod condimentum nunc, id iaculis nunc dictum id. Donec maximus nibh magna, non gravida mi sagittis id. Donec vehicula dolor vel nulla faucibus, a tristique odio molestie. Praesent porttitor, magna eget cursus aliquam, justo nisi lobortis urna, vel auctor neque leo vitae ligula.
+
+Nam et faucibus dui. Maecenas sollicitudin tellus felis. Vivamus ac nulla turpis. Vivamus at auctor erat, in maximus nisi. Fusce sapien quam, ultrices nec viverra sit amet, pretium a nunc. Praesent et orci neque. Nam sed pulvinar massa, ut facilisis est. Cras pretium, tortor aliquam scelerisque pulvinar, purus mauris maximus ligula, quis dapibus nunc felis sit amet lacus. Sed tristique diam at laoreet rutrum. Nullam eleifend enim a egestas ullamcorper. Duis at diam rhoncus, consectetur libero in, laoreet quam.
+
+In hac habitasse platea dictumst. Cras blandit elit vel leo condimentum hendrerit. Vestibulum efficitur ante fringilla vulputate euismod. Nunc molestie, arcu in imperdiet faucibus, enim est porta nulla, auctor commodo lacus dui semper est. In iaculis tristique accumsan. Proin dapibus massa ex, ac lobortis leo posuere rhoncus. Suspendisse vitae velit sit amet augue eleifend blandit. Nullam luctus dolor nec dictum luctus. Mauris non mauris faucibus, convallis justo commodo, aliquam ipsum. Morbi sit amet consequat nunc. Vivamus commodo mattis interdum. Mauris eleifend elit quis sollicitudin congue. Curabitur et tempus dolor, eu interdum urna. Nulla semper orci nec porta hendrerit. Interdum et malesuada fames ac ante ipsum primis in faucibus. Cras nec cursus dolor.
+
+Phasellus in leo ac eros pulvinar malesuada. Aenean tempor, nulla ac iaculis lobortis, est nibh ultrices nisi, in pretium lacus risus eget justo. Nam sed consequat nulla. Suspendisse eu dapibus quam, quis sollicitudin lacus. Aliquam consequat odio orci. Ut ullamcorper vitae magna vel imperdiet. Fusce imperdiet sem urna, at pharetra massa lacinia eget. Morbi ut interdum purus. Aliquam facilisis elit eget nisi sodales aliquet vitae nec eros. Sed nisl urna, ullamcorper id tortor at, luctus tristique risus. Sed at commodo turpis. Donec consequat sagittis risus.
+
+Nam posuere tellus at tempus facilisis. Etiam ut congue magna. Nulla ut risus lacus. Praesent id facilisis ante, et iaculis lectus. Praesent magna enim, varius vitae efficitur ut, elementum sit amet diam. Donec posuere urna at neque cursus tempus. Maecenas feugiat erat nec purus viverra laoreet. Nunc mollis eros sit amet congue laoreet. Etiam ac tempus augue. Nunc at semper tortor, id molestie lectus. Donec eleifend, diam sed porta pulvinar, tortor elit pellentesque velit, ut bibendum nulla magna quis tellus. Integer ut ultrices nisl. Duis sit amet eleifend sem. Sed et metus est.
+
+Nulla facilisi. Donec luctus sem nisl, non malesuada ante iaculis in. Quisque vitae odio tempus, lobortis leo eget, dapibus dolor. Aliquam cursus lectus ut consequat accumsan. In faucibus lectus sed faucibus pulvinar. In finibus ex id diam iaculis, id tempor tortor tristique. In hac habitasse platea dictumst. Praesent nec nibh purus. Suspendisse potenti.
+
+In dignissim hendrerit aliquet. Donec eget arcu fringilla, ultricies justo id, congue leo. Pellentesque eu velit arcu. Morbi pellentesque leo enim, non tincidunt neque aliquet auctor. Duis eget dui imperdiet, sagittis ligula ac, finibus sapien. Praesent rutrum, ipsum ut mollis aliquet, turpis odio pretium purus, sit amet finibus nunc justo eu orci. Nam erat nisl, auctor et dictum ut, elementum sit amet turpis. Cras egestas lacus ut mauris auctor, a tempor purus aliquet. Sed ullamcorper diam viverra mauris tincidunt commodo. Sed lacinia, ante vitae vestibulum dapibus, lectus nisi facilisis odio, id suscipit mi massa a risus. Phasellus tincidunt purus aliquet mi sagittis, accumsan aliquam nisl feugiat. Nam ex nisi, auctor non consectetur nec, venenatis id neque. Suspendisse eu mi quis dolor venenatis ullamcorper a id libero. Maecenas pretium, est ac dignissim ullamcorper, ex enim pulvinar nibh, a viverra risus dui vitae risus.
+
+Aliquam eu nisl sit amet libero elementum lacinia sit amet non lectus. Maecenas maximus, orci et scelerisque aliquet, ipsum nisi lacinia nisl, quis porttitor dolor nisi id ipsum. Sed eget aliquet magna, eget scelerisque libero. In enim orci, vulputate quis erat nec, rhoncus fermentum leo. Morbi pretium risus nunc, ac condimentum mi ultrices ac. Curabitur dignissim porta hendrerit. Nulla quis ex erat.
+
+Vivamus viverra facilisis tellus, congue cursus erat egestas id. Vivamus et lacus tempus, pellentesque leo a, ullamcorper orci. Vivamus tellus nunc, lobortis sit amet mauris a, ullamcorper lobortis enim. Maecenas semper sollicitudin tortor efficitur convallis. In efficitur nunc ac neque placerat, et ultricies dolor fermentum. Ut lacus nulla, sollicitudin vitae blandit quis, eleifend eu elit. Curabitur aliquam lacus ex, in pharetra eros vehicula eu. Phasellus ornare rutrum dui, nec rhoncus ligula scelerisque non. Donec condimentum ullamcorper risus. Nam finibus massa sed viverra consequat. Duis et nisi eu magna ornare condimentum.
+
+Sed nisi ante, ornare eu dignissim quis, ultricies non nisl. In blandit leo ut vehicula tempor. Proin a justo risus. Morbi vulputate tempus arcu a sollicitudin. Aliquam accumsan malesuada nunc sed feugiat. Integer fermentum odio nec rhoncus convallis. Praesent mauris felis, ullamcorper nec ullamcorper vel, ullamcorper in lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Etiam a orci lacinia nulla porta porta.
+
+Etiam non eros nibh. Nulla iaculis sapien id urna dignissim, eget suscipit orci faucibus. Sed vel placerat nisi, eget condimentum nisl. Nulla a augue et elit maximus fermentum dapibus sed tortor. Curabitur non ipsum ac dolor ullamcorper volutpat. Quisque at rhoncus est. Proin iaculis eu turpis a pulvinar.
+
+Donec sit amet nunc ante. Donec aliquet metus quis dictum bibendum. Sed erat quam, egestas sed risus aliquam, rutrum faucibus augue. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nunc mi dolor, laoreet ut porttitor vitae, pretium eget elit. Aenean tempus tristique metus, nec egestas leo fringilla sit amet. Ut feugiat ex diam, in pretium velit iaculis in. Ut nec gravida magna, sed vehicula dolor.
+
+Sed eu risus eu quam feugiat ultricies. Suspendisse fringilla sit amet sapien sed lobortis. Duis id mollis felis, in placerat ante. Phasellus eget iaculis orci, in auctor nisl. Cras blandit mi in libero tempor, et rhoncus ex posuere. Donec pretium, metus in vestibulum malesuada, ante nisi porta orci, quis ultrices justo libero sit amet massa. Etiam metus odio, molestie quis mollis non, commodo in orci.
+
+In quis arcu ac mauris ullamcorper dapibus ac eu nisl. Pellentesque consectetur sagittis luctus. Praesent eget mauris vel eros lacinia faucibus. Pellentesque id luctus diam. Praesent gravida dignissim mauris, a placerat arcu aliquet vitae. Suspendisse augue odio, auctor a ligula at, sagittis vestibulum neque. Nam et interdum mi. Cras feugiat mi eget bibendum fringilla. Pellentesque at purus vitae lectus tincidunt congue quis elementum nisi. Nulla facilisi. In eget metus sapien. Etiam faucibus magna nec lorem hendrerit finibus. Vivamus eget lacinia erat, id facilisis dolor. Nam malesuada diam velit, quis efficitur nibh tempus pretium.
+
+Fusce bibendum ligula luctus mattis tempor. Quisque erat lorem, venenatis volutpat augue non, luctus euismod velit. Morbi egestas sapien sed risus luctus lacinia. Donec enim purus, ultrices sit amet augue et, suscipit viverra diam. Phasellus suscipit, magna nec pretium egestas, erat metus tincidunt lacus, vel mollis diam purus non nunc. Nullam blandit blandit elit eu convallis. Sed id nisl vitae orci aliquam feugiat et non nulla. Pellentesque sed convallis dui, sodales rutrum erat.
+
+Sed quis magna fermentum lacus rhoncus porta. Donec dignissim urna at tortor feugiat, eget commodo diam pellentesque. In accumsan felis arcu. Mauris neque nunc, mollis non maximus a, mollis sed libero. Integer maximus nunc id lorem eleifend, non malesuada arcu tempus. Nulla et iaculis orci. Proin nulla diam, convallis vel ultricies vel, molestie aliquet nunc. Etiam varius erat nec porttitor consequat. Nunc id ex ac diam tempus tempor. Sed efficitur nunc sem, ac elementum mauris tincidunt non. Etiam finibus augue nec risus feugiat dictum. Proin id diam vestibulum, efficitur eros a, gravida orci. Curabitur cursus et enim vel tincidunt. Mauris ut leo quis tortor faucibus posuere. Sed sit amet malesuada augue, quis volutpat lacus. Phasellus semper eleifend nisl eget pharetra.
+
+Cras convallis pretium ligula, et euismod magna porttitor et. Sed ut tortor iaculis ex ullamcorper elementum. Sed tincidunt efficitur urna, vestibulum sodales elit consectetur vel. Fusce in facilisis sapien. Duis quis eleifend risus. Suspendisse a suscipit diam. Vestibulum dapibus cursus velit, ut ullamcorper justo auctor nec. Morbi scelerisque turpis sed hendrerit consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec quis magna eu mauris elementum consequat ac at nibh. Duis neque enim, blandit in massa sed, auctor sollicitudin quam. Maecenas lacinia efficitur aliquam. Vivamus sollicitudin sapien et dolor gravida mollis.
+
+Nullam bibendum mi ac varius ultricies. Etiam urna neque, lacinia et luctus et, tincidunt nec arcu. Donec odio ex, lacinia id efficitur bibendum, sollicitudin eu ipsum. Curabitur tempus mauris vel mauris gravida semper. Integer rhoncus felis at leo sollicitudin fermentum. Nunc vel sagittis lacus. Proin maximus congue nulla, eget suscipit nisl dapibus eget. Vestibulum libero diam, blandit a tincidunt sit amet, venenatis vitae ex. Morbi nec erat justo. Morbi magna neque, porta a sagittis fringilla, lobortis ac lectus. Fusce sit amet libero sit amet metus aliquam mattis a ac felis. Curabitur ut tempus arcu, in sollicitudin sem.
+
+Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam posuere lectus id eros faucibus imperdiet. In sollicitudin diam vel nibh fermentum ultricies. Vestibulum nunc massa, consequat id euismod nec, dictum et sem. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Maecenas auctor elit a scelerisque molestie. Etiam eu felis mi. Sed porta enim sit amet odio consectetur semper. Etiam commodo lobortis leo, at tincidunt sapien lacinia sed. Donec at sodales quam. Aliquam pharetra pellentesque mi, et accumsan mauris maximus eget. Nunc laoreet magna quam, eget posuere felis ornare nec. Vestibulum egestas ex purus, nec sollicitudin leo faucibus vitae.
+
+Proin dictum euismod sem mattis tincidunt. Sed at felis orci. Maecenas in euismod augue, et consectetur turpis. Cras ultrices diam velit, vitae finibus metus posuere et. Nunc nec egestas turpis. Maecenas dui diam, pellentesque sed vehicula sit amet, sodales a lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Integer tristique, leo nec blandit faucibus, leo purus aliquet ipsum, vehicula sollicitudin ligula risus id tortor. Cras velit massa, finibus ac rhoncus eleifend, dapibus sed nulla. Donec laoreet odio mauris, nec tincidunt ante finibus quis. Praesent viverra imperdiet feugiat. Sed non lorem felis. Donec at sem at metus dictum finibus. Curabitur eget urna fermentum leo bibendum facilisis a ac urna.
+
+Nam sit amet vehicula odio, ut tincidunt elit. Curabitur rhoncus vulputate massa quis elementum. Aenean faucibus velit sit amet lobortis congue. Duis ultricies ligula a felis congue, et commodo magna euismod. Nunc ipsum eros, euismod in odio sit amet, lacinia fermentum massa. Phasellus facilisis risus orci, non varius nunc pretium eget. Aliquam non nulla ac lacus finibus suscipit. Morbi at metus orci. Ut quis varius ex. Ut accumsan ipsum quis arcu aliquam, eget lobortis arcu laoreet. Pellentesque vitae ullamcorper felis, non molestie ipsum. Fusce in tortor et tellus mollis suscipit. Maecenas eget porta enim. Suspendisse eget euismod justo. Ut rutrum mi sit amet eros mollis, eget consectetur massa facilisis.
+
+Morbi convallis, ligula id sollicitudin euismod, risus est lobortis magna, sed congue elit purus quis purus. Maecenas ligula purus, vestibulum mollis sodales et, dignissim non velit. Nulla sed libero in lectus tincidunt blandit non maximus velit. Pellentesque sollicitudin tempus metus, non laoreet tellus cursus nec. Fusce varius, diam non venenatis imperdiet, ligula leo bibendum neque, eleifend sollicitudin felis dolor vitae sem. Phasellus suscipit hendrerit eros, quis porttitor nisi eleifend eget. Ut faucibus magna tellus, eu malesuada odio pellentesque eu. Integer nibh odio, maximus dictum mollis nec, tempus id est. Morbi fermentum purus sit amet est luctus, eu tempor orci venenatis. Maecenas eget odio vitae nisl dictum feugiat a vitae velit. Donec ac euismod ipsum. Aliquam erat volutpat.
+
+Integer sed nisl felis. Donec vitae dictum ante, ac vehicula urna. Nunc facilisis convallis neque. Donec suscipit finibus ex. In pharetra lobortis sem id fringilla. Sed molestie turpis vel tempus condimentum. Aenean non dictum sapien. Vestibulum sit amet augue faucibus, rhoncus tortor vel, pulvinar nulla. Sed at tortor placerat, elementum elit at, mattis justo. Ut vitae urna libero. Phasellus venenatis consectetur neque ac tempus.
+
+Vestibulum at luctus arcu, eu scelerisque tellus. Nam porta, tellus a tempus sodales, massa diam sodales augue, et luctus orci tellus ac nibh. Nam at dolor eu lorem sollicitudin luctus et a diam. In ultricies quam at nunc accumsan egestas. Fusce tempus arcu magna, ut consequat neque ornare sed. Nunc eu libero lobortis, lacinia elit et, vulputate velit. Nulla tristique faucibus mauris eu condimentum. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+Fusce elit dolor, pharetra vel felis convallis, porta posuere urna. Nulla facilisi. In pellentesque est a porta ultricies. In posuere ante euismod, fermentum dolor a, rutrum justo. Aliquam nulla arcu, facilisis ac justo sed, ornare fermentum metus. Nunc rhoncus sagittis facilisis. Suspendisse egestas ac risus sed auctor.
+
+Fusce ante nisi, molestie nec magna ut, lacinia blandit libero. Sed semper ultricies erat, quis egestas ante hendrerit at. Nam sollicitudin nibh lacus, nec volutpat orci lacinia a. Proin tempus lectus dolor, nec rhoncus metus tempor sit amet. Morbi nec placerat velit, in sodales lacus. Aliquam accumsan cursus enim, pellentesque ullamcorper mi rutrum non. Vestibulum vel velit in arcu tincidunt lobortis a nec lacus. Aliquam sit amet lorem posuere, sodales quam at, porta enim. Sed sed sapien quis nisl euismod iaculis vel non turpis. Integer sed felis sem. Nam dignissim libero in nulla lobortis dignissim. In gravida venenatis laoreet. Mauris ut hendrerit magna, eget consectetur velit. Nullam aliquam urna sit amet augue suscipit, nec aliquam odio rutrum.
+
+Cras turpis risus, dapibus ac lectus nec, dapibus fringilla eros. Sed hendrerit nulla nec augue feugiat, eu posuere ligula sagittis. Nunc rhoncus vulputate semper. Sed consequat faucibus eros euismod pretium. Phasellus sed velit tincidunt, molestie libero a, fermentum mauris. Etiam vehicula orci id tristique congue. Praesent ligula lorem, laoreet at velit vitae, feugiat feugiat eros. Etiam vitae suscipit quam. In sit amet imperdiet enim. Nulla facilisi. Nunc molestie, lorem hendrerit tempor semper, felis dui ornare sem, vel euismod erat velit scelerisque arcu.
+
+Curabitur fermentum magna lorem, at convallis lectus ornare vitae. Ut imperdiet blandit odio. Proin gravida imperdiet maximus. Vivamus tincidunt suscipit ex ut semper. Cras ut euismod ligula. Sed a dapibus enim. Duis tempor tellus vel lectus cursus, quis mollis ipsum lacinia.
+
+In hac habitasse platea dictumst. Nullam aliquet tristique rutrum. Nullam ullamcorper sapien sit amet varius tempus. Mauris semper blandit enim in placerat. Pellentesque finibus sapien at enim congue, et aliquam sapien rhoncus. Duis sollicitudin a diam ut suscipit. Praesent justo orci, aliquet ut nisi rhoncus, porttitor euismod nisi. Proin dictum nisl urna, nec porta leo bibendum vitae. Cras tincidunt vestibulum augue, a varius tortor accumsan in. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae;
+
+Donec tincidunt id tortor et molestie. Fusce et justo eu magna aliquet lobortis vel non turpis. Donec eget pellentesque ipsum, at blandit nisl. Morbi sed ex pharetra, porta velit ut, fermentum est. Suspendisse potenti. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc lacus nunc, porta vitae volutpat ac, efficitur eget risus. Etiam maximus neque sed rhoncus euismod. Curabitur non tristique est. Nullam commodo posuere odio, id sagittis est dictum eu. Praesent tristique luctus purus, ac mollis metus ornare et. Morbi egestas lacus vel eros tempor fringilla. In hac habitasse platea dictumst.
+
+Mauris at arcu rhoncus, tempor quam posuere, tempus risus. Fusce aliquet metus id elementum egestas. Pellentesque nec ante nulla. Sed dapibus, lorem sed maximus sodales, erat sem vehicula nunc, non eleifend nisi orci condimentum mauris. In vulputate felis id ullamcorper varius. Proin eget neque magna. Sed lectus nisl, accumsan vel fermentum at, gravida sed neque. Morbi consequat turpis vulputate bibendum porttitor. Sed laoreet pharetra elit at pretium.
+
+Fusce mollis quam posuere lacus porttitor, non scelerisque eros convallis. Maecenas dui tortor, congue eget nibh eu, finibus dignissim lectus. Aliquam eu ullamcorper lectus, at mattis est. Nullam tempor nulla posuere eros blandit, at luctus urna volutpat. Nunc sed luctus tortor. Suspendisse et ex nisl. Phasellus ut augue magna. Ut ac scelerisque eros. Phasellus porta ligula ut rutrum dignissim. Maecenas at nunc porta, vulputate dolor quis, dictum erat. Vestibulum sodales faucibus orci, vitae suscipit lacus posuere ac. Sed eget pellentesque lectus, a iaculis enim.
+
+In consequat quis neque sed feugiat. Donec pulvinar ornare nisl sollicitudin ullamcorper. Morbi non aliquam nibh, eget faucibus libero. Phasellus varius vehicula tempus. Praesent commodo vestibulum lectus et molestie. Integer gravida malesuada pharetra. Suspendisse faucibus sem tempor, vulputate nisl eu, consequat massa.
+
+Aenean nec ex porta, interdum urna ac, tincidunt libero. Proin ac est massa. Sed quis felis eget urna facilisis commodo eu fringilla velit. Cras ullamcorper ligula eget risus ultricies, nec luctus elit molestie. Suspendisse quis ultricies libero, sed ornare arcu. Sed dictum arcu mi, at tempor mi eleifend in. Nulla facilisi. Fusce eu odio ut nisl pulvinar lobortis.
+
+Cras ornare nec arcu ut accumsan. Mauris mattis congue sapien a gravida. Quisque non ante ut justo blandit placerat. Quisque porta risus vitae bibendum euismod. Praesent vestibulum ullamcorper ante, faucibus condimentum nunc cursus in. Curabitur a fringilla dolor. Vestibulum ac orci nec tellus mollis cursus ac quis lorem.
+
+Suspendisse laoreet sem vitae tellus interdum imperdiet. Sed non urna non arcu rhoncus semper. Vivamus sit amet maximus elit. Nam feugiat vitae magna et varius. Nulla bibendum pellentesque finibus. Donec fermentum aliquet magna, vitae lacinia sem mattis ut. Mauris facilisis turpis a nisl molestie consequat. Maecenas suscipit tincidunt lorem. Donec eget egestas justo. Phasellus vestibulum, erat vel efficitur efficitur, est lectus placerat erat, eget tempus est sem non justo.
+
+Maecenas tortor nibh, volutpat et ornare eget, scelerisque nec risus. In volutpat iaculis egestas. In porta ipsum diam, sit amet bibendum lectus pretium ut. In non iaculis nisl, nec viverra nulla. Integer laoreet, velit ut viverra accumsan, mi odio congue massa, sed iaculis metus turpis a orci. Etiam consectetur lorem eu sem bibendum, quis ultrices nibh blandit. Pellentesque non lacus tempor, condimentum est vestibulum, fermentum felis. Cras ut velit cursus odio vehicula gravida ut eu est. Maecenas placerat viverra ante in laoreet. Duis aliquet odio sed eleifend lobortis. Duis placerat non magna vitae cursus. Aliquam fringilla, augue vel suscipit dignissim, quam lacus egestas justo, id vulputate turpis ante nec massa. Quisque sagittis, justo et varius rutrum, enim arcu rhoncus justo, tristique luctus leo lacus quis lacus. Quisque lorem eros, luctus ut ipsum efficitur, consectetur varius risus. Duis eu justo dignissim sem gravida elementum sit amet efficitur augue.
+
+Praesent vel ligula sit amet purus blandit tempus. Morbi vulputate ipsum dolor, a efficitur velit porttitor in. Fusce ante turpis, maximus nec varius at, consectetur nec lorem. Cras a accumsan ante. Praesent malesuada, nunc a aliquet tincidunt, mi libero vulputate lacus, non viverra ex massa eget orci. Ut eleifend, odio sit amet placerat faucibus, magna elit tempus erat, et dapibus neque sem vitae est. Aenean imperdiet, purus ac pellentesque tempus, ex dui commodo ex, id bibendum ligula velit id ligula. Aenean et ligula sagittis, volutpat lacus eget, porttitor metus. Sed accumsan justo erat, a molestie nunc tristique at.
+
+Fusce fermentum neque imperdiet convallis rhoncus. Donec tincidunt elementum enim, in ultricies leo pretium in. Etiam dignissim risus sed nulla malesuada, sit amet interdum eros pretium. In commodo felis vitae varius ultricies. Donec tincidunt nunc risus, id euismod nisi efficitur non. Aenean ligula lacus, fermentum quis lectus fringilla, rhoncus sollicitudin quam. Integer pulvinar nibh vestibulum posuere convallis. Pellentesque ac elit augue. Aliquam consequat nulla id dapibus convallis. Sed vulputate vel odio quis pharetra.
+
+Aenean in mauris sit amet ante laoreet euismod eget vel est. Morbi ac mattis eros. Mauris quis massa commodo, commodo est nec, lobortis nunc. Ut turpis nisi, aliquam in libero non, varius aliquam est. Donec mattis sit amet nunc in lacinia. Suspendisse pretium purus sed rhoncus finibus. Aenean justo lectus, porttitor in viverra sed, faucibus id ex. Suspendisse egestas, tellus volutpat convallis sodales, lectus leo varius lectus, a facilisis nisl neque non mauris. Etiam convallis, massa at porta elementum, nunc dui sodales orci, a dapibus elit erat eget ex. Fusce lacinia, tellus ut molestie pellentesque, dui sem laoreet nibh, ut varius dolor eros eu risus.
+
+Quisque volutpat facilisis erat, at pellentesque urna pulvinar id. Proin at arcu et purus hendrerit iaculis. Nunc accumsan vel dolor a cursus. Phasellus id augue erat. Donec non nisl molestie, volutpat risus pharetra, rhoncus lacus. Nunc eu placerat mi. Praesent ut tempus sem, ac lobortis mi. Sed congue, nunc non ornare viverra, metus orci lacinia urna, et dignissim diam mi vel turpis. Duis sed dui pretium nunc varius pellentesque id ac ligula. Pellentesque egestas ex nec nisl congue rutrum non nec est.
+
+Aliquam finibus laoreet luctus. Duis vulputate volutpat euismod. Suspendisse potenti. Mauris dapibus velit vitae diam bibendum suscipit. Suspendisse nibh urna, bibendum sit amet tristique eu, dictum a nibh. Cras ut varius metus. Mauris nec semper tortor. Vestibulum ac est aliquet, semper elit in, rhoncus risus. Proin tempus dui ac odio aliquet imperdiet. Sed eu tristique arcu, at rhoncus lectus. Proin est leo, ultricies id scelerisque ac, finibus et velit. Mauris lectus eros, pharetra eu malesuada eu, laoreet et orci. Curabitur quis dui suscipit ex posuere laoreet. Cras lacinia, purus id tempus dictum, nulla lectus aliquam ligula, eget auctor nisl odio id tortor. Pellentesque neque ex, lobortis a tellus quis, cursus ultrices mi.
+
+Aenean suscipit dolor non massa luctus, eleifend accumsan magna semper. Nam ac odio ultrices, tempor eros sed, dapibus ipsum. Sed at nunc accumsan, dignissim dui eget, tristique dolor. Aenean erat ligula, maximus vitae urna quis, malesuada condimentum nulla. Nunc tincidunt ullamcorper tortor, vitae aliquet lorem lacinia ac. Donec sed finibus nulla, ut molestie metus. Sed nec lectus et felis porta bibendum.
+
+
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/values/themes.xml b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/values/themes.xml
new file mode 100644
index 00000000..81c980e5
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/values/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/xml/backup_rules.xml b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 00000000..fa0f996d
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/xml/data_extraction_rules.xml b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 00000000..9ee9997b
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/test/java/io/github/triniwiz/splitsheetdemo/ExampleUnitTest.kt b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/test/java/io/github/triniwiz/splitsheetdemo/ExampleUnitTest.kt
new file mode 100644
index 00000000..4bba9ec4
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/app/src/test/java/io/github/triniwiz/splitsheetdemo/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package io.github.triniwiz.splitsheetdemo
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/build.gradle b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/build.gradle
new file mode 100644
index 00000000..25369742
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/build.gradle
@@ -0,0 +1,6 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+ id 'com.android.application' version '7.3.1' apply false
+ id 'com.android.library' version '7.3.1' apply false
+ id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
+}
\ No newline at end of file
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/gradle.properties b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/gradle.properties
new file mode 100644
index 00000000..3c5031eb
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/gradle.properties
@@ -0,0 +1,23 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/gradle/wrapper/gradle-wrapper.jar b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..e708b1c0
Binary files /dev/null and b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/gradle/wrapper/gradle-wrapper.properties b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..d35fa33e
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Tue Dec 06 12:51:23 AST 2022
+distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/gradlew b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/gradlew
new file mode 100755
index 00000000..4f906e0c
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/gradlew.bat b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/gradlew.bat
new file mode 100644
index 00000000..ac1b06f9
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/settings.gradle b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/settings.gradle
new file mode 100644
index 00000000..4962a2b3
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/settings.gradle
@@ -0,0 +1,17 @@
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ google()
+ mavenCentral()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+rootProject.name = "SplitSheetDemo"
+include ':app'
+include ':splitsheet'
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/.gitignore b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/build.gradle b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/build.gradle
new file mode 100644
index 00000000..d4437852
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/build.gradle
@@ -0,0 +1,45 @@
+plugins {
+ id 'com.android.library'
+ id 'org.jetbrains.kotlin.android'
+}
+
+android {
+ namespace 'io.github.triniwiz.splitsheet'
+ compileSdk 33
+
+ defaultConfig {
+ minSdk 17
+ targetSdk 33
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles "consumer-rules.pro"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ buildFeatures {
+ viewBinding = true
+ }
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.6.0'
+ implementation 'com.google.android.material:material:1.7.0'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.5'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
+}
\ No newline at end of file
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/consumer-rules.pro b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/proguard-rules.pro b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/src/androidTest/java/io/github/triniwiz/splitsheet/ExampleInstrumentedTest.kt b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/src/androidTest/java/io/github/triniwiz/splitsheet/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..1b238179
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/src/androidTest/java/io/github/triniwiz/splitsheet/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package io.github.triniwiz.splitsheet
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("io.github.triniwiz.splitsheet.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/src/main/AndroidManifest.xml b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/src/main/java/io/github/triniwiz/splitsheet/SplitSheet.kt b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/src/main/java/io/github/triniwiz/splitsheet/SplitSheet.kt
new file mode 100644
index 00000000..184b2d8b
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/src/main/java/io/github/triniwiz/splitsheet/SplitSheet.kt
@@ -0,0 +1,698 @@
+package io.github.triniwiz.splitsheet
+
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.Drawable
+import android.os.Build
+import android.util.AttributeSet
+import android.util.SparseArray
+import android.util.TypedValue
+import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams
+import android.view.animation.DecelerateInterpolator
+import android.widget.FrameLayout
+import androidx.coordinatorlayout.widget.CoordinatorLayout
+import androidx.core.animation.doOnEnd
+import androidx.core.view.ViewCompat
+import androidx.core.view.updateLayoutParams
+import com.google.android.material.appbar.AppBarLayout
+import com.google.android.material.appbar.AppBarLayout.Behavior.DragCallback
+import com.google.android.material.appbar.AppBarLayout.OnOffsetChangedListener
+import com.google.android.material.appbar.CollapsingToolbarLayout
+import io.github.triniwiz.splitsheet.databinding.SplitsheetBinding
+import kotlin.math.abs
+import kotlin.math.roundToInt
+import kotlin.math.roundToLong
+
+
+class SplitSheet @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null
+) : FrameLayout(context, attrs) {
+
+ var mainView: View? = null
+ var sheetView: View? = null
+
+ /// If true, `mainViewController` will shift up as the sheet is shown.
+ var displaceContent = true
+ set(value) {
+ field = value
+ // todo: clean up, this no longer affects the main content since we are overlaying it on top
+
+ // val params = binding.mainContainer.layoutParams as CollapsingToolbarLayout.LayoutParams
+ // if (value) {
+ // params.collapseMode = CollapsingToolbarLayout.LayoutParams.COLLAPSE_MODE_OFF
+ // params.parallaxMultiplier = 1F
+ // } else {
+ // params.collapseMode = CollapsingToolbarLayout.LayoutParams.COLLAPSE_MODE_PIN
+ // }
+ // binding.mainContainer.layoutParams = params
+ }
+
+ /// Show a grabber handle.
+ var showHandle = true
+ set(value) {
+ field = value
+ if (field) {
+ binding.handleView.visibility = View.VISIBLE
+ } else {
+ binding.handleView.visibility = View.INVISIBLE
+ }
+ }
+
+ /// The minimum sheet height.
+ var minimumSheetHeight = 300F
+ set(value) {
+ field = value
+
+ // set the minimum height of the AppBar/Toolbar
+ val toolbar = binding.appbar.getChildAt(0) as CollapsingToolbarLayout
+ toolbar.minimumHeight = (metrics.density * value).toInt()
+ }
+
+ /// Enforce a sheet height which always shows when in "not showing" state
+ var closedSheetHeight = 90F
+
+ /// When the sheet is shown and dragged within this limit, the sheet will bounce back.
+ var snappingDistance = 150F
+
+ /// How long the show/hide animation takes.
+ var animationDuration = 0.4F
+ set(value) {
+ field = value
+ val toolbar = binding.appbar.getChildAt(0) as CollapsingToolbarLayout
+ toolbar.scrimAnimationDuration = (value * 1000).roundToLong()
+ }
+
+ /// If swiping up to show the sheet is allowed or not.
+ var swipeUpToShowAllowed = true
+
+ private var mScrollable: Boolean = true
+
+
+ private val disabledBehaviour = object : DragCallback() {
+ override fun canDrag(appBarLayout: AppBarLayout): Boolean {
+ return false
+ }
+ }
+
+ var isScrollEnabled = true
+ set(value) {
+ field = value
+ /*
+ val toolbar = binding.appbar.getChildAt(0) as CollapsingToolbarLayout
+ toolbar.updateLayoutParams {
+ scrollFlags = if (value) {
+ AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL or AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP or AppBarLayout.LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED
+ } else {
+ AppBarLayout.LayoutParams.SCROLL_FLAG_NO_SCROLL
+ }
+ }
+ */
+ }
+
+ private var binding: SplitsheetBinding
+
+ var showing = false
+ private set
+
+
+ // https://m2.material.io/design/environment/elevation.html 4dp
+ var mainViewElevation = true
+ set(value) {
+ field = value
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ ViewCompat.setElevation(binding.appbar, 0F)
+ if (value) {
+ binding.appbar.targetElevation = toPx(4).toFloat()
+ } else {
+ binding.appbar.targetElevation = 0F
+ }
+ }
+ }
+
+ private var initialAppBarBackground: Drawable
+
+ var mainViewBackgroundColor: Int? = null
+ set(value) {
+ field = value
+ if (value == null) {
+ binding.appbar.background = initialAppBarBackground
+ } else {
+ binding.appbar.background = ColorDrawable(value)
+ }
+ }
+
+ private var state = Detents.Hidden
+
+ private fun setState(state: Detents) {
+ this.state = state
+ }
+
+ interface Events {
+ fun event(name: String, value: Any?)
+ }
+
+ var eventListener: Events? = null
+
+ private val metrics = resources.displayMetrics
+
+ private fun toPx(dip: Float): Float {
+ return dip * metrics.density
+ }
+
+ private fun toPx(dip: Int): Int {
+ return (dip * metrics.density).roundToInt()
+ }
+
+ private fun toDip(px: Float): Float {
+ return px / metrics.density
+ }
+
+ private fun getScrollableLength(): Int {
+ return binding.root.height
+ }
+
+ private enum class Detents {
+ Hidden,
+ Shown,
+ Expanded
+ }
+
+ private val detents =
+ arrayOf(Pair(0F, Detents.Hidden), Pair(0F, Detents.Hidden), Pair(0F, Detents.Hidden))
+
+
+// class CustomScrollBehavior(context: Context?, attrs: AttributeSet?) :
+// AppBarLayout.ScrollingViewBehavior(context, attrs) {
+//
+// var targetToScale: LinearLayout? = null
+// var splitsheet: WeakReference? = null
+//
+// override fun onLayoutChild(
+// parent: CoordinatorLayout,
+// child: View,
+// layoutDirection: Int
+// ): Boolean {
+// val layout = super.onLayoutChild(parent, child, layoutDirection)
+// if (targetToScale == null) {
+// targetToScale = parent.findViewById(R.id.mainContainer)
+// }
+//
+// return layout
+// }
+//
+// override fun layoutDependsOn(
+// parent: CoordinatorLayout,
+// child: View,
+// dependency: View
+// ): Boolean {
+// return dependency is AppBarLayout
+// }
+//
+// override fun onStartNestedScroll(
+// coordinatorLayout: CoordinatorLayout,
+// child: View,
+// directTargetChild: View,
+// target: View,
+// axes: Int,
+// type: Int
+// ): Boolean {
+// return true
+// }
+//
+//
+// override fun onNestedScroll(
+// coordinatorLayout: CoordinatorLayout,
+// child: View,
+// target: View,
+// dxConsumed: Int,
+// dyConsumed: Int,
+// dxUnconsumed: Int,
+// dyUnconsumed: Int,
+// type: Int,
+// consumed: IntArray
+// ) {
+// super.onNestedScroll(
+// coordinatorLayout,
+// child,
+// target,
+// dxConsumed,
+// dyConsumed,
+// dxUnconsumed,
+// dyUnconsumed,
+// type,
+// consumed
+// )
+//
+//
+// // dyUnconsumed < 0 // expanding
+// // dyUnconsumed > 0 // collapsing
+// splitsheet?.get()?.let {
+// targetToScale?.updateLayoutParams {
+//
+// val newValue = it.binding.appbar.measuredHeight + abs(dxConsumed)
+//
+// // layout size can be changed here
+// this.height = newValue
+//
+//
+// if (dyConsumed < 0) {
+// // expanding
+//
+// // this.height = newValue
+// // targetToScale!!.translationY = dyUnconsumed.toFloat()
+//
+// } else if (dyConsumed > 0) {
+// // collapsing
+//
+//// this.height = newValue
+// // targetToScale!!.translationY = dyUnconsumed.toFloat()
+//
+// if (newValue >= it.minimumSheetHeight) {
+// // this.height = newValue
+// } else {
+// //this.height = it.minimumSheetHeight.toInt()
+// }
+//
+// }
+//
+// }
+// }
+// }
+//
+// override fun onStopNestedScroll(
+// coordinatorLayout: CoordinatorLayout,
+// child: View,
+// target: View,
+// type: Int
+// ) {
+// super.onStopNestedScroll(coordinatorLayout, child, target, type)
+//
+// splitsheet?.get()?.let {
+// targetToScale?.updateLayoutParams {
+// Log.d(
+// "com.test",
+// "appbar ${it.binding.appbar.measuredHeight} ${it.binding.mainContainer.measuredHeight}"
+// )
+// }
+// }
+// }
+// }
+
+ init {
+ val inflator = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
+ binding = SplitsheetBinding.inflate(inflator, this, false)
+ addView(binding.root)
+ initialAppBarBackground = binding.appbar.background
+
+ val toolbar = binding.appbar.getChildAt(0) as CollapsingToolbarLayout
+ toolbar.minimumHeight = (metrics.density * minimumSheetHeight).toInt()
+
+ binding.appbar.addOnOffsetChangedListener(object : OnOffsetChangedListener {
+ private var didEmit = false
+ private var didInit = false
+ private var lastVerticalOffset = 0
+
+ override fun onOffsetChanged(appBarLayout: AppBarLayout, verticalOffset: Int) {
+ if (verticalOffset != lastVerticalOffset) {
+ lastVerticalOffset = verticalOffset
+ binding.mainContainerOverlay.updateLayoutParams {
+ this.height = binding.appbar.measuredHeight + verticalOffset
+ }
+ }
+
+ if (verticalOffset == 0) {
+ if (showing) {
+ showing = false
+ eventListener?.event("showing", false)
+ }
+ if (didDrag) {
+ didDrag = false
+ didEmit = false
+ handler.postDelayed({
+ eventListener?.event("endDrag", null)
+ }, 40L)
+ }
+
+ } else if (abs(verticalOffset) >= appBarLayout.totalScrollRange) {
+ if (!showing) {
+ showing = true
+ eventListener?.event("showing", true)
+ }
+
+ if (didDrag) {
+ didDrag = false
+ didEmit = false
+ handler.postDelayed({
+ eventListener?.event("endDrag", null)
+ }, 40L)
+ }
+ } else {
+ if (isTouch && isDragging && !didEmit) {
+ didEmit = true
+ eventListener?.event("beginDrag", null)
+ }
+ }
+ }
+ })
+ }
+
+ private var lastEvent = MotionEvent.ACTION_UP
+ private var didDrag = false
+
+ private var isDragging = false
+ private var isTouch = false
+
+ override fun dispatchTouchEvent(event: MotionEvent): Boolean {
+
+ if (!isScrollEnabled) {
+ mainView?.dispatchTouchEvent(event)
+ sheetView?.dispatchTouchEvent(event)
+ return true
+ }
+
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> {
+ lastEvent = MotionEvent.ACTION_DOWN
+ isTouch = true
+ }
+ MotionEvent.ACTION_MOVE -> {
+
+ if (lastEvent == MotionEvent.ACTION_DOWN) {
+ lastEvent = MotionEvent.ACTION_MOVE
+ isDragging = true
+ }
+ }
+ MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
+
+ if (lastEvent == MotionEvent.ACTION_MOVE) {
+ didDrag = true
+ isTouch = false
+ }
+
+ lastEvent = event.action
+ }
+ }
+
+ return super.dispatchTouchEvent(event)
+ }
+
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+ super.onLayout(changed, left, top, right, bottom)
+
+ if (!changed) {
+ return
+ }
+
+ // ensure we resize the overlay container to be the same size as our AppBar/Toolbar size
+ val w = binding.appbar.measuredWidth
+ val h = binding.appbar.measuredHeight
+
+ binding.mainContainerOverlay.updateLayoutParams {
+ this.width = w
+ this.height = h
+ }
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+
+ // resize the AppBar to be the maximum height - which is -
+ binding.appbar.measure(
+ widthMeasureSpec,
+ MeasureSpec.makeMeasureSpec(
+ measuredHeight - toPx(closedSheetHeight).roundToInt(),
+ MeasureSpec.EXACTLY
+ )
+ )
+ }
+
+ fun setup(
+ mainView: View,
+ sheetView: View
+ ) {
+ this.mainView = mainView
+ this.sheetView = sheetView
+ setup()
+ }
+
+ private fun setup() {
+ updateShowing(false)
+
+
+ mainView?.let {
+ binding.mainContainerOverlay.addView(mainView)
+ }
+
+
+ sheetView?.let {
+ // it, -1, -2
+ binding.sheetView.addView(it)
+ }
+ }
+
+ override fun addView(child: View, index: Int, params: ViewGroup.LayoutParams) {
+ if (binding.root == child) {
+ super.addView(child, index, params)
+ return
+ }
+ val lp = child.layoutParams as? LayoutParams ?: run {
+ LayoutParams(child.layoutParams ?: generateDefaultLayoutParams())
+ }
+
+ val position = lp.position
+
+ if (mainView == null && position == Position.Undefined) {
+ mainView = child
+ return
+ }
+
+ if (sheetView == null && position == Position.Undefined) {
+ sheetView = child
+ setup()
+ return
+ }
+
+ }
+
+ enum class Position(internal val value: Int) {
+ Undefined(-1),
+ Top(0),
+ Bottom(1);
+
+ companion object {
+ internal fun fromInt(value: Int): Position? {
+ return when (value) {
+ -1 -> Undefined
+ 0 -> Top
+ 1 -> Bottom
+ else -> null
+ }
+ }
+
+ internal fun fromIntOrDefault(value: Int?, def: Position): Position {
+ return fromInt(value ?: -2) ?: def
+ }
+ }
+ }
+
+ override fun generateLayoutParams(attrs: AttributeSet): LayoutParams {
+ return LayoutParams(context, attrs)
+ }
+
+ override fun generateDefaultLayoutParams(): LayoutParams {
+ return LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ }
+
+ override fun generateLayoutParams(p: ViewGroup.LayoutParams?): ViewGroup.LayoutParams {
+ return LayoutParams(p)
+ }
+
+ override fun checkLayoutParams(p: ViewGroup.LayoutParams?): Boolean {
+ return p is LayoutParams
+ }
+
+ class LayoutParams : FrameLayout.LayoutParams {
+ internal var numericAttributes = SparseArray()
+ internal var stringAttributes = SparseArray()
+
+
+ constructor(width: Int, height: Int) : super(width, height) {
+ numericAttributes.append(R.styleable.SplitSheet_Layout_sheet_position, -1F)
+ }
+
+ constructor(source: ViewGroup.LayoutParams) : super(source) {
+ if (source is LayoutParams) {
+ numericAttributes = source.numericAttributes.clone()
+ stringAttributes = source.stringAttributes.clone()
+ } else {
+ numericAttributes.append(R.styleable.SplitSheet_Layout_sheet_position, -1F)
+ }
+ }
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
+ val a = context.obtainStyledAttributes(attrs, R.styleable.SplitSheet_Layout)
+
+ numericAttributes.append(R.styleable.SplitSheet_Layout_sheet_position, -1F)
+
+ val attributeCount = a.indexCount
+ for (i in 0 until attributeCount) {
+ val attribute = a.getIndex(i)
+ val typedValue = TypedValue()
+ a.getValue(attribute, typedValue)
+ when (typedValue.type) {
+ TypedValue.TYPE_DIMENSION -> {
+ numericAttributes.put(
+ attribute, a.getDimensionPixelSize(attribute, 0).toFloat()
+ )
+ }
+ TypedValue.TYPE_STRING -> {
+ stringAttributes.put(attribute, a.getString(attribute))
+ }
+ else -> {
+ numericAttributes.put(attribute, a.getFloat(attribute, 0f))
+ }
+ }
+ }
+
+ a.recycle()
+ }
+
+ var position: Position
+ get() {
+ return Position.fromIntOrDefault(
+ numericAttributes.get(R.styleable.SplitSheet_Layout_sheet_position)
+ .roundToInt(),
+ Position.Undefined
+ )
+ }
+ set(value) {
+ numericAttributes.put(
+ R.styleable.SplitSheet_Layout_sheet_position,
+ value.value.toFloat()
+ )
+ }
+ }
+
+ private fun scroll(amount: Int) {
+ val params = binding.appbar.layoutParams as CoordinatorLayout.LayoutParams
+ val behavior = params.behavior as AppBarLayout.Behavior?
+ if (behavior != null) {
+
+ val valueAnimator = ValueAnimator.ofInt()
+ valueAnimator.interpolator = DecelerateInterpolator()
+ valueAnimator.addUpdateListener { animation ->
+ behavior.topAndBottomOffset = (animation.animatedValue as Int)
+ binding.appbar.requestLayout()
+ }
+ valueAnimator.doOnEnd {
+ val showing = amount == toPx(-minimumSheetHeight).roundToInt()
+ this.showing = showing
+ eventListener?.event("showing", showing)
+ }
+ valueAnimator.setIntValues(behavior.topAndBottomOffset, amount)
+ valueAnimator.duration = (animationDuration * 1000).roundToLong()
+ valueAnimator.start()
+ }
+ }
+
+ fun show(shouldShow: Boolean) {
+ if (shouldShow == this.showing) {
+ return
+ }
+ this.showing = shouldShow
+
+ val scrollAmount = if (shouldShow) {
+ toPx(minimumSheetHeight)
+ } else {
+ toPx(closedSheetHeight)
+ }.roundToInt()
+
+ scroll(-scrollAmount)
+
+ // binding.scrollView.smoothScrollTo(0, 300)
+ // binding.appbar.setExpanded(!shouldShow)
+
+ /*
+ if (shouldShow && state == Detents.Shown || !shouldShow && state == Detents.Hidden) {
+ return
+ }
+ animation?.cancel()
+ animation = null
+
+ val interpolator = this.interpolator
+ val minimumSheetHeight = toPx(minimumSheetHeight).roundToInt()
+ val closedSheetHeight = toPx(closedSheetHeight).roundToInt()
+
+ val toSize = if (shouldShow) {
+ minimumSheetHeight
+ } else {
+ closedSheetHeight
+ }
+
+ val scrollAnimation = ObjectAnimator.ofInt(this, "scrollY", toSize)
+ .apply {
+ addUpdateListener {
+ val value = (it.animatedValue as Int)
+ mainContainerView.translationY = value.toFloat()
+ sheetContainerView.translationY = value.toFloat()
+ }
+ }
+
+ val mainContainerViewHeight = mainContainerView.height
+ val newHeight = height - toSize
+ val heightAnimation = ObjectAnimator.ofInt(
+ mainContainerViewHeight,
+ newHeight
+ ).apply {
+ addUpdateListener {
+ val value = (it.animatedValue as Int)
+ val params = mainContainerView.layoutParams
+ params.height = value
+ mainContainerView.layoutParams = params
+ }
+ }
+
+ animation = AnimatorSet()
+ .apply {
+ playTogether(scrollAnimation, heightAnimation)
+ this.interpolator = interpolator
+ duration = (animationDuration * 1000).roundToLong()
+ doOnEnd {
+ showing = shouldShow
+ state = if (shouldShow) Detents.Shown else Detents.Hidden
+ eventListener?.event("showing", showing)
+
+ }
+ }
+
+
+ animation?.start()
+
+ */
+
+ }
+
+ private fun updateShowing(showing: Boolean) {
+ this.showing = showing
+
+ eventListener?.event("showing", showing)
+
+ /// If `swipeUpToShowAllowed` is not enabled, prevent scrolling up when hidden.
+ if (!swipeUpToShowAllowed) {
+ mScrollable = showing
+ }
+
+ if (showHandle) {
+ binding.handleView.visibility = View.VISIBLE
+ } else {
+ binding.handleView.visibility = View.INVISIBLE
+ }
+ }
+}
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/src/main/res/drawable/handle_drawable.xml b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/src/main/res/drawable/handle_drawable.xml
new file mode 100644
index 00000000..94a48fed
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/src/main/res/drawable/handle_drawable.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/src/main/res/layout/splitsheet.xml b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/src/main/res/layout/splitsheet.xml
new file mode 100644
index 00000000..cd55d17f
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/src/main/res/layout/splitsheet.xml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/src/main/res/values/attr.xml b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/src/main/res/values/attr.xml
new file mode 100644
index 00000000..35a7fa66
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/src/main/res/values/attr.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/src/main/res/values/colors.xml b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/src/main/res/values/colors.xml
new file mode 100644
index 00000000..c8ea254d
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/src/main/res/values/colors.xml
@@ -0,0 +1,7 @@
+
+
+ #00000000
+ #FF0000
+ #00FF00
+ #0000FF
+
\ No newline at end of file
diff --git a/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/src/test/java/io/github/triniwiz/splitsheet/ExampleUnitTest.kt b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/src/test/java/io/github/triniwiz/splitsheet/ExampleUnitTest.kt
new file mode 100644
index 00000000..1e4fe518
--- /dev/null
+++ b/packages/nativescript-splitsheet/src-native/SplitSheetDemo/splitsheet/src/test/java/io/github/triniwiz/splitsheet/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package io.github.triniwiz.splitsheet
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/packages/nativescript-splitsheet/tsconfig.json b/packages/nativescript-splitsheet/tsconfig.json
new file mode 100644
index 00000000..aed7323d
--- /dev/null
+++ b/packages/nativescript-splitsheet/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "outDir": "../../dist/out-tsc",
+ "rootDir": "."
+ },
+ "exclude": ["**/*.spec.ts", "**/*.test.ts", "angular"],
+ "include": ["**/*.ts", "references.d.ts"]
+}
diff --git a/tools/demo/index.ts b/tools/demo/index.ts
index 63d054ea..6ed727ae 100644
--- a/tools/demo/index.ts
+++ b/tools/demo/index.ts
@@ -22,5 +22,6 @@ export * from './nativescript-onfido';
export * from './nativescript-persona';
export * from './nativescript-plaid';
export * from './nativescript-qr';
+export * from './nativescript-splitsheet';
export * from './nativescript-tracking-transparency';
export * from './nativescript-walletconnect';
diff --git a/tools/demo/nativescript-splitsheet/index.ts b/tools/demo/nativescript-splitsheet/index.ts
new file mode 100644
index 00000000..d6f8842e
--- /dev/null
+++ b/tools/demo/nativescript-splitsheet/index.ts
@@ -0,0 +1,9 @@
+import { DemoSharedBase } from '../utils';
+import { } from '@nstudio/nativescript-splitsheet';
+
+export class DemoSharedNativescriptSplitsheet extends DemoSharedBase {
+
+ testIt() {
+ console.log('test nativescript-splitsheet!');
+ }
+}
\ No newline at end of file
diff --git a/tools/workspace-scripts.js b/tools/workspace-scripts.js
index e03ba77c..f58fa48e 100644
--- a/tools/workspace-scripts.js
+++ b/tools/workspace-scripts.js
@@ -290,6 +290,13 @@ module.exports = {
description: '@nstudio/nativescript-cardview: Build',
},
},
+ // @nstudio/nativescript-splitsheet
+ 'nativescript-splitsheet': {
+ build: {
+ script: 'nx run nativescript-splitsheet:build.all',
+ description: '@nstudio/nativescript-splitsheet: Build',
+ },
+ },
'build-all': {
script: 'nx run-many --target=build.all --all',
description: 'Build all packages',
@@ -404,6 +411,10 @@ module.exports = {
script: 'nx run nativescript-cardview:focus',
description: 'Focus on @nstudio/nativescript-cardview',
},
+ 'nativescript-splitsheet': {
+ script: 'nx run nativescript-splitsheet:focus',
+ description: 'Focus on @nstudio/nativescript-splitsheet',
+ },
reset: {
script: 'nx g @nativescript/plugin-tools:focus-packages',
description: 'Reset Focus',
diff --git a/tsconfig.base.json b/tsconfig.base.json
index fbcf0c85..2e0bf4bf 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -107,6 +107,9 @@
],
"@nstudio/nativescript-cardview": [
"packages/nativescript-cardview/index.d.ts"
+ ],
+ "@nstudio/nativescript-splitsheet": [
+ "packages/nativescript-splitsheet/index.d.ts"
]
}
},
diff --git a/workspace.json b/workspace.json
index e786957c..b8c6ec67 100644
--- a/workspace.json
+++ b/workspace.json
@@ -30,7 +30,8 @@
"nativescript-qr": "packages/nativescript-qr",
"nativescript-tracking-transparency": "packages/nativescript-tracking-transparency",
"nativescript-walletconnect": "packages/nativescript-walletconnect",
- "nativescript-cardview": "packages/nativescript-cardview"
+ "nativescript-cardview": "packages/nativescript-cardview",
+ "nativescript-splitsheet": "packages/nativescript-splitsheet"
},
"$schema": "./node_modules/nx/schemas/workspace-schema.json"
}