﻿B4i=true
Group=Default Group
ModulesStructureVersion=1
Type=Class
Version=8.9
@EndOfDesignText@
' SwipeCard.bas  (B4X Class) — smooth drag + filtered rotation
'Aliakrami13751@gmail.com

#DesignerProperty: Key: ThresholdPct, DisplayName: Swipe Threshold % (0.1-0.8), FieldType: Float, DefaultValue: 0.25
#DesignerProperty: Key: SnapDuration, DisplayName: Snap Duration (ms), FieldType: Int, DefaultValue: 220
#DesignerProperty: Key: FlingDuration, DisplayName: Fling Duration (ms), FieldType: Int, DefaultValue: 180
#DesignerProperty: Key: StackMax, DisplayName: Max Visible Stack, FieldType: Int, DefaultValue: 3
#DesignerProperty: Key: StackOffsetX, DisplayName: Stack Offset X (dip), FieldType: Int, DefaultValue: 3
#DesignerProperty: Key: StackOffsetY, DisplayName: Stack Offset Y (dip), FieldType: Int, DefaultValue: 6
#DesignerProperty: Key: CardCorner, DisplayName: Card Corner Radius (dip), FieldType: Int, DefaultValue: 10
#DesignerProperty: Key: CardBG, DisplayName: Card Background, FieldType: Color, DefaultValue: 0xFFFFFFFF

Sub Class_Globals
	Private mEventName As String
	Private mCallBack As Object
	Public mBase As B4XView
	Private xui As XUI

	' Settings
	Private ThresholdPct As Float = 0.25
	Private SnapDuration As Int = 220
	Private FlingDuration As Int = 180
	Private StackMax As Int = 3
	Private StackOffsetX As Int = 3dip
	Private StackOffsetY As Int = 6dip
	Private CardCorner As Int = 10dip
	Private CardBG As Int = 0xFFFFFFFF

	' Items
	Type TItem (Id As String, Panel As B4XView)
	Private Items As List
	Private CurrentIndex As Int = 0

	' Drag state
	Private downX As Float
	Private baseLeft As Float
	Private dragging As Boolean = False
	Private isAnimating As Boolean = False
	Private MaxRotation As Float = 12

	' --- Smoothness helpers ---
	Private lastMoveT As Long
	Private throttleMs As Int = 8        
	Private filteredLeft As Float
	Private filteredRot As Float
	Private lpfPos As Float = 0.22         
	Private lpfRot As Float = 0.22         
End Sub

Public Sub Initialize (Callback As Object, EventName As String)
	mCallBack = Callback
	mEventName = EventName
	Items.Initialize
End Sub

Public Sub DesignerCreateView (Base As Object, Lbl As Label, Props As Map)
	mBase = Base
	mBase.Visible = True
	If Props.ContainsKey("ThresholdPct") Then ThresholdPct = Max(0.05, Min(0.9, Props.Get("ThresholdPct")))
	If Props.ContainsKey("SnapDuration") Then SnapDuration = Props.Get("SnapDuration")
	If Props.ContainsKey("FlingDuration") Then FlingDuration = Props.Get("FlingDuration")
	If Props.ContainsKey("StackMax") Then StackMax = Max(1, Props.Get("StackMax"))
	If Props.ContainsKey("StackOffsetX") Then StackOffsetX = Props.Get("StackOffsetX")
	If Props.ContainsKey("StackOffsetY") Then StackOffsetY = Props.Get("StackOffsetY")
	If Props.ContainsKey("CardCorner") Then CardCorner = Props.Get("CardCorner")
	If Props.ContainsKey("CardBG") Then CardBG = Props.Get("CardBG")
End Sub

' =========================
'          API
' =========================

Public Sub AddLayout(LayoutFile As String, ItemId As String)
	Dim pnl As B4XView = xui.CreatePanel("card")
	pnl.Enabled = True
	pnl.Color = CardBG
	pnl.SetColorAndBorder(CardBG, 0, 0, CardCorner) 
	mBase.AddView(pnl, 0, 0, mBase.Width, mBase.Height)
	pnl.LoadLayout(LayoutFile)
	pnl.Visible = False

	Dim it As TItem
	it.Initialize
	it.Id = ItemId
	it.Panel = pnl
	Items.Add(it)
	UpdateStack
End Sub

Public Sub AddPanel(PanelToWrap As B4XView, ItemId As String)
	Dim pnl As B4XView = xui.CreatePanel("card")
	pnl.Enabled = True
	pnl.Color = CardBG
	pnl.SetColorAndBorder(CardBG, 0, 0, CardCorner)
	mBase.AddView(pnl, 0, 0, mBase.Width, mBase.Height)

	PanelToWrap.RemoveViewFromParent
	pnl.AddView(PanelToWrap, 0, 0, pnl.Width, pnl.Height)
	pnl.Visible = False

	Dim it As TItem
	it.Initialize
	it.Id = ItemId
	it.Panel = pnl
	Items.Add(it)
	UpdateStack
End Sub

Public Sub RemoveById(Id As String) As Boolean
	For i = 0 To Items.Size - 1
		Dim it As TItem = Items.Get(i)
		If it.Id = Id Then
			it.Panel.RemoveViewFromParent
			Items.RemoveAt(i)
			If i <= CurrentIndex And CurrentIndex > 0 Then CurrentIndex = CurrentIndex - 1
			UpdateStack
			Return True
		End If
	Next
	Return False
End Sub

Public Sub Clear
	For Each it As TItem In Items
		it.Panel.RemoveViewFromParent
	Next
	Items.Clear
	CurrentIndex = 0
End Sub

Public Sub Count As Int
	Return Items.Size
End Sub

Public Sub CurrentItemId As String
	If CurrentIndex >= 0 And CurrentIndex < Items.Size Then
		Dim it As TItem = Items.Get(CurrentIndex)
		Return it.Id
	Else
		Return ""
	End If
End Sub

Public Sub SetThresholdPct(p As Float)
	ThresholdPct = Max(0.05, Min(0.9, p))
End Sub

Public Sub SetDurations(snapMs As Int, flingMs As Int)
	SnapDuration = snapMs
	FlingDuration = flingMs
End Sub

Public Sub SetStack(maxVisible As Int, dx As Int, dy As Int)
	StackMax = Max(1, maxVisible)
	StackOffsetX = dx
	StackOffsetY = dy
	UpdateStack
End Sub

' =========================
'     Stack Layouting
' =========================
Private Sub UpdateStack
	If mBase.IsInitialized = False Then Return
	For Each it As TItem In Items
		it.Panel.Visible = False
		it.Panel.Rotation = 0
		it.Panel.Left = 0
		it.Panel.Top = 0
		it.Panel.Width = mBase.Width
		it.Panel.Height = mBase.Height
		If it.Panel.NumberOfViews > 0 Then
			Dim child As B4XView = it.Panel.GetView(0)
			child.SetLayoutAnimated(0, 0, 0, it.Panel.Width, it.Panel.Height)
		End If
	Next

	If Items.Size = 0 Or CurrentIndex > Items.Size - 1 Then Return

	Dim visible As Int = Min(StackMax, Items.Size - CurrentIndex)
	For i = 0 To visible - 1
		Dim it As TItem = Items.Get(CurrentIndex + i)
		it.Panel.Visible = True
		Dim l As Int = i * StackOffsetX
		Dim t As Int = i * StackOffsetY
		it.Panel.SetLayoutAnimated(0, l, t, mBase.Width - l * 2, mBase.Height - t * 2)
		If it.Panel.NumberOfViews > 0 Then
			Dim child2 As B4XView = it.Panel.GetView(0)
			child2.SetLayoutAnimated(0, 0, 0, it.Panel.Width, it.Panel.Height)
		End If
		it.Panel.BringToFront
	Next

	Dim top As TItem = Items.Get(CurrentIndex)
	top.Panel.BringToFront
End Sub

' =========================
'         Touch
' =========================
Private Sub card_Touch (Action As Int, X As Float, Y As Float)
	If Items.Size = 0 Or CurrentIndex > Items.Size - 1 Then Return
	If isAnimating Then Return

	Dim p As B4XView = Sender
	Dim top As TItem = Items.Get(CurrentIndex)
	If p <> top.Panel Then Return

	Select Action
		Case p.TOUCH_ACTION_DOWN
			dragging = True
			downX = X
			baseLeft = top.Panel.Left
			filteredLeft = baseLeft
			filteredRot = 0
			lastMoveT = DateTime.Now

		Case p.TOUCH_ACTION_MOVE
			If dragging = False Then Return
			' --- Throttle ---
			Dim nowT As Long = DateTime.Now
			If nowT - lastMoveT < throttleMs Then Return
			lastMoveT = nowT

			' --- Desired new left ---
			Dim dx As Float = X - downX
			Dim desiredLeft As Float = baseLeft + dx

			' --- Low-pass filter for smoother motion ---
			filteredLeft = filteredLeft + lpfPos * (desiredLeft - filteredLeft)
			top.Panel.Left = filteredLeft

			' Rotation smoothing
			Dim desiredRot As Float = MaxRotation * (top.Panel.Left / mBase.Width)
			filteredRot = filteredRot + lpfRot * (desiredRot - filteredRot)
			top.Panel.Rotation = filteredRot

		Case p.TOUCH_ACTION_UP
			If dragging = False Then Return
			dragging = False
			HandleRelease(top)
	End Select
End Sub

Private Sub HandleRelease(top As TItem)
	Dim w As Float = mBase.Width
	Dim left As Float = top.Panel.Left
	Dim absL As Float = Abs(left)
	Dim threshold As Float = w * ThresholdPct

	If absL >= threshold Then
		Dim dirRight As Boolean = (left > 0)
		Dim targetLeft As Float = IIf(dirRight, w + 50dip, -w - 50dip)
		isAnimating = True
		top.Panel.SetLayoutAnimated(FlingDuration, targetLeft, top.Panel.Top, top.Panel.Width, top.Panel.Height)
		Sleep(FlingDuration)
		isAnimating = False

		If dirRight Then
			RaiseEvent("RightSwipe", top.Id)
		Else
			RaiseEvent("LeftSwipe", top.Id)
		End If

		top.Panel.Visible = False
		top.Panel.RemoveViewFromParent
		Items.RemoveAt(CurrentIndex)
		UpdateStack
	Else
		isAnimating = True
		top.Panel.Rotation = 0
		top.Panel.SetLayoutAnimated(SnapDuration, 0, top.Panel.Top, top.Panel.Width, top.Panel.Height)
		Sleep(SnapDuration)
		isAnimating = False
		RaiseEvent("CenterRelease", top.Id)
	End If
End Sub

Private Sub RaiseEvent(suffix As String, Id As String)
	If xui.SubExists(mCallBack, mEventName & "_" & suffix, 1) Then
		CallSubDelayed2(mCallBack, mEventName & "_" & suffix, Id)
	End If
End Sub

Private Sub Base_Resize (Width As Double, Height As Double)
	UpdateStack
End Sub
