Friday, 25 May 2018

How the flow of this context should be? ( Where to put the code to process the payment depending of the selected payment method?)

I have a multi-step form for a user to register in a conference, in each step is done an ajax request to validate the form fields of each step, all steps are in the same page. The steps are:
  • Step1 is to collect some info about the user (name and tin number)
  • Step 2 is to collect the payment method (credit card or references)
  • Step 3 is the payment
My doubt is in the step3. If the payment_method selected in the step2 is "References" is necessary to execute the code below that generates some payment codes so that they can be presented to the user so he can pay. Code that generates the payment codes:
$payment_info = [
    'name' => "user name",
    'email' => 'user email',
    'value' => 'registration total price',
    'id' => 'registration id',
];

$newPayment = new Payment($payment_info);
$transference = $newPayment->gererateReferences();

// after generate the codes with generateTransferData is necessary to 
//   show the codes to the user in the step 3 if the selected payment 
//   method was "References"

My doubt is how the flow should be, where this code should be placed in the ConferenceController. When the user is in step 2 and selects a payment method (credit card or transfers) and click "Go to step 3" is made an ajax request to the storePaymentMethods() of the ConferenesController. In this method, the field "payment_method" is validated and if it was filled by the user code 200 is returned:
public function storePaymentMethods(Request $request){
       $request->validate([
            'payment_method' => 'required',
        ]);
        return response()->json([
            'success' => true,
            'message' => 'success',
            'payment_method' => $request->payment_method,
        ], 200);
    }

If it returned code 200 the user goes to the step3, in step 3 is presented the "#credit_card_section" or "#transferences_section" with jquery based on the selected payment in the previous step. The #transferences_section is outside the form because is not necessary to submit any form for this payment method is only necessary to show some codes to the user.
<div>
    <div id="transferences_section">
        <!-- necessary fields to payments with transferences-->
    </div>
    <form method="post" id="step3form" action="">
            
            <div id="credit_card_section">
                <!-- necessary fields to payments with credit card-->
            </div>
    </form>
</div>

My doubt is where to put the code above that generates the payment codes so is possible to present that codes to the user in the div "#transferences_section".
Do you know how the flow should be to achieve that?
Maybe an approach can be something like below in storePaymentMethods() but it doesn't seem very correct) do everything in this method:
public function storePaymentMethods(Request $request){
       $request->validate([
            'payment_method' => 'required',
        ]);

        if($request->payment_method == "transferences"){
          // generate codes and present to the user that codes
        }
        else if($request->payment_method == "credit_card"){
          // show credit card inputs to the user
          // and process the credit card payment with stripe
        }



        return response()->json([
            'success' => true,
            'message' => 'success',
            'payment_method' => $request->payment_method,
        ], 200);
    }


A full summary of the flow of the registration with the multistep form that I have for now:
So for the step1 there is the form:
<div>
    <form method="post" id="step1form" action="">
        
        <!-- fields of the step 1-->
        <input type="submit" href="#step2" id="goToStep2" class="btn next-step" value="Go to step 2"/>
    </form>
</div>

step1 image to explain better:
enter image description here
when the user clicks in "Go to step 2" button is made an ajax request to validate the data and if there are no errors, code 200 is returned and the user go to the step 2:
$('#goToStep2').on('click', function (event) {
    event.preventDefault();
    var custom_form = $("#" + page_form_id_step1);
    $.ajax({
        method: "POST",
        url: '',
        data: custom_form.serialize(),
        datatype: 'json',
        success: function (data, textStatus, jqXHR) {
            var $active = $('.nav-pills li a.active');
            nextTab($active);
        },
        error: function (data) {    
            // show errors
        }
    });
});

Then in the ConferencesController there is teh storeRegistrationInfo() to handle the above ajax request:
public function storeRegistrationInfo(Request $request, $id, $slug = null, Validator $validator){  
    $rules = [];
    $messages = [];

    $rules["name_invoice"] = 'required|max:255|string';
    $rules["TIN_invoice"] = ['required', 'string', new ValidTIN()];

    $validator = Validator::make($request->all(), $rules, $messages);

    $errors = $validator->errors();
    $errors =  json_decode($errors);

    if($validator->fails()) {
        return response()->json([
            'success' => false,
            'errors' => $errors
        ], 422);
    }
    return response()->json([
        'success' => true,
        'message' => 'success'
    ], 200);
}

So, if code 200 is returned user is in the step2form:
<div>
    <form method="post" id="step2form" action="">
        
        <!-- fields of the step 2-->
         <input type="submit" href="#step3" id="goToStep3" class="btn next-step" value="Go to step 3"/>
    </form>
</div>

step2 image to explain better:
enter image description here
When the user clicks in "Go to step 3" button is made an ajax request to validate the data and if there are no errors, code 200 is returned and the user go to the step 3, ajax request:
$("#credit_card_section").hide();
$("#references_section").hide();

var page_form_id_step2 = "step2form";

    $('#goToStep3').on('click', function (event) {
        event.preventDefault();
        var custom_form = $("#" + page_form_id_step2);
        $.ajax({
            method: "POST",
            url: '',
            data: custom_form.serialize(),
            datatype: 'json',
            success: function (data, textStatus, jqXHR) {
                var result = data;
                if(result['payment_method'] == 'credit_card'){
                    $("#credit_card_section").show();
                    $("#transferences_section").hide();
                }else{
                    $("#transferences_section").show();
                    $("#credit_card_section").hide();
                }
                var $active = $('.nav-pills li a.active');
                nextTab($active);
            },
            error: function (data) {
               // show errors
            }
        });
    });

The ConferenceController has the storePayment() to handle the ajax request above, it validates if the user selected a payment method and if yes returns code 200:
public function storePaymentMethods(Request $request){
       $request->validate([
            'payment_method' => 'required',
        ]);
        return response()->json([
            'success' => true,
            'message' => 'success',
            'payment_method' => $request->payment_method,
        ], 200);
    }

Then there is the step3 div. In the step3 div it will appear the div "#credit_card_section" visible or "#transferences_section" visible depending on the payment method selected in the previous step (credit card or transferences):
<div>
    <form method="post" id="step3form" action="">
            
            <div id="credit_card_section">
                <!-- necessary fields to payments with credit card-->
            </div>
              <div id="transferences_section">
                <!-- necessary fields to payments with transferences-->
            </div>
    </form>
</div>

step3 image to explain better, depending on the payment method the info that is necessary to show in the step 3 is different:
enter image description here
// Resume of the methods of the RegistrationController, that is the controller that handles the multistep form:
class RegistrationController extends Controller
{
    public function storeQuantities(Request $request, $id, $slug = null){
        // method that stores in session the ticket types 
       // selected by the user in the conference details page
        Session::put('selectedRtypes', $selectedRtypes);
        Session::put('allParticipants' , $allParticipants);
        Session::put('customQuestions' ,  $selectedRtypes[$rtype->name]['questions']);

        // and redirects the user to the registartion page registration.blade.php to the route 'conferences.registration' 
        // this route is associated with the displayRegistrationPage() method
        return redirect(route('conferences.registration',['id' => $id, 'slug' => $slug]));
    }


  // method to display the registration.blade.php
    public function displayRegistrationPage(Request $request, $id, $slug=null){
        // get the session values
        $selectedRtypes =  Session::get('selectedRtypes');
        $allParticipants = Session::get('allParticipants');
        $customQuestions = Session::get('customQuestions');

   // redirect the user to the registration.blade.php
        if(isset($selectedRtypes)) {
            return view('conferences.registration',
                ['selectedRtypes' => $selectedRtypes, 'customQuestions' => $customQuestions, 'id' => $id, 'slug' => $slug]);
        }
        else{
            // return user to the conference details page
            return redirect(route('conferences.show',['id' => $id, 'slug' => $slug]));
        }
    }



   // method to handle the step1 of the multi step form
    public function storeRegistrationInfo(Request $request, $id, $slug = null, Validator $validator){

        // get and validate the fields of the step1 
       // and returns code 200 if al is ok

        return response()->json([
            'success' => true,
            'message' => 'success'
        ], 200);
    }


   // method to handle the step2 of the multi step form
    public function storePaymentMethods(Request $request){

        // validate if the payment_method field was filled
        // if was filled return code 200 
        // and returns the payment_method 
        //so in the step 3 div is possible to show a section for
       // when the payment_method is credit card (#credit_card_section) 
          or transfers ("transfers_section")
        return response()->json([
            'success' => true,
            'message' => 'success',
            'payment_method' => $request->payment_method,
        ], 200);
    }
}

Full context diagram:
enter image description here


from How the flow of this context should be? ( Where to put the code to process the payment depending of the selected payment method?)

WebRTC, Capture Screen

my current problem is, that I want to find a way to capture a frame/screenshot during a webrtc connection on Android. I know there are already some solutions to this here but none were working for me.

With my current approach I followed this Gist.

The problem is, that the it returns a black bitmap. I will append my approach but it basically is the same as the Gist. If anybody has any ideas how to solve this. Thanks in advance.

Activity SingleFrameCapturer.BitmapListener gotFrameListener = new SingleFrameCapturer.BitmapListener() {

@Override
public void gotBitmap(Bitmap theBitmap) {
    Log.e(TAG, "got bitmap!");

    ImageView imageView = findViewById(R.id.object_preview);
    imageView.setImageBitmap(theBitmap);

    imageView.setVisibility(View.VISIBLE);
        }
    };

    MediaStream stream = contextManager.getStream();
    SingleFrameCapturer.toBitmap(this, stream, gotFrameListener);
}

SingleFrameCapturer

import android.graphics.Bitmap;
import android.util.Base64;
import android.util.Log;
import java.nio.ByteBuffer;

import org.webrtc.VideoTrack;
import org.webrtc.MediaStream;
import org.webrtc.EglBase;
import org.webrtc.RendererCommon;

import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLDisplay;



public class SingleFrameCapturer {

    public interface BitmapListener {
        public void gotBitmap(Bitmap theBitmap);
    }

    private static boolean firstTimeOnly = true;


    // the below pixelBuffer code is based on from
    // https://github.com/CyberAgent/android-gpuimage/blob/master/library/src/jp/co/cyberagent/android/gpuimage/PixelBuffer.java
    //
    class PixelBuffer implements org.webrtc.VideoRenderer.Callbacks {
        final static String TAG = "PixelBuffer";
        final static boolean LIST_CONFIGS = false;

        int mWidth, mHeight;
        EGL10 mEGL;
        EGLDisplay mEGLDisplay;
        boolean gotFrame = false;
        String mThreadOwner;
        BitmapListener listener;
        android.app.Activity activity;


        public PixelBuffer(android.app.Activity activity, BitmapListener listener) {
            this.listener = listener;
            this.activity = activity;
        }


        private static final String VERTEX_SHADER_STRING =
                "varying vec2 interp_tc;\n"
                        + "attribute vec4 in_pos;\n"
                        + "attribute vec4 in_tc;\n"
                        + "\n"
                        + "uniform mat4 texMatrix;\n"
                        + "\n"
                        + "void main() {\n"
                        + "    gl_Position = in_pos;\n"
                        + "    interp_tc = (texMatrix * in_tc).xy;\n"
                        + "}\n";


        @Override
        public void renderFrame(final org.webrtc.VideoRenderer.I420Frame i420Frame) {
            Log.d(TAG, "entered renderFrame");
            //
            // we only want to grab a single frame but our method may get called
            // a few times before we're done.
            //
            if (gotFrame || i420Frame.width == 0 || i420Frame.height == 0) {
                Log.d(TAG, "Already got frame so taking honourable exit");
                org.webrtc.VideoRenderer.renderFrameDone(i420Frame);
                return;
            }
            activity.runOnUiThread(new Runnable() {
                public void run() {
                    int width = i420Frame.width;
                    int height = i420Frame.height;
                    Log.d(TAG, "about to call initWithSize");
                    initWithSize(width, height);
                    Bitmap bitmap = toBitmap(i420Frame);
                    org.webrtc.VideoRenderer.renderFrameDone(i420Frame);
                    gotFrame = true;
                    listener.gotBitmap(bitmap);
                    destroy();
                }
            });
        }

        private int buildARGB(int r, int g, int b) {
            return (0xff << 24) |(r << 16) | (g << 8) | b;
        }

        private Bitmap toBitmap(org.webrtc.VideoRenderer.I420Frame frame) {

            if (frame.yuvFrame) {

                //EglBase eglBase = EglBase.create();
                EglBase eglBase = StreamActivity.rootEglBase;

                if(firstTimeOnly) {
                    eglBase.createDummyPbufferSurface();
                    firstTimeOnly = false;
                }
                eglBase.makeCurrent();
                TextureToRGB textureToRGB = new TextureToRGB();
                int numPixels = mWidth *mHeight;
                final int bytesPerPixel = 4;
                ByteBuffer framebuffer = ByteBuffer.allocateDirect(numPixels*bytesPerPixel);

                final float frameAspectRatio = (float) frame.rotatedWidth() / (float) frame.rotatedHeight();

                final float[] rotatedSamplingMatrix =
                        RendererCommon.rotateTextureMatrix(frame.samplingMatrix, frame.rotationDegree);
                final float[] layoutMatrix = RendererCommon.getLayoutMatrix(
                        false, frameAspectRatio, (float) mWidth / mHeight);
                final float[] texMatrix = RendererCommon.multiplyMatrices(rotatedSamplingMatrix, layoutMatrix);

                textureToRGB.convert(framebuffer, mWidth, mHeight, frame.textureId, texMatrix);

                byte [] frameBytes = framebuffer.array();
                int [] dataARGB = new int[numPixels];
                for(int i = 0, j = 0; j < numPixels; i+=bytesPerPixel, j++) {
                    //
                    // data order in frameBytes is red, green, blue, alpha, red, green, ....
                    //
                    dataARGB[j] = buildARGB(frameBytes[i] & 0xff,frameBytes[i+1] &0xff,frameBytes[i+2] &0xff);
                }

                Bitmap bitmap = Bitmap.createBitmap(dataARGB, mWidth, mHeight, Bitmap.Config.ARGB_8888);
                return bitmap;
            }
            else {
                return null;
            }
        }

        private void initWithSize(final int width, final int height) {
            mWidth = width;
            mHeight = height;

            // Record thread owner of OpenGL context
            mThreadOwner = Thread.currentThread().getName();
        }


        public void destroy() {
        }


        private int getConfigAttrib(final EGLConfig config, final int attribute) {
            int[] value = new int[1];
            return mEGL.eglGetConfigAttrib(mEGLDisplay, config,
                    attribute, value) ? value[0] : 0;
        }
    }


    final private static String TAG = "SingleFrameCapturer";
    org.webrtc.VideoRenderer renderer;

    private  SingleFrameCapturer(final android.app.Activity activity, MediaStream mediaStream, final BitmapListener gotFrameListener) {
        if( mediaStream.videoTracks.size() == 0) {
            Log.e(TAG, "No video track to capture from");
            return;
        }

        final VideoTrack videoTrack = mediaStream.videoTracks.get(0);
        final PixelBuffer vg = new PixelBuffer(activity, new BitmapListener() {

            @Override
            public void gotBitmap(final Bitmap bitmap) {
                activity.runOnUiThread(new Runnable(){
                    public void run() {
                        videoTrack.removeRenderer(renderer);
                        try {
                            gotFrameListener.gotBitmap(bitmap);
                        } catch( Exception e1) {
                            Log.e(TAG, "Exception in gotBitmap callback:" + e1.getMessage());
                            e1.printStackTrace(System.err);
                        }
                    }
                });

            }
        });
        renderer = new org.webrtc.VideoRenderer(vg);
        videoTrack.addRenderer(renderer);
    }

    /**
     * This constructor builds an object which captures a frame from mediastream to a Bitmap.
     * @param mediaStream The input media mediaStream.
     * @param gotFrameListener A callback which will receive the Bitmap.
     */
    public static void toBitmap(android.app.Activity activity, MediaStream mediaStream, final BitmapListener gotFrameListener) {
        new SingleFrameCapturer(activity, mediaStream, gotFrameListener);
    }

    /**
     * This method captures a frame from the supplied media stream to a jpeg file written to the supplied outputStream.
     * @param mediaStream  the source media stream
     * @param quality the quality of the jpeq 0 to 100.
     * @param outputStream the output stream the jpeg file will be written to.
     * @param done a runnable that will be invoked when the outputstream has been written to.
     * @return The frame capturer. You should keep a reference to the frameCapturer until the done object is invoked.
     */
    public static void toOutputStream(android.app.Activity activity, MediaStream mediaStream, final int quality, final java.io.OutputStream outputStream, final Runnable done) {
        BitmapListener gotFrameListener = new BitmapListener() {

            @Override
            public void gotBitmap(Bitmap theBitmap) {
                theBitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream);
                try {
                    done.run();
                } catch( Exception e1) {
                    Log.e(TAG, "Exception in toOutputStream done callback:" + e1.getMessage());
                    e1.printStackTrace(System.err);
                }

            }
        };
        toBitmap(activity, mediaStream, gotFrameListener);
    }

    /**
     * This method captures a frame from the supplied mediastream to a dataurl written to a StringBuilder.
     * @param mediaStream  the source media stream
     * @param quality the quality of the jpeq 0 to 100.
     * @param output a StringBuilder which will be the recipient of the dataurl.
     * @param done a runnable that will be invoked when the dataurl is built.
     * @return The frame capturer. You should keep a reference to the frameCapturer until the done object is invoked.
     */
    public static void toDataUrl(android.app.Activity activity, MediaStream mediaStream, final int quality, final StringBuilder output, final Runnable done) {

        final java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream();
        Runnable convertToUrl = new Runnable() {

            @Override
            public void run() {
                output.append("data:image/jpeg;base64,");
                output.append(Base64.encodeToString(outputStream.toByteArray(), Base64.DEFAULT));
                try {
                    done.run();
                } catch( Exception e1) {
                    Log.e(TAG, "Exception in toDataUrl done callback:" + e1.getMessage());
                    e1.printStackTrace(System.err);
                }
            }
        };
        toOutputStream(activity, mediaStream, quality, outputStream, convertToUrl);
    }
}

TextureToRGB

import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import org.webrtc.*;
/**
 * Class for converting OES textures RGBA. It should be constructed on a thread with
 * an active EGL context, and only be used from that thread. It is used by the EasyrtcSingleFrameCapturer.
 */
public class TextureToRGB {
    // Vertex coordinates in Normalized Device Coordinates, i.e.
    // (-1, -1) is bottom-left and (1, 1) is top-right.
    private static final FloatBuffer DEVICE_RECTANGLE = GlUtil.createFloatBuffer(new float[] {
            -1.0f, -1.0f, // Bottom left.
            1.0f, -1.0f, // Bottom right.
            -1.0f, 1.0f, // Top left.
            1.0f, 1.0f, // Top right.
    });

    // Texture coordinates - (0, 0) is bottom-left and (1, 1) is top-right.
    private static final FloatBuffer TEXTURE_RECTANGLE = GlUtil.createFloatBuffer(new float[] {
            0.0f, 0.0f, // Bottom left.
            1.0f, 0.0f, // Bottom right.
            0.0f, 1.0f, // Top left.
            1.0f, 1.0f // Top right.
    });


    private static final String VERTEX_SHADER =
            "varying vec2 interp_tc;\n"
                    + "attribute vec4 in_pos;\n"
                    + "attribute vec4 in_tc;\n"
                    + "\n"
                    + "uniform mat4 texMatrix;\n"
                    + "\n"
                    + "void main() {\n"
                    + "    gl_Position = in_pos;\n"
                    + "    interp_tc = (texMatrix * in_tc).xy;\n"
                    + "}\n";

    private static final String FRAGMENT_SHADER =
            "#extension GL_OES_EGL_image_external : require\n"
                    + "precision mediump float;\n"
                    + "varying vec2 interp_tc;\n"
                    + "\n"
                    + "uniform samplerExternalOES oesTex;\n"
                    + "\n"
                    + "void main() {\n"
                    + "  gl_FragColor = texture2D(oesTex, interp_tc);\n"
                    + "}\n";
    // clang-format on

    private final GlTextureFrameBuffer textureFrameBuffer;
    private final GlShader shader;
    private final int texMatrixLoc;
    private final ThreadUtils.ThreadChecker threadChecker = new ThreadUtils.ThreadChecker();
    private boolean released = false;

    /**
     * This class should be constructed on a thread that has an active EGL context.
     */
    public TextureToRGB() {
        threadChecker.checkIsOnValidThread();
        textureFrameBuffer = new GlTextureFrameBuffer(GLES20.GL_RGBA);
        shader = new GlShader(VERTEX_SHADER, FRAGMENT_SHADER);
        shader.useProgram();
        texMatrixLoc = shader.getUniformLocation("texMatrix");

        GLES20.glUniform1i(shader.getUniformLocation("oesTex"), 0);
        GlUtil.checkNoGLES2Error("Initialize fragment shader uniform values.");
        // Initialize vertex shader attributes.
        shader.setVertexAttribArray("in_pos", 2, DEVICE_RECTANGLE);
        // If the width is not a multiple of 4 pixels, the texture
        // will be scaled up slightly and clipped at the right border.
        shader.setVertexAttribArray("in_tc", 2, TEXTURE_RECTANGLE);
    }

    public void convert(ByteBuffer buf, int width, int height, int srcTextureId,
                        float[] transformMatrix) {
        threadChecker.checkIsOnValidThread();
        if (released) {
            throw new IllegalStateException("TextureToRGB.convert called on released object");
        }

        int size = width * height;
        if (buf.capacity() < size) {
            throw new IllegalArgumentException("TextureToRGB.convert called with too small buffer");
        }
        // Produce a frame buffer starting at top-left corner, not
        // bottom-left.
        transformMatrix =
                RendererCommon.multiplyMatrices(transformMatrix, RendererCommon.verticalFlipMatrix());

        final int frameBufferWidth = width;
        final int frameBufferHeight =height;
        textureFrameBuffer.setSize(frameBufferWidth, frameBufferHeight);

        // Bind our framebuffer.
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, textureFrameBuffer.getFrameBufferId());
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, srcTextureId);
        GLES20.glUniformMatrix4fv(texMatrixLoc, 1, false, transformMatrix, 0);
        GLES20.glViewport(0, 0, width, height);

        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);

        GLES20.glReadPixels(
                0, 0, frameBufferWidth, frameBufferHeight, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);

        // Restore normal framebuffer.
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);

        // Unbind texture. Reportedly needed on some devices to get
        // the texture updated from the camera.
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
        GlUtil.checkNoGLES2Error("TextureToRGB.convert");
    }

    public void release() {
        threadChecker.checkIsOnValidThread();
        released = true;
        shader.release();
        textureFrameBuffer.release();
    }
}



from WebRTC, Capture Screen

Phonegap Status bar styleDefault & fall back for android below 6

I want to set my android app status bar to light background and dark text. It is working perfectly when I view my app in Phonegap mobile app. But after building the app and installing in mobile it's not working. I'm using Android 7.

I used below code in config

<preference name="StatusBarBackgroundColor" value="#F1F1F1" />
<preference name="StatusBarStyle" value="default" />

I've also tried using javascript in index.js after the device ready

StatusBar.backgroundColorByHexString("#F1F1F1");
StatusBar.styleDefault();

I tried removing the plugin and installed the latest code from github as suggested by related posts in StackOverflow. Nothing is working. Any suggestions?

Also, Is there any fallback code to handle android versions below 6? I came to know that those versions doesn't support dark text in status bar.



from Phonegap Status bar styleDefault & fall back for android below 6