Sunday, February 2, 2014

Pattern Lock for Windows Phone

Do you have a security / privacy application? You need to lock your application's screen to provide secure access? Well, I am writing a series of open source control which will help you do just that!

In this first article, I am going to cover how to create a Pattern Lock or Swipe Lock, that you might have seen quite often on Android platform. This is my second effort to port such functionality from Android platform or other platforms. First up is Notification Notch Control.

                                              Image 1. A sample for swipe lock

Well, I'll try to create something which blends nicely with Modern UI a.k.a Metro.

                                           Image 2A sample for WP swipe lock

PREREQUISITE:  


1. Knowledge of MVVM in .Net.
2. MVVM Light Toolkit.

                                   Image 3. Snapshot for Project's core component


LETS START BUILDING THE CONTROL: 

We will quickly watch for the core STEPS to build this control.

CREATE RECTANGULAR COMPONENT:

Our main component is Rectangle (which fits well in Metro design) as shown in image  2.


Image 4Rectangle when not selected                            Image 5Rectangle when selected 

Following code is used to build square component:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
using System.Windows;
using System.Windows.Controls;

namespace PatternLockControl.Code
{
    public class SquarePattern : PatternDesignBase
    {
        #region Data
        #region SquareProperty
        private Border _Square;
        public Border Square
        {
            get
            { return _Square; }
            set
            {
                if (_Square == value)
                {
                    return;
                }
                _Square = value;
                RaisePropertyChanged("Square");
            }
        }
        #endregion

        /// 
        /// Sets or gets the Zindex Value for Icon
        /// 
        public override int ZIndexVal
        {
            get { return (int)Square.GetValue(Canvas.ZIndexProperty); }
            set { Square.SetValue(Canvas.ZIndexProperty, value); }
        }

        #region BorderColProperty
        private Color _BorderCol;
        public Color BorderCol
        {
            get
            { return _BorderCol; }
            set
            {
                if (_BorderCol == value)
                {
                    return;
                }
                _BorderCol = value;
                RaisePropertyChanged("BorderCol");
                BorderBrush = new SolidColorBrush(_BorderCol);
            }
        }
        #endregion

        #region BorderBrushProperty
        private SolidColorBrush _BorderBrush;
        public SolidColorBrush BorderBrush
        {
            get
            { return _BorderBrush; }
            private set
            {
                if (_BorderBrush == value)
                {
                    return;
                }
                _BorderBrush = value;
                RaisePropertyChanged("BorderBrush");
            }
        }
        #endregion

        public int Row { get; set; }

        public int Column { get; set; }
        #endregion

        #region Constructor
        public SquarePattern(int row, int column, int width, int value, Color fillNormal, Color fillSelected, Color borderCol)
            : base(fillNormal, fillSelected, value)
        {
            this.FillColNormal = fillNormal;
            this.BorderCol = borderCol;
            this.Row = row;
            this.Column = column;
            Square = new Border() { Name = string.Concat(row, column), Width = width, Height = width, Background = this.FillBrushNormal, BorderBrush = BorderBrush, BorderThickness = new Thickness(2) };
            ZIndexVal = 1;
        } 
        #endregion

        #region Methods
        public override void GridPosition(int row, int col)
        {
            Square.SetValue(Grid.RowProperty, row);
            Square.SetValue(Grid.ColumnProperty, col);
        }
        public override void MarkAsSelected()
        {
            this.Square.Background = FillBrushSelected;
        }
        #endregion
    }
}

Note: This class inherits from PatternDesignBase (abstract) class. PatternDesignBase work as a base class for such controls which could have different core items like Square (in current sample), Triangle, Circles, etc.

CREATE LAYOUT ROOT :

This control's layout will be hosted in a UserControl which also implements it's  core functionality for Pattern Lock Control.

Full code can be found in PatternLockMetroControl.cs and PatternLockMetroControl.xaml. I am going to list down important functionalities from it.
  1. InitializeAnimateGrid. 
  2. DrawLine.
  3. AddPolygon: . 
InitializeAnimateGrid
This method is responsible to initializing SquarePatterns and placing them into a Grid control which collectively forms core of this control

private void InitializeAnimateGrid()
        {
            LockPatterShapes = new ObservableCollection();
            var chromeColor = (this.Resources["PhoneAccentBrush"] as SolidColorBrush).Color;
            int pattVal = 0;
            for (int row = 0, maxRows = RootContainer.RowDefinitions.Count; row < maxRows; ++row)
            {
                for (int col = 0, maxCols = RootContainer.ColumnDefinitions.Count; col < maxCols; ++col)
                {
                    var LockPatterShape = new SquarePattern(row, col, 100, pattVal++, Colors.Black, chromeColor, chromeColor);
                    LockPatterShape.GridPosition(row, col);
                    RootContainer.Children.Add(LockPatterShape.Square);
                    LockPatterShape.Square.MouseEnter += SquarePattern_MouseMoveEvent;
                    LockPatterShape.Square.MouseLeftButtonUp += Square_MouseLeftButtonUp;
                    LockPatterShapes.Add(LockPatterShape);
                }
            }
        }

DrawLine
This methods holds the responsibility to draw lines between two square pattern, as we continue to swipe. There are certain methods available in LineHelper class which helps to complete the task by this method. This method comes into action when user starts moving his fingers on screen. I've captured the sample code for a quick reference here.

private void DrawLine()
        {
            if (MyPattern.Count < 2) return;
            if (_MouseMoveLine != null)
            {
                RootContainer.Children.Remove(_MouseMoveLine);
                _MouseMoveLine = null;
            }

            Point ptDest, ptSource;
            var sqPattEnd = MyPattern.LastOrDefault().Value;
            var sqPattStart = MyPattern.ElementAt(MyPattern.Count - 2).Value;

            //Find points to draw line from start and end.
            ptDest = LineHelper.GetPtRelativeToQuadrant(RootContainer, sqPattEnd.Square, LineHelper.FindQuadrantToDrawLineBW(sqPattEnd.Row, sqPattEnd.Column, sqPattStart.Row, sqPattStart.Column));
            ptSource = LineHelper.GetPtRelativeToQuadrant(RootContainer, sqPattStart.Square, LineHelper.FindQuadrantToDrawLineBW(sqPattStart.Row, sqPattStart.Column, sqPattEnd.Row, sqPattEnd.Column));

            Line line;
            LineType lType = LineHelper.FindLineType(sqPattStart.Row, sqPattStart.Column, sqPattEnd.Row, sqPattEnd.Column);

            if (lType == LineType.SIMPLE)
                line = LineHelper.CreateLine(ptSource, ptDest, DragLineWidth, (this.Resources["PhoneAccentBrush"] as SolidColorBrush), ZIdxForDrawnLines++, RootContainer, lType);
            else
                line = LineHelper.CreateLine(ptSource, ptDest, DottedLineWidth, SelectedLineBrush, ZIdxForDrawnLines++, RootContainer, lType);
            AddPolygon(MyPattern.ElementAt(MyPattern.Count - 2).Value.Square, LineHelper.FindQuadrantToDrawLineBW(sqPattStart.Row, sqPattStart.Column, sqPattEnd.Row, sqPattEnd.Column));

            RootContainer.Children.Add(line);
        }



AddPolygon
This method generates tiny white colored triangle. These little triangles indicate the direction of swipe when user swipes it's fingers from one rectangle to another. These triangles will only be visible when they have moved from one rectangle to another. Logic to identify where the triangles to be drawn is written inside GetPoinstForPolygon(). A sample code for AddPolygon is referenced here:

private void AddPolygon(Border shape, LineQuadrant quad)
        {
            Polygon poly = new Polygon();
            poly.Fill = new SolidColorBrush(Colors.White);
            poly.Points = LineHelper.GetPointsForPolygon(shape.ActualWidth, quad, poly);
            shape.BorderThickness = new Thickness(0);
            shape.Child = poly;
            //Set the Max ZIdx for poly/Border so that it will always be on top of all drawn lines
            shape.SetValue(Canvas.ZIndexProperty, ZIdxForDrawnLines + 1);
        }

That's it!! We are DONE!!!


OTHER IMPORTANT FEATURES / INFO:

1. Registration Mode : We can easily configure this control to register a new pattern or to use verify an existing pattern
2. Decoy Pattern : This feature provides an additional flexibility to register additional set of pattern. This will be helpful in case you want your application to support multiple access mode say one for Admin and another for User mode. 
3. Pattern Format : Based on your selection i.e. Registration Mode or Verify Pattern mode this control will generate an event to publish the result. In case of Registration mode if your selection is as per Image 6 then it will return a List of integer [1,2,3,6,9].

                                           Image 6A sample selection for result.


VIDEO

No comments:

Post a Comment