Monday, June 20, 2016

Google map api v3 with Ionic framework

My web was using Ionic framwork 1.x. After I read some threads and examples on the web, initiate the google map as following is the only way I can make the map show up.

index.html
<script src="http://maps.googleapis.com/maps/api/js?key=yourAPIkey&libraries=places" async defer></script>              

view html:
 <ion-header-bar class="bar bar-header bar-positive" ng-init="initMap()"></ion-header-bar>

<ion-content>          
       <div id="map" data-tap-disabled="true"></div>          
</ion-content>

javascript:
$scope.initMap = function() {
        $scope.directionsService = new google.maps.DirectionsService;
        $scope.directionsDisplay = new google.maps.DirectionsRenderer({
            suppressMarkers:true,
            preserveViewport: true
            //polylineOptions: { strokeColor: "red" }
        });
        var mylocation = new google.maps.LatLng('41.72', '-71.93');
        var grayStyles = [
            {   featureType: "all",
                elementType: "labels.text",
                stylers: [
                  { gamma: 0.01 }
                ]
            },
        ];
        var mapOptions = {
            center: mylocation,
            zoom: 11,
            styles: grayStyles,
            mapTypeId: google.maps.MapTypeId.ROADMAP
        };
        var map = new google.maps.Map(document.getElementById("map"),
            mapOptions);      
        $scope.directionsDisplay.setMap(map);  
        var trafficLayer = new google.maps.TrafficLayer();
        trafficLayer.setMap(map);

        marker = new google.maps.Marker({
            position: mylocation ,
            icon: "http://maps.google.com/mapfiles/kml/pal3/icon48.png",
            map: map
        });
        marker.infowindow = new google.maps.InfoWindow({                  
            content: 'My xxx Inc.'
        });              
        google.maps.event.addListener(marker, 'click', (function(marker) {
            return function() {                          
                    marker.infowindow.open(map, marker);
            }
        })(marker));  
     
        $scope.map = map;            
             
    };

Android Webview call Angular controller function.

Since the web is written with AngularJS 1.x, things are working with $scope that it does not look alike simple javascript; anyway, here is how I call a function declared in a controller successfully through Webview.

Android webview:
myWebView.loadUrl("javascript:drvAppDevicePosition('" + mLat + "','" + mLng + "');");


Javascript:
window.drvAppDevicePosition = function(a, b) {      
        $scope.$apply(function() {          
                console.log("Lat:"+a+"; Lng:"+b);
        });      
    };



Android - Capture Signature

Recently, I implemented an App ran on Android for capture signature.
There are some examples on the web that I can taken for reference.
I also wanna share mine here if someone is also looking for this.


public class CaptureSignature extends Activity {
    LinearLayout mContent;
    SignatureView mSignature;
    Button mClear, mGetSign, mCancel;
    public static String tempDir;
    public String current = null;
    private Bitmap mBitmap;
    View mView;
    File mypath;

    @Override    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.signature);
        getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, 
ViewGroup.LayoutParams.MATCH_PARENT);

        tempDir = Environment.getExternalStoragePublicDirectory( 
Environment.DIRECTORY_DOWNLOADS) 
+ "/" + getResources().getString(R.string.external_dir) + "/";

        ContextWrapper cw = new ContextWrapper(getApplicationContext());
        File directory = cw.getDir(getResources().getString(R.string.external_dir), 
Context.MODE_PRIVATE);

        prepareDirectory();
        current = "mySignaturre.png";

        mypath= new File(tempDir,current);

        mContent = (LinearLayout) findViewById(R.id.linearLayout);
        mSignature = new SignatureView(this, null);

        mSignature.setBackgroundColor(Color.WHITE);

        mContent.addView(mSignature);
        mClear = (Button)findViewById(R.id.clear);
        mGetSign = (Button)findViewById(R.id.getsign);
        mGetSign.setEnabled(false);
        mCancel = (Button)findViewById(R.id.cancel);
        mView = mContent;


        mClear.setOnClickListener(new OnClickListener()
        {
            public void onClick(View v)
            {
                mSignature.clear();
                mGetSign.setEnabled(false);
            }
        });

        mGetSign.setOnClickListener(new OnClickListener()
        {
            public void onClick(View v)
            {        
                boolean error = captureSignature();
                if(!error){
                    mView.setDrawingCacheEnabled(true);
                    mSignature.save(mView);
                    finish();
                }
            }
        });

        mCancel.setOnClickListener(new OnClickListener()
        {
            public void onClick(View v)
            {
            finish();
            }
        });

    }

    @Override    protected void onDestroy() {
        super.onDestroy();
    }

    private boolean captureSignature() {
        boolean error = false;
        String errorMessage = "";
        if(error){
        }
        return error;
    }

    private String getTodaysDate() {
        final Calendar c = Calendar.getInstance();
        int todaysDate =     (c.get(Calendar.YEAR) * 10000) +
                ((c.get(Calendar.MONTH) + 1) * 100) +
                (c.get(Calendar.DAY_OF_MONTH));
        return(String.valueOf(todaysDate));
    }

    private String getCurrentTime() {
        final Calendar c = Calendar.getInstance();
        int currentTime =     (c.get(Calendar.HOUR_OF_DAY) * 10000) +
                (c.get(Calendar.MINUTE) * 100) +
                (c.get(Calendar.SECOND));
        return(String.valueOf(currentTime));
    }

    private boolean prepareDirectory()
    {
        try {
            if (makedirs())
            {
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    private boolean makedirs()
    {
        File tempdir = new File(tempDir);
        if (!tempdir.exists())
            tempdir.mkdirs();

        return true;
    }

    public class SignatureView extends View
    {
        private static final float STROKE_WIDTH = 18f;
        private static final float HALF_STROKE_WIDTH = STROKE_WIDTH / 2;
        private Paint paint = new Paint();
        private Path path = new Path();

        private float lastTouchX;
        private float lastTouchY;
        private final RectF dirtyRect = new RectF();

        public SignatureView(Context context, AttributeSet attrs)
        {
            super(context, attrs);
            paint.setAntiAlias(true);
            paint.setColor(Color.BLACK);
            paint.setStyle(Paint.Style.STROKE);
            paint.setStrokeJoin(Paint.Join.ROUND);
            paint.setStrokeWidth(STROKE_WIDTH);
        }

        public void save(View v)
        {
            if(mBitmap == null)
            {
                mBitmap =  Bitmap.createBitmap (mContent.getWidth(), mContent.getHeight(), 
Bitmap.Config.RGB_565);
            }
            Canvas canvas = new Canvas(mBitmap);

            try            {
                //Remove the old Signature if exists.                
                boolean deleted = mypath.delete();
                FileOutputStream mFileOutStream = new FileOutputStream(mypath);

                v.draw(canvas);
                mBitmap.compress(Bitmap.CompressFormat.PNG, 90, mFileOutStream);
                mFileOutStream.flush();
                mFileOutStream.close();
                String url = Images.Media.insertImage(getContentResolver(), mBitmap, "title", 
null);

            }
            catch(Exception e)
            {
                Log.v("log_tag", e.toString());
            }
        }

        public void clear()
        {
            path.reset();
            invalidate();
        }

        @Override        protected void onDraw(Canvas canvas)
        {
            canvas.drawPath(path, paint);
        }

        @Override        public boolean onTouchEvent(MotionEvent event)
        {
            float eventX = event.getX();
            float eventY = event.getY();
            mGetSign.setEnabled(true);

            switch (event.getAction())
            {
                case MotionEvent.ACTION_DOWN:
                    path.moveTo(eventX, eventY);
                    lastTouchX = eventX;
                    lastTouchY = eventY;
                    return true;

                case MotionEvent.ACTION_MOVE:

                case MotionEvent.ACTION_UP:

                    resetDirtyRect(eventX, eventY);
                    int historySize = event.getHistorySize();
                    for (int i = 0; i < historySize; i++)
                    {
                        float historicalX = event.getHistoricalX(i);
                        float historicalY = event.getHistoricalY(i);
                        expandDirtyRect(historicalX, historicalY);
                        path.lineTo(historicalX, historicalY);
                    }
                    path.lineTo(eventX, eventY);
                    break;

                default:
                    debug("Ignored touch event: " + event.toString());
                    return false;
            }

            invalidate((int) (dirtyRect.left - HALF_STROKE_WIDTH),
                    (int) (dirtyRect.top - HALF_STROKE_WIDTH),
                    (int) (dirtyRect.right + HALF_STROKE_WIDTH),
                    (int) (dirtyRect.bottom + HALF_STROKE_WIDTH));

            lastTouchX = eventX;
            lastTouchY = eventY;

            return true;
        }

        private void debug(String string){
        }

        private void expandDirtyRect(float historicalX, float historicalY)
        {
            if (historicalX < dirtyRect.left)
            {
                dirtyRect.left = historicalX;
            }
            else if (historicalX > dirtyRect.right)
            {
                dirtyRect.right = historicalX;
            }

            if (historicalY < dirtyRect.top)
            {
                dirtyRect.top = historicalY;
            }
            else if (historicalY > dirtyRect.bottom)
            {
                dirtyRect.bottom = historicalY;
            }
        }

        private void resetDirtyRect(float eventX, float eventY)
        {
            dirtyRect.left = Math.min(lastTouchX, eventX);
            dirtyRect.right = Math.max(lastTouchX, eventX);
            dirtyRect.top = Math.min(lastTouchY, eventY);
            dirtyRect.bottom = Math.max(lastTouchY, eventY);
        }
    }

}





AndroidMainfest.xml:

<activity android:name=".CaptureSignature" android:label="Signature Confirmation"  
  android:screenOrientation="landscape"   
 android:theme="@android:style/Theme.Dialog" />


signature.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    
android:orientation="vertical" android:layout_width="match_parent"   
 android:layout_height="fill_parent">
    <LinearLayout android:layout_height="wrap_content"        
android:id="@+id/linearLayout2" android:layout_width="match_parent">
        <Button android:layout_height="50dp" android:layout_weight=".30"            
android:text="Cancel" android:layout_width="0dp" android:id="@+id/cancel" />
        <Button android:layout_height="50dp" android:layout_weight=".35"            
android:text="Clear" android:layout_width="0dp" android:id="@+id/clear" />
        <Button android:layout_height="50dp" android:layout_weight=".35"            
android:text="Confirm" android:layout_width="0dp" android:id="@+id/getsign" />
    </LinearLayout>
    <TableLayout android:layout_height="wrap_content"        
android:id="@+id/tableLayout1" android:layout_width="match_parent">
        <TableRow android:id="@+id/tableRow3" 
android:layout_width="wrap_content"            android:layout_height="wrap_content">
        </TableRow>
    </TableLayout>
    <LinearLayout android:layout_height="match_parent"        
android:orientation="vertical"        android:id="@+id/linearLayout" 
android:layout_width="match_parent" />
</LinearLayout>


Thursday, March 10, 2016

[Amazon/SNS] AWS SNS publish SMS PHP 5.6 Implementation

Recently, I decided to add a verification code thru SMS to validate the user login. After I the feasibility of how my project integrate this feature, I decided to give AWS SNS a try since I already got a few services from them.

Here I wanna share my experience of how to set this up and hope it helps someone.

My server environment for this setup: 
Windows 7 IIS 7.5, PHP 5.6.19, AWS SDK PHP v3


First, download and install AWS SDK PHP

my SDK PHP version is v3. and the minimum PHP version is 5.5

The first problem that came to me was when I tried to config OPCache following the Optimal Settings , I got Html error 500 when run phpinfo(). It took me some times to figure out the event log and it shown me Zend Opcache:
"Unable to reattach to base address
Attempt to access invalid address." 
For more information, you can see here.

Anyway, I finally followed the most easy fix, just set permission to 'everyone' on the temp folder, thanks to fedevegili the one who suggested.

Ok, before you can run the API the SDK provided successfully, you need to create an AWS account  of course, as well as getting the Access key ID and Secret for the authentication putting in your script. 

I created an user solely for all functions of SNS via IAM. There are several ways to configure your Credential File, I choose a file location I preferred instead of the default anyway, and setup like follows:

<?php
//these two lines placed on top of the script
use Aws\Credentials\CredentialProvider;
use Aws\Sns\SnsClient;

$provider = CredentialProvider::ini('default', '/aws/credentials.ini');
$sns = SnsClient::factory(array(
            'version' => 'latest',
            'region'  => 'us-east-1',
            'credentials' => CredentialProvider::memoize($provider)
));
?>

credentials.ini
[default]
aws_access_key_id = <YOUR AWS ACCESS KEY ID>
aws_secret_access_key = <YOUR AWS SECRET ACCESS KEY>

Next, I followed here to create my first "Topic" in SNS.
  1. Make sure you set the region to "us-east-1"; otherwise, the option "SMS" won't be show up in the drop-down list for you select when you create "Subscription" later.
  2. Display Name must be given, and this Display Name will be show at the beginning of the SMS message followed by a ">" sign then your actual message.
After that, I created a subscription with protocol "SMS" and a mobile phone number as the Endpoint. Then you need to send a confirmation request to the Subscription(the User), and the user need to reply 'YES' for receiving further SMS from you. 

As of today, 3/10/16, be aware that the API "publish" will only work as sending all subscriptions under a Topic. Although I see someone is able to send to specific Endpoint, they all have "Applications" defined in the middle for their specific Endpoint works; however, I don't have one (Application). 

This is not a big problem though, I made one topic for one subscription only for one Endpoint (mobile number).  The thing is you may need to know which topic you're going to send for the specific Endpoint. 

Since the format of TopicArn is like 
arn:aws:sns:<Region>:<Subscriber/Topic Owner>:<TopicName>
I can use the TopicName to distinguish who I'm going to send, and like this

$region="us-east-1";
$subscriber="1234567890";
$topicName="USER001";
$TargetArn="arn:aws:sns:".$region.$subscriber.":".$topicName;
$Message="Hello World!";
$sns->publish(array('TargetArn'=>$TargetArn,'Message' => $Message)); 

Hope you Enjoy!