Skip to content

Commit

Permalink
Add first draft version
Browse files Browse the repository at this point in the history
  • Loading branch information
robinmanuelthiel committed Mar 24, 2017
1 parent ab463b6 commit 9bdd061
Show file tree
Hide file tree
Showing 98 changed files with 8,077 additions and 0 deletions.
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#Introduction
TODO: Give a short introduction of your project. Let this section explain the objectives or the motivation behind this project.

#Getting Started
TODO: Guide users through getting your code up and running on their own system. In this section you can talk about:
1. Installation process
2. Software dependencies
3. Latest releases
4. API references

#Build and Test
TODO: Describe and show how to build your code and run the tests.

#Contribute
TODO: Explain how other users and developers can contribute to make your code better.

If you want to learn more about creating good readme files then refer the following [guidelines](https://www.visualstudio.com/en-us/docs/git/create-a-readme). You can also seek inspiration from the below readme files:
- [ASP.NET Core](https://github.com/aspnet/Home)
- [Visual Studio Code](https://github.com/Microsoft/vscode)
- [Chakra Core](https://github.com/Microsoft/ChakraCore)
13 changes: 13 additions & 0 deletions SwipeCards.Controls/Models/Card.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SwipeCards.Controls.Models
{
public class Card
{
public string Text { get; set; }
}
}
27 changes: 27 additions & 0 deletions SwipeCards.Controls/MyPage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Xamarin.Forms;

namespace SwipeCards.Controls
{
public class MyPage : ContentPage
{
public MyPage()
{
var button = new Button
{
Text = "Click Me!",
VerticalOptions = LayoutOptions.CenterAndExpand,
HorizontalOptions = LayoutOptions.CenterAndExpand,
};

int clicked = 0;
button.Clicked += (s, e) => button.Text = "Clicked: " + clicked++;

Content = button;
}
}
}
26 changes: 26 additions & 0 deletions SwipeCards.Controls/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Reflection;
using System.Runtime.CompilerServices;

// Information about this assembly is defined by the following attributes.
// Change them to the values specific to your project.

[assembly: AssemblyTitle("SwipeCards.Controls")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("(c) Robin-Manuel Thiel")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
// and "{Major}.{Minor}.{Build}.*" will update just the revision.

[assembly: AssemblyVersion("1.0.*")]

// The following attributes are used to specify the signing key for the assembly,
// if desired. See the Mono documentation for more information about signing.

//[assembly: AssemblyDelaySign(false)]
//[assembly: AssemblyKeyFile("")]
68 changes: 68 additions & 0 deletions SwipeCards.Controls/SwipeCards.Controls.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{73CC4C24-1BD0-49E3-AE34-360D404B8BA0}</ProjectGuid>
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<UseMSBuildEngine>true</UseMSBuildEngine>
<OutputType>Library</OutputType>
<RootNamespace>SwipeCards.Controls</RootNamespace>
<AssemblyName>SwipeCards.Controls</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<TargetFrameworkProfile>Profile111</TargetFrameworkProfile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Models\Card.cs" />
<Compile Include="Views\CardStackView.xaml.cs">
<DependentUpon>..\..\SwipeCards.Demo.Forms\Views\CardStackView.xaml</DependentUpon>
</Compile>
<Compile Include="Views\CardView.xaml.cs">
<DependentUpon>..\..\SwipeCards.Demo.Forms\Views\CardView.xaml</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<Reference Include="Xamarin.Forms.Core">
<HintPath>..\packages\Xamarin.Forms.2.3.3.193\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Core.dll</HintPath>
</Reference>
<Reference Include="Xamarin.Forms.Platform">
<HintPath>..\packages\Xamarin.Forms.2.3.3.193\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Platform.dll</HintPath>
</Reference>
<Reference Include="Xamarin.Forms.Xaml">
<HintPath>..\packages\Xamarin.Forms.2.3.3.193\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Xaml.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Folder Include="Models\" />
<Folder Include="Views\" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Views\CardStackView.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
<EmbeddedResource Include="Views\CardView.xaml">
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<Import Project="..\packages\Xamarin.Forms.2.3.3.193\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets" Condition="Exists('..\packages\Xamarin.Forms.2.3.3.193\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets')" />
</Project>
21 changes: 21 additions & 0 deletions SwipeCards.Controls/Views/CardStackView.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ContentView
x:Class="SwipeCards.Controls.CardStackView"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
Padding="20">

<Grid>
<RelativeLayout x:Name="CardStack" />

<!--
HACK: The TouchObserber is a terrible hack, so let me explain the problem
On Android, Child elements obserb touch events and don't pass them to the sender.
So when attaching the PanGestureRecognizer to the parent view, it won't fire, when dragging the child.
To fix this temporary, I added this transparent view on top of all others and attach the PanGestureRecognizer to it.
A custom view renderer with an "IgnoreTouch" property might be a smarter solution...
-->
<BoxView x:Name="TouchObserber" BackgroundColor="Transparent" />
</Grid>

</ContentView>
207 changes: 207 additions & 0 deletions SwipeCards.Controls/Views/CardStackView.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
using System;
using System.Collections.Generic;
using Xamarin.Forms;
using SwipeCards.Controls.Models;
using System.Collections;
using System.Threading.Tasks;
using System.Windows.Input;

namespace SwipeCards.Controls
{
public partial class CardStackView : ContentView
{
public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource), typeof(IList), typeof(CardStackView), null, propertyChanged: (bindable, oldValue, newValue) => ((CardStackView)bindable).Setup());
public IList ItemsSource
{
get { return (IList)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}

public static readonly BindableProperty CardMoveDistanceProperty = BindableProperty.Create(nameof(CardMoveDistance), typeof(int), typeof(CardStackView), 0);
public int CardMoveDistance
{
get { return (int)GetValue(CardMoveDistanceProperty); }
set { SetValue(CardMoveDistanceProperty, value); }
}

public static BindableProperty SwipedRightCommandProperty = BindableProperty.Create(nameof(SwipedRightCommand), typeof(ICommand), typeof(CardStackView), null);
public ICommand SwipedRightCommand
{
get { return (ICommand)GetValue(SwipedRightCommandProperty); }
set { SetValue(SwipedRightCommandProperty, value); }
}

public static BindableProperty SwipedLeftCommandProperty = BindableProperty.Create(nameof(SwipedLeftCommand), typeof(ICommand), typeof(CardStackView), null);
public ICommand SwipedLeftCommand
{
get { return (ICommand)GetValue(SwipedLeftCommandProperty); }
set { SetValue(SwipedLeftCommandProperty, value); }
}

// Called when a card is swiped left/right
public Action<object> SwipedRight = null;
public Action<object> SwipedLeft = null;



private const int numberOfCards = 2;
private const int animationLength = 250;

private CardView[] cardViews = new CardView[numberOfCards];
private float defaultSubcardScale = 0.8f;
private float cardDistance = 0;
private int itemIndex = 0;

public CardStackView()
{
InitializeComponent();

// Add two cards to stack
// Use inverse direction to ensure that first card is on top
for (var i = numberOfCards - 1; i >= 0; i--)
{
// Create CardView
var cardView = new CardView(i);
cardView.IsVisible = false;
cardViews[i] = cardView;
cardView.IsEnabled = false;

// Add CardView to UI
CardStack.Children.Add(
cardView,
Constraint.Constant(0), // X
Constraint.Constant(0), // Y
Constraint.RelativeToParent((parent) => { return parent.Width; }), // Width
Constraint.RelativeToParent((parent) => { return parent.Height; }) // Height
);
}

// Register pan gesture
var panGesture = new PanGestureRecognizer();
panGesture.PanUpdated += OnPanUpdated;
TouchObserber.GestureRecognizers.Add(panGesture);
}

async void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
switch (e.StatusType)
{
case GestureStatus.Started:
//HandleTouchStart();
break;
case GestureStatus.Running:
HandleTouchRunning((float)e.TotalX);
break;
case GestureStatus.Completed:
await HandleTouchCompleted();
break;
}
}

private void Setup()
{
itemIndex = 0;
ShowNextCard();
}

protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height);

// Recalculate move distance
CardMoveDistance = (int)(width / 3);
}

private void HandleTouchRunning(float xDiff)
{
var topCard = cardViews[0];
var backCard = cardViews[1];

// Move the top card
if (topCard.IsVisible)
{
// Move the card
topCard.TranslationX = (xDiff);

// Calculate a angle for the card
float rotationAngel = (float)(0.3f * Math.Min(xDiff / this.Width, 1.0f));
topCard.Rotation = rotationAngel * 57.2957795f;

// Keep a record of how far its moved
cardDistance = xDiff;
}

// Scale the backcard
if (backCard.IsVisible)
{
backCard.Scale = Math.Min(defaultSubcardScale + Math.Abs((cardDistance / CardMoveDistance) * (1.0f - defaultSubcardScale)), 1.0f);
}
}

private async Task HandleTouchCompleted()
{
var topCard = cardViews[0];
var backCard = cardViews[1];

// Check if card has been dragged far enough to trigger action
if (Math.Abs(cardDistance) >= CardMoveDistance)
{
// Move card off the screen
await topCard.TranslateTo(cardDistance > 0 ? this.Width : -this.Width, 0, animationLength / 2, Easing.SpringOut);
topCard.IsVisible = false;

// Fire events
if (cardDistance > 0)
{
if (SwipedRight != null)
SwipedRight(topCard.Item);
if (SwipedRightCommand != null && SwipedRightCommand.CanExecute(topCard.Item))
SwipedRightCommand.Execute(topCard.Item);
}
else
{
if (SwipedLeft != null)
SwipedLeft(topCard.Item);
if (SwipedLeftCommand != null && SwipedLeftCommand.CanExecute(topCard.Item))
SwipedLeftCommand.Execute(topCard.Item);
}

// Next card
itemIndex++;
ShowNextCard();
}
else
{
// Move card back to the center
var traslateAnmimation = topCard.TranslateTo((-topCard.X), -topCard.Y, animationLength, Easing.SpringOut);
var rotateAnimation = topCard.RotateTo(0, animationLength, Easing.SpringOut);

// Scale the back card down
var scaleAnimation = backCard.ScaleTo(defaultSubcardScale, animationLength, Easing.SpringOut);

await Task.WhenAll(new List<Task> { traslateAnmimation, rotateAnimation, scaleAnimation });
}
}

private void ShowNextCard()
{
for (int i = 0; i < Math.Min(numberOfCards, ItemsSource.Count); i++)
{
var cardView = cardViews[i];
cardView.IsVisible = false;
cardView.Scale = (i == 0) ? 1.0f : defaultSubcardScale;
cardView.Rotation = 0;
cardView.TranslationX = 0;

// Check if next item is available
if (itemIndex + i < ItemsSource.Count)
{
cardView.Item = ItemsSource[itemIndex + i];
cardView.UpdateUi();

cardView.IsVisible = true;
}
}
}
}
}
Loading

0 comments on commit 9bdd061

Please sign in to comment.