zh 10 месяцев назад
Сommit
744682d4c4
67 измененных файлов с 2039 добавлено и 0 удалено
  1. 20 0
      .gitignore
  2. 3 0
      .idea/.gitignore
  3. 1 0
      .idea/.name
  4. 6 0
      .idea/compiler.xml
  5. 10 0
      .idea/deploymentTargetSelector.xml
  6. 20 0
      .idea/gradle.xml
  7. 10 0
      .idea/migrations.xml
  8. 9 0
      .idea/misc.xml
  9. 6 0
      .idea/vcs.xml
  10. 1 0
      app/.gitignore
  11. 89 0
      app/build.gradle
  12. 22 0
      app/proguard-rules.pro
  13. 26 0
      app/src/androidTest/java/com/baoshi/piece/ExampleInstrumentedTest.java
  14. 36 0
      app/src/main/AndroidManifest.xml
  15. 12 0
      app/src/main/java/com/baoshi/piece/MainActivity.java
  16. 39 0
      app/src/main/java/com/baoshi/piece/base/BaseActivity.java
  17. 25 0
      app/src/main/java/com/baoshi/piece/base/BaseApplication.java
  18. 184 0
      app/src/main/java/com/baoshi/piece/base/BaseWebViewActivity.java
  19. 19 0
      app/src/main/java/com/baoshi/piece/controller/MainController.java
  20. 44 0
      app/src/main/java/com/baoshi/piece/db/DBHelper.java
  21. 222 0
      app/src/main/java/com/baoshi/piece/db/dao/DeliveryDao.java
  22. 43 0
      app/src/main/java/com/baoshi/piece/db/po/DeliveryRecord.java
  23. 13 0
      app/src/main/java/com/baoshi/piece/model/MessageModel.java
  24. 36 0
      app/src/main/java/com/baoshi/piece/model/PaginationModel.java
  25. 9 0
      app/src/main/java/com/baoshi/piece/utils/AppConfig.java
  26. 146 0
      app/src/main/java/com/baoshi/piece/utils/JavaScriptInterface.java
  27. 160 0
      app/src/main/java/com/baoshi/piece/utils/MacAddressUtils.java
  28. 36 0
      app/src/main/java/com/baoshi/piece/utils/TimeUtils.java
  29. 84 0
      app/src/main/java/com/baoshi/piece/utils/TipActionUtils.java
  30. 5 0
      app/src/main/java/com/baoshi/piece/view/MainView.java
  31. 170 0
      app/src/main/res/drawable/ic_launcher_background.xml
  32. 30 0
      app/src/main/res/drawable/ic_launcher_foreground.xml
  33. 15 0
      app/src/main/res/layout/activity_main.xml
  34. 13 0
      app/src/main/res/layout/activity_web_view.xml
  35. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_logo.xml
  36. 5 0
      app/src/main/res/mipmap-anydpi-v26/ic_logo_round.xml
  37. 6 0
      app/src/main/res/mipmap-anydpi/ic_launcher.xml
  38. 6 0
      app/src/main/res/mipmap-anydpi/ic_launcher_round.xml
  39. BIN
      app/src/main/res/mipmap-hdpi/ic_logo.png
  40. BIN
      app/src/main/res/mipmap-hdpi/ic_logo_foreground.png
  41. BIN
      app/src/main/res/mipmap-hdpi/ic_logo_round.png
  42. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher.webp
  43. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
  44. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher.webp
  45. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
  46. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
  47. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
  48. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
  49. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
  50. BIN
      app/src/main/res/raw/error.mp3
  51. BIN
      app/src/main/res/raw/scanner_repeat.wav
  52. BIN
      app/src/main/res/raw/tip.mp3
  53. 16 0
      app/src/main/res/values-night/themes.xml
  54. 12 0
      app/src/main/res/values/colors.xml
  55. 3 0
      app/src/main/res/values/strings.xml
  56. 16 0
      app/src/main/res/values/themes.xml
  57. 13 0
      app/src/main/res/xml/backup_rules.xml
  58. 19 0
      app/src/main/res/xml/data_extraction_rules.xml
  59. 17 0
      app/src/test/java/com/baoshi/piece/ExampleUnitTest.java
  60. 4 0
      build.gradle
  61. 22 0
      gradle.properties
  62. 22 0
      gradle/libs.versions.toml
  63. BIN
      gradle/wrapper/gradle-wrapper.jar
  64. 6 0
      gradle/wrapper/gradle-wrapper.properties
  65. 185 0
      gradlew
  66. 89 0
      gradlew.bat
  67. 29 0
      settings.gradle

+ 20 - 0
.gitignore

@@ -0,0 +1,20 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
+/app/release/
+/app/debug/
+version.properties
+.idea
+/.idea

+ 3 - 0
.idea/.gitignore

@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml

+ 1 - 0
.idea/.name

@@ -0,0 +1 @@
+Piece

+ 6 - 0
.idea/compiler.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <bytecodeTargetLevel target="17" />
+  </component>
+</project>

+ 10 - 0
.idea/deploymentTargetSelector.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="deploymentTargetSelector">
+    <selectionStates>
+      <SelectionState runConfigName="app">
+        <option name="selectionMode" value="DROPDOWN" />
+      </SelectionState>
+    </selectionStates>
+  </component>
+</project>

+ 20 - 0
.idea/gradle.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="GradleMigrationSettings" migrationVersion="1" />
+  <component name="GradleSettings">
+    <option name="linkedExternalProjectsSettings">
+      <GradleProjectSettings>
+        <option name="externalProjectPath" value="$PROJECT_DIR$" />
+        <option name="gradleHome" value="$PROJECT_DIR$/../../../java/gradle/gradle-8.7" />
+        <option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
+        <option name="modules">
+          <set>
+            <option value="$PROJECT_DIR$" />
+            <option value="$PROJECT_DIR$/app" />
+          </set>
+        </option>
+        <option name="resolveExternalAnnotations" value="false" />
+      </GradleProjectSettings>
+    </option>
+  </component>
+</project>

+ 10 - 0
.idea/migrations.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectMigrations">
+    <option name="MigrateToGradleLocalJavaHome">
+      <set>
+        <option value="$PROJECT_DIR$" />
+      </set>
+    </option>
+  </component>
+</project>

+ 9 - 0
.idea/misc.xml

@@ -0,0 +1,9 @@
+<project version="4">
+  <component name="ExternalStorageConfigurationManager" enabled="true" />
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/build/classes" />
+  </component>
+  <component name="ProjectType">
+    <option name="id" value="Android" />
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>

+ 1 - 0
app/.gitignore

@@ -0,0 +1 @@
+/build

+ 89 - 0
app/build.gradle

@@ -0,0 +1,89 @@
+plugins {
+    alias(libs.plugins.android.application)
+}
+
+android {
+    namespace 'com.baoshi.piece'
+    compileSdk 34
+
+    defaultConfig {
+        applicationId "com.baoshi.piece"
+        minSdk 30
+        targetSdk 34
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+    }
+
+    buildTypes {
+        debug {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+            buildConfigField "String", "WEB_URL", "\"http://192.168.2.175:5173/\""
+            signingConfig signingConfigs.debug
+        }
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+            buildConfigField "String", "WEB_URL", "\"https://app.baoshi56.com/\""
+            signingConfig signingConfigs.debug
+            // 设置release构建类型的输出文件名
+            android.applicationVariants.all { variant ->
+                if (variant.buildType.name.equals('release')) {
+                    variant.outputs.all {
+                        outputFileName = "PIECE.${defaultConfig.versionName}.apk"
+                    }
+                }
+            }
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+    buildFeatures {
+        viewBinding true
+        buildConfig true
+    }
+}
+
+dependencies {
+
+    implementation libs.appcompat
+    implementation libs.material
+    implementation libs.activity
+    implementation libs.constraintlayout
+    testImplementation libs.junit
+    androidTestImplementation libs.ext.junit
+    androidTestImplementation libs.espresso.core
+
+    // 基础依赖
+    implementation 'androidx.appcompat:appcompat:1.6.1'
+    implementation 'com.google.android.material:material:1.11.0'
+
+    /**
+     * Gson(JSON 序列化/反序列化)
+     * 实现 JSON 数据与 Java/Kotlin 对象的互相转换
+     * */
+    implementation ("com.google.code.gson:gson:2.7")
+
+    /**
+     * YUtils(工具集合)
+     * 提供常用工具类(日志、屏幕适配、权限管理等)
+     * */
+    implementation ("com.github.yechaoa.YUtils:yutils:3.4.0")
+
+
+    /**
+     * OkHttp(网络请求)
+     * 实现网络请求功能
+     */
+    implementation ("com.squareup.okhttp3:okhttp:4.12.0")
+
+    /**
+     * lang3 (Java 工具类)
+     * 提供 Java 常用工具类
+     */
+    implementation ("org.apache.commons:commons-lang3:3.12.0")
+}

+ 22 - 0
app/proguard-rules.pro

@@ -0,0 +1,22 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
+-keep class com.yechaoa.yutils.**{*;}

+ 26 - 0
app/src/androidTest/java/com/baoshi/piece/ExampleInstrumentedTest.java

@@ -0,0 +1,26 @@
+package com.baoshi.piece;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+    @Test
+    public void useAppContext() {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        assertEquals("com.baoshi.piece", appContext.getPackageName());
+    }
+}

+ 36 - 0
app/src/main/AndroidManifest.xml

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
+    <!-- 网络权限 -->
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.VIBRATE" />
+    <!--允许应用读取公共目录(如 Documents)中的文件-->
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <!-- Android 11+ 需要声明 MANAGE_EXTERNAL_STORAGE 以获取完整文件访问权限 -->
+    <uses-permission
+        android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
+        tools:ignore="ScopedStorage" />
+
+    <application
+        android:name=".base.BaseApplication"
+        android:allowBackup="true"
+        android:usesCleartextTraffic="true"
+        android:dataExtractionRules="@xml/data_extraction_rules"
+        android:fullBackupContent="@xml/backup_rules"
+        android:icon="@mipmap/ic_logo"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_logo"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.Piece"
+        tools:targetApi="31">
+        <activity
+            android:name=".MainActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>

+ 12 - 0
app/src/main/java/com/baoshi/piece/MainActivity.java

@@ -0,0 +1,12 @@
+package com.baoshi.piece;
+
+import com.baoshi.piece.base.BaseWebViewActivity;
+import com.baoshi.piece.db.dao.DeliveryDao;
+import com.baoshi.piece.utils.AppConfig;
+
+public class MainActivity extends BaseWebViewActivity {
+    @Override
+    protected String getWebViewUrl() {
+        return AppConfig.getWebUrl().concat("#/piece-dashboard");
+    }
+}

+ 39 - 0
app/src/main/java/com/baoshi/piece/base/BaseActivity.java

@@ -0,0 +1,39 @@
+package com.baoshi.piece.base;
+
+import android.os.Bundle;
+import android.view.ViewGroup;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.viewbinding.ViewBinding;
+
+public abstract class BaseActivity<T extends ViewBinding> extends AppCompatActivity {
+    protected T viewBinding;
+    public ViewGroup getView() {
+        return (ViewGroup)viewBinding.getRoot();
+    }
+
+    public BaseActivity<T> getActivity(){
+        return this;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        viewBinding = this.bindView();
+        setContentView(viewBinding.getRoot());
+
+        this.init();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+    }
+
+    protected abstract T bindView();
+
+    protected abstract void init();
+
+    protected abstract Class<? extends BaseActivity<T>> space();
+}

+ 25 - 0
app/src/main/java/com/baoshi/piece/base/BaseApplication.java

@@ -0,0 +1,25 @@
+package com.baoshi.piece.base;
+
+import android.app.Application;
+
+import com.yechaoa.yutils.ActivityUtil;
+import com.yechaoa.yutils.YUtils;
+
+/**
+ * BaseApplication
+ * 通过在 AndroidManifest 声明
+ * 在应用程序创建时第一个被实例化的
+ * 其 onCreate() 在整个应用的生命周期仅执行一次
+ * 一般在这个继承 Application 的类中, 我们会进行一些通用工具类、模块的初始化操作
+ */
+public class BaseApplication extends Application {
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        // 初始化YUtils工具类
+        YUtils.init(this);
+        // 注册 Activity 生命周期回调
+        registerActivityLifecycleCallbacks(ActivityUtil.getActivityLifecycleCallbacks());
+    }
+}

+ 184 - 0
app/src/main/java/com/baoshi/piece/base/BaseWebViewActivity.java

@@ -0,0 +1,184 @@
+package com.baoshi.piece.base;
+
+import android.annotation.SuppressLint;
+import android.graphics.Bitmap;
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.webkit.CookieManager;
+import android.webkit.WebResourceError;
+import android.webkit.WebResourceRequest;
+import android.webkit.WebResourceResponse;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+
+import androidx.annotation.Nullable;
+
+import com.baoshi.piece.databinding.ActivityWebViewBinding;
+import com.baoshi.piece.db.dao.DeliveryDao;
+import com.baoshi.piece.db.po.DeliveryRecord;
+import com.baoshi.piece.utils.JavaScriptInterface;
+import com.baoshi.piece.utils.MacAddressUtils;
+import com.yechaoa.yutils.LogUtil;
+import com.yechaoa.yutils.ToastUtil;
+import com.yechaoa.yutils.YUtils;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public abstract class BaseWebViewActivity extends BaseActivity<ActivityWebViewBinding> {
+    private final Render render = new Render();
+    private DeliveryDao deliveryDao;
+    private static final int REQUEST_CODE = 1001;
+
+    // H5地址
+    protected abstract String getWebViewUrl();
+
+    // onStart回调H5 Javascript, 默认调用onRefresh()
+    protected String getCallStartJavascript() {
+        return "javascript:(function() {" +
+                "onRefresh()" +
+                "})()";
+    }
+
+    @Override
+    protected ActivityWebViewBinding bindView() {
+        return ActivityWebViewBinding.inflate(getLayoutInflater());
+    }
+
+    @Override
+    protected Class<? extends BaseActivity<ActivityWebViewBinding>> space() {
+        return this.getClass();
+    }
+
+    @Override
+    protected void init() {
+        render.init();
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        YUtils.showLoading(this, "加载中...");
+        deliveryDao = new DeliveryDao(this);
+        super.onCreate(savedInstanceState);
+    }
+
+    // 在活动由不可见变为可见的时候调用
+    @Override
+    protected void onStart() {
+        super.onStart();
+        // 调用H5 Javascript
+        String jsCode = getCallStartJavascript();
+        if (StringUtils.isNotEmpty(jsCode)) {
+            viewBinding.webView.evaluateJavascript(jsCode, null);
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (deliveryDao != null) {
+            deliveryDao.close(); // 在 DeliveryDao 中添加关闭方法
+        }
+        viewBinding.webView.clearCache(true); // 清除缓存
+        viewBinding.webView.clearHistory(); // 清除历史记录
+        viewBinding.webView.clearFormData(); // 清除表单数据
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            this.moveTaskToBack(true);
+            return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    /**
+     * 获取Header
+     */
+    public Map<String, String> getHeaderParams() {
+        Map<String, String> params = new HashMap<>();
+        params.put("Source", "app");
+        return params;
+    }
+
+
+    /**
+     * 页面渲染
+     */
+    private class Render {
+        @SuppressLint("SetJavaScriptEnabled")
+        private void init() {
+            CookieManager cookieManager = CookieManager.getInstance();
+            cookieManager.setAcceptCookie(true);
+            cookieManager.setAcceptThirdPartyCookies(viewBinding.webView, true);
+
+            WebSettings webSettings = viewBinding.webView.getSettings();
+
+            // 基础设置
+            webSettings.setJavaScriptEnabled(true);
+            webSettings.setDomStorageEnabled(true);
+            webSettings.setAllowFileAccess(true);
+            // 推荐增加的现代缓存配置
+            webSettings.setCacheMode(WebSettings.LOAD_DEFAULT); // 使用默认缓存策略
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                webSettings.setSafeBrowsingEnabled(true); // 启用安全浏览
+            }
+            // JavaScript 接口
+            viewBinding.webView.addJavascriptInterface(
+                    new JavaScriptInterface(getActivity(), getHeaderParams(), viewBinding.webView, deliveryDao),
+                    "android"
+            );
+
+            this.inject();
+
+            // 加载h5
+            viewBinding.webView.loadUrl(getWebViewUrl(), getHeaderParams());
+        }
+
+        // 注入web加载点
+        private void inject() {
+            viewBinding.webView.setWebViewClient(new WebViewClient() {
+                // 开始加载页面
+                @Override
+                public void onPageStarted(WebView view, String url, Bitmap favicon) {
+                    super.onPageStarted(view, url, favicon);
+                    LogUtil.d("onPageStarted: " + url);
+                }
+
+                // 完成页面加载
+                @Override
+                public void onPageFinished(WebView view, String url) {
+                    LogUtil.d("onPageFinished: " + url);
+                    super.onPageFinished(view, url);
+                    YUtils.hideLoading();
+                }
+
+                // 页面加载失败
+                @Override
+                public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
+                    super.onReceivedError(view, request, error);
+                    LogUtil.e("WebView加载失败url: " + request.getUrl().toString());
+                    LogUtil.e("WebView加载失败原因: " + error.getDescription());
+                    ToastUtil.showCenter("页面加载失败!" + error.getDescription());
+                }
+
+                // 拦截页面资源请求
+                @Nullable
+                @Override
+                public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
+                    LogUtil.d("请求地址= " + request.getUrl().toString());
+                    return super.shouldInterceptRequest(view, request);
+                }
+            });
+        }
+    }
+
+}

+ 19 - 0
app/src/main/java/com/baoshi/piece/controller/MainController.java

@@ -0,0 +1,19 @@
+package com.baoshi.piece.controller;
+
+import com.baoshi.piece.model.MessageModel;
+import com.baoshi.piece.view.MainView;
+
+public class MainController {
+    private final MainView view;
+    private final MessageModel model;
+
+    public MainController(MainView view) {
+        this.view = view;
+        this.model = new MessageModel();
+    }
+
+    public void loadMessage() {
+        String message = model.getMessage();
+        view.showMessage(message);
+    }
+}

+ 44 - 0
app/src/main/java/com/baoshi/piece/db/DBHelper.java

@@ -0,0 +1,44 @@
+package com.baoshi.piece.db;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
+
+public class DBHelper extends SQLiteOpenHelper {
+    private static final String DB_NAME = "delivery_db.db";
+    private static final int DB_VERSION = 2;
+    // 送货记录表结构
+    private static final String CREATE_TABLE_DELIVERY =
+            "CREATE TABLE delivery (" +
+                    "id INTEGER PRIMARY KEY AUTOINCREMENT," +
+                    "deliveryNo TEXT NOT NULL UNIQUE," +  // 送货单号(唯一)
+                    "machine TEXT NOT NULL," +           // 机器编号
+                    "operator TEXT NOT NULL," +          // 操作员
+                    "operatorName TEXT NOT NULL," +      // 操作员名称
+                    "operationTime TEXT NOT NULL)";     // 操作时间(ISO8601格式)
+
+    public DBHelper(Context context) {
+        super(context, DB_NAME, null, DB_VERSION);
+        // 开启WAL模式 日志监控
+        SQLiteDatabase db = getWritableDatabase();
+        db.enableWriteAheadLogging();  // 开启WAL模式
+        Log.d("DB_PATH", db.getPath());  // 输出数据库路径
+    }
+
+    @Override
+    public void onCreate(SQLiteDatabase db) {
+        db.execSQL(CREATE_TABLE_DELIVERY);
+        // 为唯一键添加索引
+        db.execSQL("CREATE UNIQUE INDEX idx_delivery_no ON delivery(deliveryNo)");
+        db.execSQL("CREATE INDEX idx_operation_time ON delivery(operationTime DESC)");
+    }
+
+    @Override
+    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+        db.execSQL("DROP TABLE IF EXISTS delivery");
+        onCreate(db);
+    }
+
+
+}

+ 222 - 0
app/src/main/java/com/baoshi/piece/db/dao/DeliveryDao.java

@@ -0,0 +1,222 @@
+package com.baoshi.piece.db.dao;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteConstraintException;
+import android.database.sqlite.SQLiteDatabase;
+import android.util.Log;
+
+import com.baoshi.piece.db.DBHelper;
+import com.baoshi.piece.db.po.DeliveryRecord;
+import com.baoshi.piece.utils.TimeUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DeliveryDao {
+    private final DBHelper dbHelper;
+
+    public DeliveryDao(Context context) {
+        dbHelper = new DBHelper(context);
+    }
+
+    // 添加送货记录
+    public long addDelivery(DeliveryRecord record) {
+        // 1. 先检查是否存在
+        if (isDeliveryNoExists(record.getDeliveryNo())) {
+            return -1; // 已存在标识
+        }
+
+        SQLiteDatabase db = dbHelper.getWritableDatabase();
+        ContentValues values = new ContentValues();
+        values.put("deliveryNo", record.getDeliveryNo());
+        values.put("machine", record.getMachine());
+        values.put("operator", record.getOperator());
+        values.put("operatorName", record.getOperatorName());
+        values.put("operationTime", record.getOperationTime());
+
+        try {
+            long id = db.insertOrThrow("delivery", null, values);
+            return id; // 成功返回ID
+        } catch (SQLiteConstraintException e) {
+            Log.e("DB_ERROR", "唯一键冲突: " + record.getDeliveryNo(), e);
+            return -1; // 冲突
+        } catch (Exception e) {
+            Log.e("DB_ERROR", "插入失败", e);
+            return -2; // 其他错误
+        } finally {
+            db.close();
+        }
+    }
+
+    /**
+     * 检查送货单号是否存在
+     */
+    public boolean isDeliveryNoExists(String deliveryNo) {
+        SQLiteDatabase db = dbHelper.getReadableDatabase();
+        Cursor cursor = null;
+
+        try {
+            cursor = db.query(
+                    "delivery",
+                    new String[]{"deliveryNo"},
+                    "deliveryNo = ?",
+                    new String[]{deliveryNo},
+                    null, null, null
+            );
+            return cursor != null && cursor.getCount() > 0;
+        } finally {
+            if (cursor != null) cursor.close();
+            db.close();
+        }
+    }
+
+    // 根据送货单号查询
+    public DeliveryRecord getByDeliveryNo(String deliveryNo) {
+        SQLiteDatabase db = dbHelper.getReadableDatabase();
+        Cursor cursor = db.query(
+            "delivery",
+            null,
+            "deliveryNo=?",
+            new String[]{deliveryNo},
+            null, null, null
+        );
+
+        DeliveryRecord record = null;
+        if (cursor != null && cursor.moveToFirst()) {
+            record = new DeliveryRecord(
+                cursor.getString(cursor.getColumnIndexOrThrow("deliveryNo")),
+                cursor.getString(cursor.getColumnIndexOrThrow("machine")),
+                cursor.getString(cursor.getColumnIndexOrThrow("operator")),
+                cursor.getString(cursor.getColumnIndexOrThrow("operatorName"))
+            );
+            record.setId(cursor.getLong(cursor.getColumnIndexOrThrow("id")));
+            record.setOperationTime(cursor.getString(cursor.getColumnIndexOrThrow("operationTime")));
+            cursor.close();
+        }
+        db.close();
+        return record;
+    }
+
+    // 分页查询方法
+    public List<DeliveryRecord> getRecordsPaginated(int pageIndex, int pageSize) {
+        List<DeliveryRecord> records = new ArrayList<>();
+        SQLiteDatabase db = dbHelper.getReadableDatabase();
+        // 计算偏移量
+        int offset = (pageIndex - 1) * pageSize;
+        // 分页查询SQL
+        String sql = "SELECT * FROM delivery ORDER BY operationTime DESC LIMIT ? OFFSET ?";
+        Cursor cursor = null;
+        try {
+            cursor = db.rawQuery(sql, new String[]{String.valueOf(pageSize), String.valueOf(offset)});
+
+            if (cursor != null && cursor.moveToFirst()) {
+                do {
+                    DeliveryRecord record = new DeliveryRecord(
+                            cursor.getString(cursor.getColumnIndexOrThrow("deliveryNo")),
+                            cursor.getString(cursor.getColumnIndexOrThrow("machine")),
+                            cursor.getString(cursor.getColumnIndexOrThrow("operator")),
+                            cursor.getString(cursor.getColumnIndexOrThrow("operatorName"))
+                    );
+                    record.setId(cursor.getLong(cursor.getColumnIndexOrThrow("id")));
+                    record.setOperationTime(cursor.getString(cursor.getColumnIndexOrThrow("operationTime")));
+                    records.add(record);
+                } while (cursor.moveToNext());
+            }
+        } finally {
+            if (cursor != null) cursor.close();
+            db.close();
+        }
+        return records;
+    }
+
+    // 获取总记录数
+    public int getTotalRecordsCount() {
+        SQLiteDatabase db = dbHelper.getReadableDatabase();
+        Cursor cursor = null;
+        try {
+            cursor = db.rawQuery("SELECT COUNT(*) FROM delivery", null);
+            if (cursor != null && cursor.moveToFirst()) {
+                return cursor.getInt(0);
+            }
+            return 0;
+        } finally {
+            if (cursor != null) cursor.close();
+            db.close();
+        }
+    }
+
+
+    // 获取所有记录(按时间倒序)
+    public List<DeliveryRecord> getAllRecords() {
+        List<DeliveryRecord> records = new ArrayList<>();
+        SQLiteDatabase db = dbHelper.getReadableDatabase();
+        Cursor cursor = db.query(
+            "delivery",
+            null,
+            null, null,
+            null, null,
+            "operationTime DESC"  // 按操作时间倒序排列
+        );
+
+        if (cursor != null) {
+            while (cursor.moveToNext()) {
+                DeliveryRecord record = new DeliveryRecord(
+                    cursor.getString(cursor.getColumnIndexOrThrow("deliveryNo")),
+                    cursor.getString(cursor.getColumnIndexOrThrow("machine")),
+                    cursor.getString(cursor.getColumnIndexOrThrow("operator")),
+                    cursor.getString(cursor.getColumnIndexOrThrow("operatorName"))
+                );
+                record.setId(cursor.getLong(cursor.getColumnIndexOrThrow("id")));
+                record.setOperationTime(cursor.getString(cursor.getColumnIndexOrThrow("operationTime")));
+                records.add(record);
+            }
+            cursor.close();
+        }
+        db.close();
+        return records;
+    }
+
+    // 更新机器信息
+    public int updateMachine(String deliveryNo, String newMachine) {
+        SQLiteDatabase db = dbHelper.getWritableDatabase();
+        ContentValues values = new ContentValues();
+        values.put("machine", newMachine);
+        values.put("operationTime", TimeUtils.getBeijingTime());
+        
+        int count = db.update(
+            "delivery",
+            values,
+            "deliveryNo=?",
+            new String[]{deliveryNo}
+        );
+        db.close();
+        return count;
+    }
+
+    // 删除记录
+    public int deleteRecord(String deliveryNo) {
+        SQLiteDatabase db = dbHelper.getWritableDatabase();
+        int count = db.delete(
+            "delivery",
+            "deliveryNo=?",
+            new String[]{deliveryNo}
+        );
+        db.close();
+        return count;
+    }
+
+    // 删除记录
+    public void deleteAll() {
+        SQLiteDatabase db = dbHelper.getWritableDatabase();
+        db.delete("delivery", null, null);
+        db.close();
+    }
+
+    public void close() {
+        if (dbHelper != null) {
+            dbHelper.close();
+        }
+    }
+}

+ 43 - 0
app/src/main/java/com/baoshi/piece/db/po/DeliveryRecord.java

@@ -0,0 +1,43 @@
+package com.baoshi.piece.db.po;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+public class DeliveryRecord {
+    private long id;
+    private String deliveryNo;
+    private String machine;
+    private String operator;
+    private String operatorName;
+    private String operationTime;
+
+    // 构造方法
+    public DeliveryRecord(String deliveryNo, String machine, String operator, String operatorName) {
+        this.deliveryNo = deliveryNo;
+        this.machine = machine;
+        this.operator = operator;
+        this.operatorName = operatorName;
+        this.operationTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
+                                .format(new Date());
+    }
+
+    // Getters
+    public long getId() { return id; }
+    public String getDeliveryNo() { return deliveryNo; }
+    public String getMachine() { return machine; }
+    public String getOperator() { return operator; }
+    public String getOperationTime() { return operationTime; }
+
+    // Setters
+    public void setId(long id) { this.id = id; }
+    public void setOperationTime(String time) { this.operationTime = time; }
+
+    public String getOperatorName() {
+        return operatorName;
+    }
+
+    public void setOperatorName(String operatorName) {
+        this.operatorName = operatorName;
+    }
+}

+ 13 - 0
app/src/main/java/com/baoshi/piece/model/MessageModel.java

@@ -0,0 +1,13 @@
+package com.baoshi.piece.model;
+
+public class MessageModel {
+    private String message;
+
+    public MessageModel() {
+        this.message = "Hello World!";
+    }
+
+    public String getMessage() {
+        return message;
+    }
+}

+ 36 - 0
app/src/main/java/com/baoshi/piece/model/PaginationModel.java

@@ -0,0 +1,36 @@
+package com.baoshi.piece.model;
+
+import java.util.List;
+
+public class PaginationModel<T> {
+    private List<T> data;        // 当前页数据
+    private int currentPage;     // 当前页码
+    private int pageSize;        // 每页大小
+    private int totalItems;      // 总记录数
+    private int totalPages;      // 总页数
+    
+    public PaginationModel(List<T> data, int currentPage, int pageSize, int totalItems) {
+        this.data = data;
+        this.currentPage = currentPage;
+        this.pageSize = pageSize;
+        this.totalItems = totalItems;
+        this.totalPages = (int) Math.ceil((double) totalItems / pageSize);
+    }
+    
+    // Getters
+    public List<T> getData() { return data; }
+    public int getCurrentPage() { return currentPage; }
+    public int getPageSize() { return pageSize; }
+    public int getTotalItems() { return totalItems; }
+    public int getTotalPages() { return totalPages; }
+    
+    // 是否有上一页
+    public boolean hasPreviousPage() {
+        return currentPage > 1;
+    }
+    
+    // 是否有下一页
+    public boolean hasNextPage() {
+        return currentPage < totalPages;
+    }
+}

+ 9 - 0
app/src/main/java/com/baoshi/piece/utils/AppConfig.java

@@ -0,0 +1,9 @@
+package com.baoshi.piece.utils;
+
+import com.baoshi.piece.BuildConfig;
+
+public class AppConfig {
+    public static String getWebUrl() {
+        return BuildConfig.WEB_URL;
+    }
+}

+ 146 - 0
app/src/main/java/com/baoshi/piece/utils/JavaScriptInterface.java

@@ -0,0 +1,146 @@
+package com.baoshi.piece.utils;
+
+import android.app.Activity;
+import android.util.Log;
+import android.webkit.JavascriptInterface;
+import android.webkit.WebView;
+
+import com.baoshi.piece.db.dao.DeliveryDao;
+import com.baoshi.piece.db.po.DeliveryRecord;
+import com.yechaoa.yutils.GsonUtil;
+import com.yechaoa.yutils.SpUtil;
+import com.yechaoa.yutils.YUtils;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 提供给H5调用的接口
+ */
+public class JavaScriptInterface {
+    private DeliveryDao deliveryDao;
+    private final Activity activity;
+    private final WebView webView;
+    private final Map<String, String> headerParams;
+
+    public JavaScriptInterface(Activity activity, Map<String, String> headerParams, WebView webView, DeliveryDao deliveryDao) {
+        this.activity = activity;
+        this.webView = webView;
+        this.headerParams = headerParams;
+        this.deliveryDao = new DeliveryDao(activity);
+    }
+
+    /**
+     * 返回
+     */
+    @JavascriptInterface
+    public void goBack() {
+        activity.moveTaskToBack(true);
+    }
+
+    /**
+     * 关闭当前页面,回到主页
+     */
+    @JavascriptInterface
+    public void finish() {
+        activity.finish();
+    }
+
+    /**
+     * H5获取header数据(window.android.getHeader)
+     *
+     * @return
+     */
+    @JavascriptInterface
+    public String getHeader() {
+        return GsonUtil.GsonString(headerParams);
+    }
+
+    /**
+     * H5获取mac地址(window.android.getMacAddress)
+     */
+    @JavascriptInterface
+    public String readMacAddress() {
+        return MacAddressUtils.readMacAddress(activity);
+    }
+
+    /**
+     * H5保存mac地址(window.android.saveMacAddress)
+     * @param macAddress mac地址
+     * @return true:保存成功,false:保存失败
+     */
+    @JavascriptInterface
+    public boolean saveMacAddress(String macAddress) {
+        return MacAddressUtils.saveMacAddress(activity, macAddress);
+    }
+
+    /**
+     * 添加新记录
+     *
+     * @param macAddress mac地址
+     * @param deliveryNo 快递单号
+     * @param userId 用户id
+     * @param userName 用户名
+     * @return
+     */
+    @JavascriptInterface
+    public long addDelivery(String macAddress, String deliveryNo, String userId, String userName) {
+        DeliveryRecord record = new DeliveryRecord(deliveryNo, macAddress, userId, userName);
+        return deliveryDao.addDelivery(record);
+    }
+
+    /**
+     * 分页查询全部记录
+     */
+    @JavascriptInterface
+    public String pageDelivery(int page, int size) {
+        return GsonUtil.GsonString(deliveryDao.getRecordsPaginated(page, size));
+    }
+
+    /**
+     * 焦点
+     */
+    @JavascriptInterface
+    public void focus() {
+        webView.requestFocus();
+    }
+
+    /**
+     * 扫描成功语音
+     */
+    @JavascriptInterface
+    public void scanSuccessVib() {
+        TipActionUtils.getInstance().scanSuccessTip(activity);
+    }
+
+    /**
+     * 扫描失败语音
+     */
+    @JavascriptInterface
+    public void scanErrorVib() {
+        TipActionUtils.getInstance().scanErrorTip(activity);
+    }
+
+    /**
+     * 重复扫描语音
+     */
+    @JavascriptInterface
+    public void scanRepeatVib() {
+        TipActionUtils.getInstance().scanRepeatTip(activity);
+    }
+
+    @JavascriptInterface
+    public void saveUserId(String userId) {
+        SpUtil.setString("userId", userId);
+    }
+
+    @JavascriptInterface
+    public void getUserId() {
+        SpUtil.getString("userId");
+    }
+
+    @JavascriptInterface
+    public String getVersionName() {
+        return YUtils.getVersionName();
+    }
+}

+ 160 - 0
app/src/main/java/com/baoshi/piece/utils/MacAddressUtils.java

@@ -0,0 +1,160 @@
+package com.baoshi.piece.utils;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.content.ContentResolver;
+import android.os.Build;
+import android.util.Log;
+import java.io.InputStream;
+
+import java.io.OutputStream;
+
+
+
+public class MacAddressUtils {
+    private static final String TAG = "MacAddressManager";
+    private static final String DIR_NAME = "MyAppData";
+    private static final String FILE_NAME = "mac_address.txt";
+    private static final String MIME_TYPE = "text/plain";
+
+    // 保存到公共的 Documents/MyAppData 目录
+    public static boolean saveMacAddress(Context context, String mac) {
+        try {
+            deleteMacAddress(context); // 删除已保存的文件(可选)
+            ContentResolver resolver = context.getContentResolver();
+
+            // 构建文件元数据
+            ContentValues values = new ContentValues();
+            values.put(MediaStore.MediaColumns.DISPLAY_NAME, FILE_NAME);
+            values.put(MediaStore.MediaColumns.MIME_TYPE, MIME_TYPE);
+            values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOCUMENTS + "/" + DIR_NAME);
+
+            // Android 10+ 需要明确指定 IS_PENDING 状态
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+                values.put(MediaStore.MediaColumns.IS_PENDING, 1);
+            }
+
+            // 获取或创建文件 Uri
+            Uri fileUri = resolver.insert(MediaStore.Files.getContentUri("external"), values);
+            if (fileUri == null) {
+                Log.e(TAG, "Failed to create file Uri");
+                return false;
+            }
+
+            try (OutputStream os = resolver.openOutputStream(fileUri)) {
+                if (os == null) {
+                    Log.e(TAG, "Failed to open output stream");
+                    return false;
+                }
+                os.write(mac.getBytes());
+
+                // Android 10+ 需要更新 IS_PENDING 状态
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+                    values.clear();
+                    values.put(MediaStore.MediaColumns.IS_PENDING, 0);
+                    resolver.update(fileUri, values, null, null);
+                }
+                return true;
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Save failed: " + e.getMessage());
+            return false;
+        }
+    }
+
+    // 从公共目录读取
+    public static String readMacAddress(Context context) {
+        try {
+            ContentResolver resolver = context.getContentResolver();
+            Uri collection = MediaStore.Files.getContentUri("external");
+
+            // 构建查询条件
+            String selection = MediaStore.MediaColumns.RELATIVE_PATH + " LIKE ? AND " +
+                    MediaStore.MediaColumns.DISPLAY_NAME + " = ?";
+            String[] selectionArgs = new String[]{
+                    "%" + DIR_NAME + "%",  // 路径包含目录名
+                    FILE_NAME
+            };
+
+            // 执行查询
+            try (android.database.Cursor cursor = resolver.query(
+                    collection,
+                    new String[]{MediaStore.MediaColumns._ID},
+                    selection,
+                    selectionArgs,
+                    null
+            )) {
+                if (cursor == null || !cursor.moveToFirst()) {
+                    Log.d(TAG, "File not found");
+                    return null;
+                }
+
+                // 获取文件 Uri
+                long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID));
+                Uri fileUri = Uri.withAppendedPath(collection, String.valueOf(id));
+
+                // 读取文件内容
+                try (InputStream is = resolver.openInputStream(fileUri)) {
+                    if (is == null) {
+                        Log.e(TAG, "Failed to open input stream");
+                        return null;
+                    }
+                    byte[] buffer = new byte[is.available()];
+                    is.read(buffer);
+                    return new String(buffer);
+                }
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Read failed: " + e.getMessage());
+            return null;
+        }
+    }
+
+    // 删除已保存的文件(可选)
+    public static boolean deleteMacAddress(Context context) {
+        try {
+            ContentResolver resolver = context.getContentResolver();
+            Uri fileUri = findFileUri(context);
+            if (fileUri != null) {
+                return resolver.delete(fileUri, null, null) > 0;
+            }
+            return false;
+        } catch (Exception e) {
+            Log.e(TAG, "Delete failed: " + e.getMessage());
+            return false;
+        }
+    }
+
+    // 辅助方法:查找文件 Uri
+    private static Uri findFileUri(Context context) {
+        ContentResolver resolver = context.getContentResolver();
+        Uri collection = MediaStore.Files.getContentUri("external");
+
+        String[] projection = new String[]{MediaStore.MediaColumns._ID};
+        String selection = MediaStore.MediaColumns.RELATIVE_PATH + " LIKE ? AND " +
+                MediaStore.MediaColumns.DISPLAY_NAME + " = ?";
+        String[] selectionArgs = new String[]{
+                "%" + DIR_NAME + "%",
+                FILE_NAME
+        };
+
+        try (android.database.Cursor cursor = resolver.query(
+                collection,
+                projection,
+                selection,
+                selectionArgs,
+                null
+        )) {
+            if (cursor != null && cursor.moveToFirst()) {
+                long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID));
+                return Uri.withAppendedPath(collection, String.valueOf(id));
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Find file failed: " + e.getMessage());
+        }
+        return null;
+    }
+}

+ 36 - 0
app/src/main/java/com/baoshi/piece/utils/TimeUtils.java

@@ -0,0 +1,36 @@
+package com.baoshi.piece.utils;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+public class TimeUtils {
+    // 北京时区(GMT+8)
+    private static final TimeZone BEIJING_ZONE = TimeZone.getTimeZone("GMT+8");
+    
+    // 北京时间的格式化器
+    private static final SimpleDateFormat BEIJING_FORMAT = 
+        new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
+    
+    static {
+        BEIJING_FORMAT.setTimeZone(BEIJING_ZONE);
+    }
+    
+    // 获取当前北京时间
+    public static String getBeijingTime() {
+        return BEIJING_FORMAT.format(new Date());
+    }
+    
+    // 获取带时区信息的时间(ISO8601格式)
+    public static String getBeijingTimeISO() {
+        SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX", Locale.getDefault());
+        isoFormat.setTimeZone(BEIJING_ZONE);
+        return isoFormat.format(new Date());
+    }
+    
+    // 转换任意时间为北京时间
+    public static String convertToBeijingTime(Date date) {
+        return BEIJING_FORMAT.format(date);
+    }
+}

+ 84 - 0
app/src/main/java/com/baoshi/piece/utils/TipActionUtils.java

@@ -0,0 +1,84 @@
+package com.baoshi.piece.utils;
+
+import android.app.Activity;
+import android.media.MediaPlayer;
+import android.os.Build;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+
+import com.baoshi.piece.R;
+import com.yechaoa.yutils.LogUtil;
+
+/**
+ * 一些提示 包含但不仅限于语音 震动等
+ *  出于资源复用与资源初始化开销的角度来看使用单例懒加载
+ *
+ * @author Zhendong Zhou
+ * @date 2022/1/11
+ */
+public class TipActionUtils {
+
+    private MediaPlayer scanErrMedia;
+    private MediaPlayer scanRepeatMedia;
+    private MediaPlayer scanSucMedia;
+    private Vibrator scanErrVib;
+    private Vibrator scanRepeatVib;
+    private volatile static TipActionUtils tipAction;
+
+    public static TipActionUtils getInstance() {
+        if (tipAction==null) {
+            tipAction = new TipActionUtils();
+        }
+        return tipAction;
+    }
+
+    public void scanRepeatTip(Activity activity){
+        if (scanRepeatMedia==null){
+            scanRepeatMedia = MediaPlayer.create(activity, R.raw.scanner_repeat);
+        }
+        if (scanRepeatVib==null){
+            scanRepeatVib = (Vibrator) activity.getSystemService(Activity.VIBRATOR_SERVICE);
+        }
+        scanRepeatMedia.start();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            scanRepeatVib.vibrate(VibrationEffect.createOneShot(1000, -1));
+        }else{
+            scanRepeatVib.vibrate(new long[]{1000}, -1);
+        }
+    }
+
+    public void scanErrorTip(Activity activity){
+        if (scanErrMedia==null){
+            scanErrMedia = MediaPlayer.create(activity, R.raw.error);
+        }
+        if (scanErrVib==null){
+            scanErrVib = (Vibrator) activity.getSystemService(Activity.VIBRATOR_SERVICE);
+        }
+        scanErrMedia.start();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            scanErrVib.vibrate(VibrationEffect.createOneShot(1000, -1));
+        }else{
+            scanErrVib.vibrate(new long[]{1000}, -1);
+        }
+    }
+
+    public void scanSuccessTip(Activity activity){
+        if (scanSucMedia==null){
+            scanSucMedia = MediaPlayer.create(activity,R.raw.tip);
+        }
+        scanSucMedia.start();
+    }
+
+    public void successVib(Activity activity, String... args) {
+        if (args != null && args.length > 0 &&  args[0] != null) {
+            LogUtil.d(args[0]);
+        }
+        scanSuccessTip(activity);
+    }
+    public void errorVib(Activity activity, String... args) {
+        if (args != null && args.length > 0 && args[0] != null) {
+            LogUtil.e(args[0]);
+        }
+        scanErrorTip(activity);
+    }
+}

+ 5 - 0
app/src/main/java/com/baoshi/piece/view/MainView.java

@@ -0,0 +1,5 @@
+package com.baoshi.piece.view;
+
+public interface MainView {
+    void showMessage(String message);
+}

+ 170 - 0
app/src/main/res/drawable/ic_launcher_background.xml

@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#3DDC84"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+</vector>

+ 30 - 0
app/src/main/res/drawable/ic_launcher_foreground.xml

@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="85.84757"
+                android:endY="92.4963"
+                android:startX="42.9492"
+                android:startY="49.59793"
+                android:type="linear">
+                <item
+                    android:color="#44000000"
+                    android:offset="0.0" />
+                <item
+                    android:color="#00000000"
+                    android:offset="1.0" />
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path
+        android:fillColor="#FFFFFF"
+        android:fillType="nonZero"
+        android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
+        android:strokeWidth="1"
+        android:strokeColor="#00000000" />
+</vector>

+ 15 - 0
app/src/main/res/layout/activity_main.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/main"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".MainActivity">
+    <TextView
+        android:id="@+id/tv_message"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="24sp"/>
+</LinearLayout>

+ 13 - 0
app/src/main/res/layout/activity_web_view.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/main"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:context=".base.BaseWebViewActivity">
+    <WebView
+        android:id="@+id/webView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+</LinearLayout>

+ 5 - 0
app/src/main/res/mipmap-anydpi-v26/ic_logo.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@color/ic_logo_background"/>
+    <foreground android:drawable="@mipmap/ic_logo_foreground"/>
+</adaptive-icon>

+ 5 - 0
app/src/main/res/mipmap-anydpi-v26/ic_logo_round.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@color/ic_logo_background"/>
+    <foreground android:drawable="@mipmap/ic_logo_foreground"/>
+</adaptive-icon>

+ 6 - 0
app/src/main/res/mipmap-anydpi/ic_launcher.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

+ 6 - 0
app/src/main/res/mipmap-anydpi/ic_launcher_round.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

BIN
app/src/main/res/mipmap-hdpi/ic_logo.png


BIN
app/src/main/res/mipmap-hdpi/ic_logo_foreground.png


BIN
app/src/main/res/mipmap-hdpi/ic_logo_round.png


BIN
app/src/main/res/mipmap-mdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp


BIN
app/src/main/res/raw/error.mp3


BIN
app/src/main/res/raw/scanner_repeat.wav


BIN
app/src/main/res/raw/tip.mp3


+ 16 - 0
app/src/main/res/values-night/themes.xml

@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.Piece" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_200</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/black</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_200</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+</resources>

+ 12 - 0
app/src/main/res/values/colors.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="ic_logo_background">#074E93</color>
+    <color name="purple_200">#FFBB86FC</color>
+    <color name="purple_500">#FF6200EE</color>
+    <color name="purple_700">#FF3700B3</color>
+    <color name="teal_200">#FF03DAC5</color>
+    <color name="teal_700">#FF018786</color>
+    <color name="black">#FF000000</color>
+    <color name="white">#FFFFFFFF</color>
+
+</resources>

+ 3 - 0
app/src/main/res/values/strings.xml

@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">计件宝</string>
+</resources>

+ 16 - 0
app/src/main/res/values/themes.xml

@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.Piece" parent="Theme.MaterialComponents.DayNight.NoActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_500</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/white</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_700</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+</resources>

+ 13 - 0
app/src/main/res/xml/backup_rules.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+   Sample backup rules file; uncomment and customize as necessary.
+   See https://developer.android.com/guide/topics/data/autobackup
+   for details.
+   Note: This file is ignored for devices older that API 31
+   See https://developer.android.com/about/versions/12/backup-restore
+-->
+<full-backup-content>
+    <!--
+   <include domain="sharedpref" path="."/>
+   <exclude domain="sharedpref" path="device.xml"/>
+-->
+</full-backup-content>

+ 19 - 0
app/src/main/res/xml/data_extraction_rules.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+   Sample data extraction rules file; uncomment and customize as necessary.
+   See https://developer.android.com/about/versions/12/backup-restore#xml-changes
+   for details.
+-->
+<data-extraction-rules>
+    <cloud-backup>
+        <!-- TODO: Use <include> and <exclude> to control what is backed up.
+        <include .../>
+        <exclude .../>
+        -->
+    </cloud-backup>
+    <!--
+    <device-transfer>
+        <include .../>
+        <exclude .../>
+    </device-transfer>
+    -->
+</data-extraction-rules>

+ 17 - 0
app/src/test/java/com/baoshi/piece/ExampleUnitTest.java

@@ -0,0 +1,17 @@
+package com.baoshi.piece;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+public class ExampleUnitTest {
+    @Test
+    public void addition_isCorrect() {
+        assertEquals(4, 2 + 2);
+    }
+}

+ 4 - 0
build.gradle

@@ -0,0 +1,4 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+alias(libs.plugins.android.application) apply false
+}

+ 22 - 0
gradle.properties

@@ -0,0 +1,22 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. For more details, visit
+# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+android.enableJetifier=true
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true

+ 22 - 0
gradle/libs.versions.toml

@@ -0,0 +1,22 @@
+[versions]
+agp = "8.4.0"
+junit = "4.13.2"
+junitVersion = "1.1.5"
+espressoCore = "3.5.1"
+appcompat = "1.6.1"
+material = "1.10.0"
+activity = "1.8.0"
+constraintlayout = "2.1.4"
+
+[libraries]
+junit = { group = "junit", name = "junit", version.ref = "junit" }
+ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
+espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
+appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
+material = { group = "com.google.android.material", name = "material", version.ref = "material" }
+activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
+constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
+
+[plugins]
+android-application = { id = "com.android.application", version.ref = "agp" }
+

BIN
gradle/wrapper/gradle-wrapper.jar


+ 6 - 0
gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,6 @@
+#Tue May 27 08:58:59 CST 2025
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=file:///D:/java/gradle/gradle-8.7.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists

+ 185 - 0
gradlew

@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# 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
+#
+#      https://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.
+#
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=`expr $i + 1`
+    done
+    case $i in
+        0) set -- ;;
+        1) set -- "$args0" ;;
+        2) set -- "$args0" "$args1" ;;
+        3) set -- "$args0" "$args1" "$args2" ;;
+        4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"

+ 89 - 0
gradlew.bat

@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

+ 29 - 0
settings.gradle

@@ -0,0 +1,29 @@
+pluginManagement {
+    repositories {
+        maven {
+            url = uri("https://www.jitpack.io")
+        }
+        maven {
+            url = uri("https://maven.aliyun.com/repository/public/")
+        }
+        google()
+        mavenCentral()
+        gradlePluginPortal()
+    }
+}
+dependencyResolutionManagement {
+    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+    repositories {
+        maven {
+            url = uri("https://www.jitpack.io")
+        }
+        maven {
+            url = uri("https://maven.aliyun.com/repository/public/")
+        }
+        google()
+        mavenCentral()
+    }
+}
+
+rootProject.name = "Piece"
+include ':app'