From 2765aa84c26ebc23c8c8ff677479641ad72f328c Mon Sep 17 00:00:00 2001 From: anonymouzz Date: Fri, 4 Aug 2017 00:17:39 +0700 Subject: [PATCH] Implement (and use) zxing scanner actvitiy, #noticket --- android/app/build.gradle | 1 + android/app/src/main/AndroidManifest.xml | 6 +- .../java/com/dinect/checker/MainActivity.java | 10 +- .../dinect/checker/zxing/FindUserThread.java | 64 +++++ .../dinect/checker/zxing/ScannerActivity.java | 225 ++++++++++++++++++ .../main/java/com/dinect/net/ApiClient.java | 108 +++++++++ .../net/DinectAuthorizationInterceptor.java | 86 +++++++ .../res/layout/activity_zxing_scanner.xml | 23 ++ 8 files changed, 517 insertions(+), 6 deletions(-) create mode 100644 android/app/src/main/java/com/dinect/checker/zxing/FindUserThread.java create mode 100644 android/app/src/main/java/com/dinect/checker/zxing/ScannerActivity.java create mode 100644 android/app/src/main/java/com/dinect/net/ApiClient.java create mode 100644 android/app/src/main/java/com/dinect/net/DinectAuthorizationInterceptor.java create mode 100644 android/app/src/main/res/layout/activity_zxing_scanner.xml diff --git a/android/app/build.gradle b/android/app/build.gradle index e237588..1ebb7c9 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -50,4 +50,5 @@ dependencies { compile 'com.android.support:appcompat-v7:25.0.0' compile 'com.squareup.okhttp3:okhttp:3.8.1' compile 'com.squareup.okio:okio:1.13.0' + compile 'me.dm7.barcodescanner:zxing:1.9.7' } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 35e7ded..7be5bea 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,3 +1,4 @@ + + - + \ No newline at end of file diff --git a/android/app/src/main/java/com/dinect/checker/MainActivity.java b/android/app/src/main/java/com/dinect/checker/MainActivity.java index d879651..2dc8062 100644 --- a/android/app/src/main/java/com/dinect/checker/MainActivity.java +++ b/android/app/src/main/java/com/dinect/checker/MainActivity.java @@ -6,7 +6,7 @@ import android.util.Log; import android.widget.Toast; import android.content.Context; import android.content.SharedPreferences; -import com.dinect.checker.CameraActivity; +import com.dinect.checker.zxing.ScannerActivity; import io.flutter.app.FlutterActivity; import io.flutter.plugins.GeneratedPluginRegistrant; import io.flutter.plugin.common.MethodCall; @@ -23,9 +23,9 @@ public class MainActivity extends FlutterActivity { private static final String PREF_POS_MERCHANT_ID = "pref_pos_merchant_id"; private static final String PREF_DOC_ID = "pref_doc_id"; private static final String PREF_POS_ID = "pref_pos_id"; - static final String PREF_API_URL = "prefs_api_token"; - static final String PREF_APP_TOKEN = "pres_app_token"; - static final String PREF_POS_TOKEN = "pref_pos_token"; + public static final String PREF_API_URL = "prefs_api_token"; + public static final String PREF_APP_TOKEN = "pres_app_token"; + public static final String PREF_POS_TOKEN = "pref_pos_token"; private MethodChannel mChannel; private SharedPreferences mPreferences; @@ -59,7 +59,7 @@ public class MainActivity extends FlutterActivity { break; case "startScanner": Map arguments = call.arguments(); - Intent cameraIntent = new Intent(MainActivity.this, CameraActivity.class); + Intent cameraIntent = new Intent(MainActivity.this, ScannerActivity.class); cameraIntent.putExtra(PREF_API_URL, (String) arguments.get("url")); cameraIntent.putExtra(PREF_APP_TOKEN, (String) arguments.get("appToken")); cameraIntent.putExtra(PREF_POS_TOKEN, (String) arguments.get("token")); diff --git a/android/app/src/main/java/com/dinect/checker/zxing/FindUserThread.java b/android/app/src/main/java/com/dinect/checker/zxing/FindUserThread.java new file mode 100644 index 0000000..2bb4bda --- /dev/null +++ b/android/app/src/main/java/com/dinect/checker/zxing/FindUserThread.java @@ -0,0 +1,64 @@ +/* + * Copyright 2017 . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dinect.checker.zxing; + +import android.support.annotation.NonNull; +import android.util.Log; +import android.util.Pair; + +import com.dinect.net.ApiClient; + +/** + * Created by anonymous on 03.08.17. + */ + +public final class FindUserThread extends Thread { + + private static final String TAG = "C.FindUserThread"; + + private ScannerActivity activity; + private final ApiClient client; + private final String card; + + /** + * @param activity caller activity + * @param client ApiClient instance + * @param card card number + */ + FindUserThread(final @NonNull ScannerActivity activity, final @NonNull ApiClient client, final @NonNull String card) { + this.activity = activity; + this.client = client; + this.card = card; + } + + @Override + public void run() { + if (null != activity) { + final Pair response = client.findUser(card); + Log.d(TAG, "network request done with result: " + response.first); + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + activity.networkCallback(response); + } + }); + } + } + + void close() { + activity = null; + } +} diff --git a/android/app/src/main/java/com/dinect/checker/zxing/ScannerActivity.java b/android/app/src/main/java/com/dinect/checker/zxing/ScannerActivity.java new file mode 100644 index 0000000..28947a7 --- /dev/null +++ b/android/app/src/main/java/com/dinect/checker/zxing/ScannerActivity.java @@ -0,0 +1,225 @@ +/* + * Copyright 2017 . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dinect.checker.zxing; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.Handler; +import android.support.annotation.NonNull; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.util.Log; +import android.util.Pair; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.Window; +import android.widget.FrameLayout; +import android.widget.Toast; + +import com.dinect.checker.MainActivity; +import com.dinect.checker.R; +import com.dinect.net.ApiClient; +import com.google.zxing.Result; + +import me.dm7.barcodescanner.zxing.ZXingScannerView; + +/** + * Created by anonymous + */ +public class ScannerActivity extends AppCompatActivity implements ZXingScannerView.ResultHandler { + + public static final String ERROR_INFO = "ERROR_INFO"; + + final static String TAG = "C.ScannerActivity"; + + private ZXingScannerView scannerView; + private ApiClient apiClient; + private FindUserThread findUserThread; + private LogoutDialogFragment logoutDialog; + + @Override + public void onCreate(Bundle state) { + super.onCreate(state); + + if (!isCameraAvailable()) { + // Cancel request if there is no rear-facing camera. + cancelRequest(); + return; + } + final String url = getIntent().getStringExtra(MainActivity.PREF_API_URL); + final String appToken = getIntent().getStringExtra(MainActivity.PREF_APP_TOKEN); + final String token = getIntent().getStringExtra(MainActivity.PREF_POS_TOKEN); + + Log.d(TAG, "initializing scanner activity with url " + + url + ", appToken " + appToken + ", token " + token); + + apiClient = new ApiClient(url, appToken, token); + scannerView = new ZXingScannerView(this); + + // Hide the window title. + requestWindowFeature(Window.FEATURE_NO_TITLE); + setContentView(R.layout.activity_zxing_scanner); + setupToolbar(); + final FrameLayout root = (FrameLayout) findViewById(R.id.zxingRoot); + root.addView(scannerView, 0); + } + + private boolean isCameraAvailable() { + PackageManager pm = getPackageManager(); + return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA); + } + + private void cancelRequest() { + final Intent response = new Intent(); + response.putExtra(ERROR_INFO, "Camera unavailable"); + setResult(RESULT_CANCELED, response); + finish(); + } + + private void setupToolbar() { + final Toolbar toolbar = (Toolbar) findViewById(R.id.zxingToolbar); + setSupportActionBar(toolbar); + + final ActionBar actionBar = getSupportActionBar(); + + if (actionBar != null) { + actionBar.setTitle(getString(R.string.scanner_title)); + actionBar.setDisplayHomeAsUpEnabled(false); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.logout) { + logoutDialog = new ScannerActivity.LogoutDialogFragment(); + logoutDialog.show(getFragmentManager(), "logout"); + return true; + } else if (item.getItemId() == R.id.faq) { + final Intent intent = new Intent(); + intent.putExtra("item", "faq"); + setResult(RESULT_OK, intent); + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onResume() { + super.onResume(); + scannerView.setResultHandler(this); + scannerView.startCamera(); + } + + @Override + public void onPause() { + super.onPause(); + scannerView.stopCamera(); + } + + @Override + public void onBackPressed() { + setResult(RESULT_CANCELED); + finish(); + } + + @Override + public void handleResult(Result raw) { + final String card = raw.getText(); + Toast.makeText(this, card, Toast.LENGTH_SHORT).show(); + + findUserThread = new FindUserThread(this, apiClient, card); + findUserThread.start(); + + final Handler handler = new Handler(); + handler.postDelayed(new Runnable() { + @Override + public void run() { + scannerView.resumeCameraPreview(ScannerActivity.this); + } + }, 2000); + } + + protected void networkCallback(final @NonNull Pair result) { + + if (null != result.first) { + Log.d(TAG, "user found, finish activity with result"); + final Intent intent = new Intent(); + intent.putExtra("user", result.second); + intent.putExtra("card", result.first); + setResult(RESULT_OK, intent); + finish(); + } + findUserThread.close(); + findUserThread = null; + } + + void dismissDialog() { + if (logoutDialog != null) { + logoutDialog.dismiss(); + logoutDialog = null; + } + } + + void logout() { + final Intent intent = new Intent(); + intent.putExtra("item", "logout"); + setResult(RESULT_OK, intent); + finish(); + } + + public static class LogoutDialogFragment extends DialogFragment { + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + final LayoutInflater inflater = getActivity().getLayoutInflater(); + final View content = inflater.inflate(R.layout.f_logout_dialog, null); + builder.setView(content); + final View positiveButton = content.findViewById(R.id.positiveButton); + final View negativeButton = content.findViewById(R.id.negativeButton); + + negativeButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + ((ScannerActivity) getActivity()).dismissDialog(); + } + }); + + positiveButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + ((ScannerActivity) getActivity()).logout(); + } + }); + + return builder.create(); + } + } +} diff --git a/android/app/src/main/java/com/dinect/net/ApiClient.java b/android/app/src/main/java/com/dinect/net/ApiClient.java new file mode 100644 index 0000000..f144047 --- /dev/null +++ b/android/app/src/main/java/com/dinect/net/ApiClient.java @@ -0,0 +1,108 @@ +/* + * Copyright 2017 . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dinect.net; + +import android.support.annotation.NonNull; +import android.util.Log; +import android.util.Pair; + +import org.json.JSONArray; +import org.json.JSONException; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +/** + * Created by anonymous + */ + +public final class ApiClient { + + private static final String TAG = "C.ApiClient"; + + private static final int TIMEOUT_CONNECTION = 3; + private static final int TIMEOUT_READ = 3; + private static final int TIMEOUT_WRITE = 3; + + private final String endpoint; + + + final OkHttpClient http; + + /** + * @param endpoint POS API endpoint + * @param appToken Application Token + * @param token POS token + */ + public ApiClient(final @NonNull String endpoint, final @NonNull String appToken, final @NonNull String token) { + this.endpoint = endpoint; + + http = new OkHttpClient(). + newBuilder() + .connectTimeout(TIMEOUT_CONNECTION, TimeUnit.SECONDS) + .readTimeout(TIMEOUT_READ, TimeUnit.SECONDS) + .writeTimeout(TIMEOUT_WRITE, TimeUnit.SECONDS) + .addInterceptor(new DinectAuthorizationInterceptor(appToken, token, "checker/0.1", true)) + .build(); + } + + + /*** + * + * @param card card/foreigncarf number + * @return (null, error) on fail or (card, user) info on success + */ + public Pair findUser(final @NonNull String card) { + + final Request.Builder builder = new Request.Builder(); + final Request request; + + final HttpUrl.Builder httpBuilder = HttpUrl.parse(endpoint + "/users/").newBuilder(); + + httpBuilder.addQueryParameter("auto", card); + request = builder + .url(httpBuilder.build()) + .build(); + + try { + final Response response = http.newCall(request).execute(); + final String body = response.body().string(); + switch (response.code()) { + case 200: + final JSONArray users = new JSONArray(body); + if (users.length() > 0) { + return new Pair<>(card, users.get(0).toString()); + } else { + return new Pair<>(null, "Пользователь с таким id не найден"); + } + case 204: + return new Pair<>(null, "Пользователь с таким id не найден"); + default: + return new Pair<>(null, "Что-то пошло не так"); + } + } catch (final IOException | JSONException e) { + Log.e(TAG, e.getMessage(), e); + return new Pair<>(null, "Упс..."); + } + } + + +} diff --git a/android/app/src/main/java/com/dinect/net/DinectAuthorizationInterceptor.java b/android/app/src/main/java/com/dinect/net/DinectAuthorizationInterceptor.java new file mode 100644 index 0000000..2d4a312 --- /dev/null +++ b/android/app/src/main/java/com/dinect/net/DinectAuthorizationInterceptor.java @@ -0,0 +1,86 @@ +/* + * Copyright 2017 . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dinect.net; + +import java.io.IOException; + +import okhttp3.Headers; +import okhttp3.HttpUrl; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +/** + * @author anonymous + */ +public final class DinectAuthorizationInterceptor implements Interceptor { + + private final String token; + private final String appToken; + + private final String dmAuthorization; + private final String authorization; + private final String userAgent; + private final boolean useAuthHeader; + + public DinectAuthorizationInterceptor(final String appToken, final String token, final String clientInfo, final boolean useAuthHeader) { + this.appToken = appToken; + this.token = token; + this.useAuthHeader = useAuthHeader; + userAgent = clientInfo; + // optimization: concatenate once + dmAuthorization = "dmapptoken " + appToken; + authorization = "dmtoken " + token; + } + + @Override + public Response intercept(Interceptor.Chain chain) throws IOException { + + final Request originalRequest = chain.request(); + final HttpUrl originalUrl = originalRequest.url(); + final Request.Builder requestBuilder = originalRequest.newBuilder(); + + HttpUrl url = originalUrl; + Headers headers; + + headers = originalRequest.headers(); + final Headers.Builder headersBuilder = headers.newBuilder(); + // always set UA and content type + headersBuilder.set("User-Agent", userAgent); + + // Add auth info. Either in headers or query parameters + if (useAuthHeader) { + headersBuilder.set("DM-Authorization", dmAuthorization); + if (null != token) { + headersBuilder.set("Authorization", authorization); + } + headers = headersBuilder.build(); + } else { + final HttpUrl.Builder urlBuilder = originalRequest.url().newBuilder(); + urlBuilder.addQueryParameter("_dmapptoken", appToken); + urlBuilder.addQueryParameter("user_agent", userAgent); + if (null != token) { + urlBuilder.addQueryParameter("_dmtoken", token); + } + url = urlBuilder.build(); + } + + final Request request = requestBuilder.url(url).headers(headers).build(); + final Response response = chain.proceed(request); + + return response; + } +} \ No newline at end of file diff --git a/android/app/src/main/res/layout/activity_zxing_scanner.xml b/android/app/src/main/res/layout/activity_zxing_scanner.xml new file mode 100644 index 0000000..cc7b341 --- /dev/null +++ b/android/app/src/main/res/layout/activity_zxing_scanner.xml @@ -0,0 +1,23 @@ + + + + + + + +