Browse Source

签到签出的流程

liukai 8 months ago
parent
commit
f6f94c43c0
38 changed files with 1787 additions and 107 deletions
  1. 99 3
      app/src/main/java/vn/hongyegroup/yybusiness/adapter/AttendanceAdapter.java
  2. 4 1
      app/src/main/java/vn/hongyegroup/yybusiness/entity/AttendanceBean.java
  3. 10 3
      app/src/main/java/vn/hongyegroup/yybusiness/http/ApiService.kt
  4. 68 0
      app/src/main/java/vn/hongyegroup/yybusiness/mvvm/AppRepository.kt
  5. 1 1
      app/src/main/java/vn/hongyegroup/yybusiness/mvvm/LoginViewModel.kt
  6. 198 0
      app/src/main/java/vn/hongyegroup/yybusiness/mvvm/MainViewModel.kt
  7. 1 0
      app/src/main/java/vn/hongyegroup/yybusiness/ui/LoginActivity.kt
  8. 209 30
      app/src/main/java/vn/hongyegroup/yybusiness/ui/MainActivity.kt
  9. 25 2
      app/src/main/java/vn/hongyegroup/yybusiness/ui/SplashActivity.kt
  10. 29 0
      app/src/main/java/vn/hongyegroup/yybusiness/widget/AreYouSureUtil.kt
  11. 61 0
      app/src/main/java/vn/hongyegroup/yybusiness/widget/ShowCheckInUtil.kt
  12. 18 0
      app/src/main/java/vn/hongyegroup/yybusiness/widget/signWrite/DrawPoint.java
  13. 311 0
      app/src/main/java/vn/hongyegroup/yybusiness/widget/signWrite/HandWriteView.java
  14. 114 0
      app/src/main/java/vn/hongyegroup/yybusiness/widget/signWrite/PointUtil.java
  15. 44 0
      app/src/main/java/vn/hongyegroup/yybusiness/widget/signWrite/SignFileOutputStream.java
  16. 34 0
      app/src/main/java/vn/hongyegroup/yybusiness/widget/signWrite/TimedPoint.java
  17. 8 0
      app/src/main/res/drawable/shape_sign_blue_bg.xml
  18. 8 0
      app/src/main/res/drawable/shape_sign_green_bg.xml
  19. 8 0
      app/src/main/res/drawable/shape_sign_white_bg.xml
  20. 8 0
      app/src/main/res/drawable/shape_sign_yellow_bg.xml
  21. 8 0
      app/src/main/res/drawable/shape_white_10round.xml
  22. 2 0
      app/src/main/res/layout/activity_login.xml
  23. 41 19
      app/src/main/res/layout/activity_main.xml
  24. 212 29
      app/src/main/res/layout/item_contact.xml
  25. 65 0
      app/src/main/res/layout/popop_are_you_sure.xml
  26. 97 0
      app/src/main/res/layout/popop_sign_check_in_out.xml
  27. 11 0
      app/src/main/res/values/attrs.xml
  28. 2 0
      app/src/main/res/values/colors.xml
  29. 1 1
      buildSrc/src/main/kotlin/Dependencies.kt
  30. 2 2
      cs-baselib/build.gradle.kts
  31. 2 1
      cs-baselib/src/main/java/com/android/basiclib/base/activity/AbsActivity.kt
  32. 8 1
      cs-baselib/src/main/java/com/android/basiclib/engine/dialog/DialogExt.kt
  33. 65 0
      cs-baselib/src/main/java/com/android/basiclib/engine/picker/PickerExt.kt
  34. 5 5
      cs-baselib/src/main/java/com/android/basiclib/ext/DateTimeExt.kt
  35. 6 6
      cs-baselib/src/main/java/com/android/basiclib/view/gloading/GloadingGlobalStatusView.java
  36. 2 3
      cs-baselib/src/main/res/layout/view_gloading_global_status.xml
  37. BIN
      cs-baselib/src/main/res/mipmap-xhdpi/loading_error.webp
  38. BIN
      cs-baselib/src/main/res/mipmap-xhdpi/loading_no_data.webp

+ 99 - 3
app/src/main/java/vn/hongyegroup/yybusiness/adapter/AttendanceAdapter.java

@@ -1,13 +1,19 @@
 package vn.hongyegroup.yybusiness.adapter;
 
+import android.animation.LayoutTransition;
 import android.content.Context;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.ImageView;
 import android.widget.TextView;
 
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.android.basiclib.engine.image_load.ImageLoadExtKt;
+import com.android.basiclib.utils.CheckUtil;
+import com.android.basiclib.utils.CommUtils;
+
 import me.yokeyword.indexablerv.IndexableAdapter;
 import vn.hongyegroup.yybusiness.R;
 import vn.hongyegroup.yybusiness.entity.AttendanceBean;
@@ -15,9 +21,11 @@ import vn.hongyegroup.yybusiness.entity.AttendanceBean;
 public class AttendanceAdapter extends IndexableAdapter<AttendanceBean> {
 
     private LayoutInflater mInflater;
+    private LayoutTransition layoutTransition;
 
     public AttendanceAdapter(Context context) {
         mInflater = LayoutInflater.from(context);
+        layoutTransition = new LayoutTransition();
     }
 
     @Override
@@ -43,7 +51,67 @@ public class AttendanceAdapter extends IndexableAdapter<AttendanceBean> {
     public void onBindContentViewHolder(RecyclerView.ViewHolder holder, AttendanceBean entity) {
         ContentVH vh = (ContentVH) holder;
         vh.tvName.setText(entity.staff_name);
-        vh.tvMobile.setText(entity.job_date);
+        vh.tv_job_date.setText(entity.job_date);
+        vh.tv_start_time.setText(entity.start_time);
+        vh.tv_end_time.setText(entity.end_time);
+
+        // 设置 LayoutTransition
+        vh.ll_more_box.setLayoutTransition(layoutTransition);
+
+        if (!CheckUtil.isEmpty(entity.check_in_time) && !CheckUtil.isEmpty(entity.check_in_img)) {
+            vh.iv_in.setVisibility(View.VISIBLE);
+            vh.tv_in.setVisibility(View.GONE);
+            vh.tv_in_time.setVisibility(View.VISIBLE);
+            vh.tv_in_time.setText(entity.check_in_time);
+            ImageLoadExtKt.loadImage(vh.iv_in, entity.check_in_img,
+                    0, 0, false,
+                    false, 0, null,
+                    false, false);
+            vh.fl_sign_in_box.setBackground(CommUtils.getDrawable(R.drawable.shape_sign_white_bg));
+        } else {
+            vh.iv_in.setVisibility(View.GONE);
+            vh.tv_in.setVisibility(View.VISIBLE);
+            vh.tv_in_time.setVisibility(View.GONE);
+            vh.fl_sign_in_box.setBackground(CommUtils.getDrawable(R.drawable.shape_sign_blue_bg));
+        }
+
+        if (!CheckUtil.isEmpty(entity.check_out_time) && !CheckUtil.isEmpty(entity.check_out_time)) {
+            vh.iv_out.setVisibility(View.VISIBLE);
+            vh.tv_out.setVisibility(View.GONE);
+            vh.tv_out_time.setVisibility(View.VISIBLE);
+            vh.tv_out_time.setText(entity.check_out_time);
+            ImageLoadExtKt.loadImage(vh.iv_out, entity.check_out_img,
+                    0, 0, false,
+                    false, 0, null,
+                    false, false);
+            vh.fl_sign_out_box.setBackground(CommUtils.getDrawable(R.drawable.shape_sign_white_bg));
+        } else {
+            vh.iv_out.setVisibility(View.GONE);
+            vh.tv_out.setVisibility(View.VISIBLE);
+            vh.tv_out_time.setVisibility(View.GONE);
+            vh.fl_sign_out_box.setBackground(CommUtils.getDrawable(R.drawable.shape_sign_green_bg));
+        }
+
+        if (entity.isExpend) {
+            vh.iv_drop_down.setImageResource(R.drawable.home_up_icon);
+            vh.ll_more_box.setVisibility(View.VISIBLE);
+        } else {
+            vh.iv_drop_down.setImageResource(R.drawable.home_down_icon);
+            vh.ll_more_box.setVisibility(View.GONE);
+        }
+
+        vh.ll_name_box.setOnClickListener(v -> {
+            entity.isExpend = !entity.isExpend;
+            notifyDataSetChanged();
+        });
+
+        vh.fl_sign_in_box.setOnClickListener(v -> {
+            if (mListener != null) mListener.onCheckIn(entity);
+        });
+
+        vh.fl_sign_out_box.setOnClickListener(v -> {
+            if (mListener != null) mListener.onCheckOut(entity);
+        });
     }
 
 
@@ -57,12 +125,40 @@ public class AttendanceAdapter extends IndexableAdapter<AttendanceBean> {
     }
 
     private class ContentVH extends RecyclerView.ViewHolder {
-        TextView tvName, tvMobile;
+        TextView tvName, tv_job_date, tv_start_time, tv_end_time, tv_in, tv_out, tv_in_time, tv_out_time;
+        ImageView iv_in, iv_out, iv_drop_down;
+        ViewGroup fl_sign_in_box, fl_sign_out_box, ll_more_box, ll_name_box;
 
         public ContentVH(View itemView) {
             super(itemView);
             tvName = (TextView) itemView.findViewById(R.id.tv_name);
-            tvMobile = (TextView) itemView.findViewById(R.id.tv_mobile);
+            tv_job_date = (TextView) itemView.findViewById(R.id.tv_job_date);
+            tv_start_time = (TextView) itemView.findViewById(R.id.tv_start_time);
+            tv_end_time = (TextView) itemView.findViewById(R.id.tv_end_time);
+            tv_in = (TextView) itemView.findViewById(R.id.tv_in);
+            tv_out = (TextView) itemView.findViewById(R.id.tv_out);
+            tv_in_time = (TextView) itemView.findViewById(R.id.tv_in_time);
+            tv_out_time = (TextView) itemView.findViewById(R.id.tv_out_time);
+            iv_out = (ImageView) itemView.findViewById(R.id.iv_out);
+            iv_in = (ImageView) itemView.findViewById(R.id.iv_in);
+            iv_drop_down = (ImageView) itemView.findViewById(R.id.iv_drop_down);
+            fl_sign_in_box = itemView.findViewById(R.id.fl_sign_in_box);
+            fl_sign_out_box = itemView.findViewById(R.id.fl_sign_out_box);
+            ll_more_box = itemView.findViewById(R.id.ll_more_box);
+            ll_name_box = itemView.findViewById(R.id.ll_name_box);
         }
     }
+
+
+    private OnAttendanceListener mListener;
+
+    public void setOnAttendanceListener(OnAttendanceListener listener) {
+        mListener = listener;
+    }
+
+    public interface OnAttendanceListener {
+        void onCheckIn(AttendanceBean entity);
+
+        void onCheckOut(AttendanceBean entity);
+    }
 }

+ 4 - 1
app/src/main/java/vn/hongyegroup/yybusiness/entity/AttendanceBean.java

@@ -16,10 +16,13 @@ public class AttendanceBean implements IndexableEntity {
 
     public String check_out_id;   //签出数据
     public String check_out_time;
-    public String status_show;
+    public String check_out_img;
 
+    public String status_show;
     public int status;
 
+    public boolean isExpend = false;  //是否展开
+
     @Override
     public String getFieldIndexBy() {
         return staff_name;

+ 10 - 3
app/src/main/java/vn/hongyegroup/yybusiness/http/ApiService.kt

@@ -3,12 +3,17 @@ package vn.hongyegroup.yybusiness.http
 import com.android.basiclib.bean.BaseBean
 import com.android.basiclib.bean.PageInfo
 import com.android.cs_service.CommApi
+import okhttp3.MultipartBody
+import retrofit2.http.Field
 import retrofit2.http.FieldMap
 import retrofit2.http.FormUrlEncoded
 
 import retrofit2.http.GET
+import retrofit2.http.Multipart
 import retrofit2.http.POST
+import retrofit2.http.Part
 import retrofit2.http.Query
+import retrofit2.http.QueryMap
 import vn.hongyegroup.yybusiness.entity.AttendanceBean
 import vn.hongyegroup.yybusiness.entity.CheckInOut
 import vn.hongyegroup.yybusiness.entity.LoginBean
@@ -26,21 +31,23 @@ interface ApiService {
     /**
      * 登出
      */
+    @FormUrlEncoded
     @POST(CommApi.LOG_OUT)
-    suspend fun requestLogout(@Query("token") token: String): BaseBean<Boolean>
+    suspend fun requestLogout(@Field("token") token: String): BaseBean<Boolean>
 
 
     /**
      * 待签到列表
      */
     @GET(CommApi.APPLIED_LIST)
-    suspend fun fetchAppliedList(): BaseBean<PageInfo<AttendanceBean>>
+    suspend fun fetchAppliedList(@QueryMap params: Map<String, String>): BaseBean<PageInfo<AttendanceBean>>
 
 
     /**
      * 签到签出
      */
+    @Multipart
     @POST(CommApi.CHECK_IN_OUT)
-    suspend fun checkInOrOut(): BaseBean<CheckInOut>
+    suspend fun checkInOrOut(@Part parts: List<MultipartBody.Part>): BaseBean<CheckInOut>
 
 }

+ 68 - 0
app/src/main/java/vn/hongyegroup/yybusiness/mvvm/AppRepository.kt

@@ -1,12 +1,20 @@
 package vn.hongyegroup.yybusiness.mvvm
 
 import com.android.basiclib.base.vm.BaseRepository
+import com.android.basiclib.bean.BaseBean
 import com.android.basiclib.bean.OkResult
+import com.android.basiclib.bean.PageInfo
 import com.android.basiclib.engine.network.httpRequest
 import com.android.basiclib.utils.CheckUtil
 import com.android.basiclib.utils.log.MyLogUtils
+import okhttp3.MediaType
+import okhttp3.MultipartBody
+import okhttp3.RequestBody
+import vn.hongyegroup.yybusiness.entity.AttendanceBean
+import vn.hongyegroup.yybusiness.entity.CheckInOut
 import vn.hongyegroup.yybusiness.entity.LoginBean
 import vn.hongyegroup.yybusiness.http.AppRetrofit
+import java.io.File
 import javax.inject.Inject
 import javax.inject.Singleton
 
@@ -40,12 +48,72 @@ class AppRepository @Inject constructor() : BaseRepository() {
      */
     suspend inline fun requestLogout(token: String): OkResult<Boolean> {
 
+        MyLogUtils.w("请求参数,token:$token")
+
         return httpRequest {
             AppRetrofit.apiService.requestLogout(token)
         }
 
     }
 
+    /**
+     * 获取考勤成员列表
+     */
+    suspend inline fun fetchAppliedList(
+        token: String,
+        keyword: String?,
+        startDate: String,
+        endDate: String
+    ): OkResult<PageInfo<AttendanceBean>> {
+
+        val params = hashMapOf<String, String>()
+        params["token"] = token
+        params["cur_page"] = "1"
+        params["page_size"] = "9999"
+
+        if (!CheckUtil.isEmpty(keyword)) {
+            params["keyword"] = keyword!!
+        }
+        if (!CheckUtil.isEmpty(startDate)) {
+            params["start_date"] = startDate
+        }
+        if (!CheckUtil.isEmpty(endDate)) {
+            params["end_date"] = endDate
+        }
+
+        return httpRequest {
+            AppRetrofit.apiService.fetchAppliedList(params)
+        }
+
+    }
+
+    /**
+     * 签到签出
+     */
+    suspend inline fun requestCheckInOut(
+        token: String,
+        appliedId: String,
+        isCheckIn: Boolean,
+        path: String?
+    ): OkResult<CheckInOut> {
+
+        val builder = MultipartBody.Builder()
+        builder.setType(MultipartBody.FORM)
+        builder.addFormDataPart("token", token)
+        builder.addFormDataPart("applied_id", appliedId)
+        builder.addFormDataPart("check_type", if (isCheckIn) "1" else "2")
 
+        if (!CheckUtil.isEmpty(path)) {
+            val file = File(path!!)
+            val imageBody = RequestBody.create(MediaType.parse("multipart/form-data"), file)
+            builder.addFormDataPart("capture", file.name, imageBody)
+        }
+
+        val parts = builder.build().parts()
+
+        return httpRequest {
+            AppRetrofit.apiService.checkInOrOut(parts)
+        }
+    }
 
 }

+ 1 - 1
app/src/main/java/vn/hongyegroup/yybusiness/mvvm/LoginViewModel.kt

@@ -23,7 +23,7 @@ class LoginViewModel @Inject constructor(
 
 
     /**
-     * 执行登录接口
+     * 执行登录
      */
     fun doLogin(code: String, password: String): LiveData<Boolean> {
         val liveData = MutableLiveData<Boolean>()

+ 198 - 0
app/src/main/java/vn/hongyegroup/yybusiness/mvvm/MainViewModel.kt

@@ -0,0 +1,198 @@
+package vn.hongyegroup.yybusiness.mvvm
+
+import android.annotation.SuppressLint
+import androidx.fragment.app.FragmentActivity
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.SavedStateHandle
+import com.android.basiclib.base.vm.BaseViewModel
+import com.android.basiclib.bean.OkResult
+import com.android.basiclib.engine.picker.pickDate
+import com.android.basiclib.engine.preferences.EasyDataStore
+import com.android.basiclib.engine.toast.toast
+import com.android.basiclib.ext.timeStampFormat2Date
+import com.android.basiclib.utils.CheckUtil
+import com.android.cs_service.Constants
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import vn.hongyegroup.yybusiness.adapter.AttendanceAdapter
+import vn.hongyegroup.yybusiness.entity.AttendanceBean
+import vn.hongyegroup.yybusiness.entity.CheckInOut
+import java.util.Date
+import javax.inject.Inject
+
+
+@HiltViewModel
+class MainViewModel @Inject constructor(
+    private val repository: AppRepository,
+    val savedState: SavedStateHandle
+) : BaseViewModel() {
+
+    private var isNeedPlaceHolder = true
+    var mKeyword: String? = null
+    var mStartDateTimeStamp: Long = System.currentTimeMillis()
+    var mEndDateTimeStamp: Long = System.currentTimeMillis()
+    var mDatas = mutableListOf<AttendanceBean>()
+    lateinit var mAdapter: AttendanceAdapter
+
+    var pickTimeLD = MutableLiveData<Date>()
+
+    /**
+     * 获取考勤成员的列表数据
+     */
+    fun fetchAppliedList(): LiveData<Boolean> {
+        val liveData = MutableLiveData<Boolean>()
+
+        launchOnUI {
+
+            //获取Token
+            val token = withContext(Dispatchers.IO) {
+                EasyDataStore.get(Constants.KEY_TOKEN, "")
+            }
+
+            //开始Loading
+            if (isNeedPlaceHolder) loadStartLoading()
+
+            val listResult = repository.fetchAppliedList(
+                token,
+                mKeyword,
+                mStartDateTimeStamp.timeStampFormat2Date(),
+                mEndDateTimeStamp.timeStampFormat2Date()
+            )
+
+            if (listResult is OkResult.Success) {
+                //成功
+                loadSuccess()
+                handleData(listResult.data.list)
+                liveData.value = true
+
+            } else {
+                val message = (listResult as OkResult.Error).exception.message
+                loadError(message)
+                toast(message)
+
+                liveData.value = false
+            }
+
+        }
+
+        return liveData
+    }
+
+    //处理数据-添加或刷新
+    @SuppressLint("NotifyDataSetChanged")
+    private fun handleData(list: List<AttendanceBean>?) {
+        if (!CheckUtil.isEmpty(list)) {
+            //有数据
+            mDatas.clear()
+            mDatas.addAll(list!!)
+            mAdapter.setDatas(mDatas)
+
+        } else {
+            //展示无数据
+            mDatas.clear()
+            mAdapter.setDatas(mDatas)
+            loadNoData()
+        }
+
+        isNeedPlaceHolder = false
+    }
+
+
+    /**
+     * 执行退出登录
+     */
+    fun doLogout(): LiveData<Boolean> {
+        val liveData = MutableLiveData<Boolean>()
+        launchOnUI {
+
+            loadStartProgress()
+
+            //获取Token
+            val token = withContext(Dispatchers.IO) {
+                EasyDataStore.get(Constants.KEY_TOKEN, "")
+            }
+
+            val logoutResult = repository.requestLogout(token)
+
+            if (logoutResult is OkResult.Success) {
+                //成功
+                loadHideProgress()
+
+                //移除Token
+                EasyDataStore.remove<String>(Constants.KEY_TOKEN)
+
+                liveData.value = logoutResult.data
+
+            } else {
+                val message = (logoutResult as OkResult.Error).exception.message
+               loadHideProgress()
+                toast(message)
+
+                liveData.value = false
+            }
+
+        }
+        return liveData
+    }
+
+    /**
+     * 筛选时间
+     */
+    fun pickerDate(isPickStart: Boolean, activity: FragmentActivity) {
+        activity.pickDate(
+            selectedDateTimeStamp = if (isPickStart) mStartDateTimeStamp else mEndDateTimeStamp,
+            onConfirmAction = { date ->
+                if (isPickStart) {
+                    mStartDateTimeStamp = date.time
+                } else {
+                    mEndDateTimeStamp = date.time
+                }
+
+                pickTimeLD.value = date
+            })
+    }
+
+
+    /**
+     * 执行签到签出
+     */
+    fun requestCheckInOut(isCheckIn: Boolean, appliedId: String, path: String?): LiveData<CheckInOut> {
+        val liveData = MutableLiveData<CheckInOut>()
+
+        launchOnUI {
+
+            //获取Token
+            val token = withContext(Dispatchers.IO) {
+                EasyDataStore.get(Constants.KEY_TOKEN, "")
+            }
+
+            //开始Loading
+            loadStartProgress()
+
+            val listResult = repository.requestCheckInOut(
+                token,
+                appliedId,
+                isCheckIn,
+                path
+            )
+
+            if (listResult is OkResult.Success) {
+                //成功
+                loadHideProgress()
+                liveData.value = listResult.data
+
+            } else {
+                loadHideProgress()
+                val message = (listResult as OkResult.Error).exception.message
+                toast(message)
+            }
+
+        }
+
+        return liveData
+    }
+
+}
+

+ 1 - 0
app/src/main/java/vn/hongyegroup/yybusiness/ui/LoginActivity.kt

@@ -79,6 +79,7 @@ class LoginActivity : BaseVVDActivity<LoginViewModel, ActivityLoginBinding>() {
         }
     }
 
+    //去首页
     private fun gotoMainPage() {
         gotoActivity<MainActivity>()
         finish()

+ 209 - 30
app/src/main/java/vn/hongyegroup/yybusiness/ui/MainActivity.kt

@@ -2,29 +2,42 @@ package vn.hongyegroup.yybusiness.ui
 
 import android.os.Build
 import android.os.Bundle
+import android.view.View
 import android.view.WindowManager
+import android.view.inputmethod.EditorInfo
 import androidx.recyclerview.widget.LinearLayoutManager
 import com.android.basiclib.base.activity.BaseVVDActivity
-import com.android.basiclib.base.vm.EmptyViewModel
+import com.android.basiclib.engine.dialog.PopupType
+import com.android.basiclib.engine.dialog.showPopup
 import com.android.basiclib.engine.toast.toast
+import com.android.basiclib.ext.click
+import com.android.basiclib.ext.gotoActivity
+import com.android.basiclib.ext.timeStampFormat2Date
+import com.android.basiclib.utils.CheckUtil
 import com.android.basiclib.utils.CommUtils
+import com.android.basiclib.utils.KeyboardUtils
 import com.android.basiclib.utils.StatusBarUtils
 import com.android.basiclib.utils.log.MyLogUtils
+import com.android.basiclib.view.gloading.Gloading
 import dagger.hilt.android.AndroidEntryPoint
-import me.yokeyword.indexablerv.IndexableAdapter.OnItemContentClickListener
 import vn.hongyegroup.yybusiness.R
 import vn.hongyegroup.yybusiness.adapter.AttendanceAdapter
 import vn.hongyegroup.yybusiness.databinding.ActivityMainBinding
+import vn.hongyegroup.yybusiness.databinding.PopopAreYouSureBinding
+import vn.hongyegroup.yybusiness.databinding.PopopSignCheckInOutBinding
 import vn.hongyegroup.yybusiness.entity.AttendanceBean
+import vn.hongyegroup.yybusiness.mvvm.MainViewModel
+import vn.hongyegroup.yybusiness.widget.AreYouSureUtil
+import vn.hongyegroup.yybusiness.widget.ShowCheckInUtil
 
 
 /**
  * 应用的首页
  */
 @AndroidEntryPoint
-class MainActivity : BaseVVDActivity<EmptyViewModel, ActivityMainBinding>() {
+class MainActivity : BaseVVDActivity<MainViewModel, ActivityMainBinding>() {
 
-    private var mAdapter: AttendanceAdapter? = null
+    protected lateinit var mGLoadingHolder: Gloading.Holder
 
     override fun init(savedInstanceState: Bundle?) {
 
@@ -41,64 +54,230 @@ class MainActivity : BaseVVDActivity<EmptyViewModel, ActivityMainBinding>() {
         StatusBarUtils.setMargin(mContext, mBinding.statusBarView)
 
 
+        initLoadingView()
+        showSelectedDate(false)
         initRV()
+        initData()
         initListener()
     }
 
+    private fun initLoadingView() {
+        mGLoadingHolder = Gloading.getDefault().wrap(mBinding.flLoadBox).withRetry {
+            onGoadingRetry()
+        }
+    }
+
+    private fun onGoadingRetry() {
+        initData()
+    }
+
+    private fun initData() {
+        //请求接口
+        mViewModel.fetchAppliedList().observe(this) {
+//            mBinding.refreshLayout.isRefreshing = false
+            hideStateProgress()
+        }
+
+    }
+
     private fun initRV() {
         mBinding.indexableLayout.setLayoutManager(LinearLayoutManager(this))
         // 设置数据适配器
-        mAdapter = AttendanceAdapter(this)
-        mBinding.indexableLayout.setAdapter(mAdapter)
-        // 设置数据源
-        mAdapter?.setDatas(initDatas());
-        initDatas()
+        mViewModel.mAdapter = AttendanceAdapter(this)
+        mBinding.indexableLayout.setAdapter(mViewModel.mAdapter)
 
         // 设置右侧Index的滚动风格
         mBinding.indexableLayout.setOverlayStyle_MaterialDesign(CommUtils.getColor(R.color.button_box_bg))
         mBinding.indexableLayout.setStickyEnable(false)
 
         // 设置Item监听
-        mAdapter?.setOnItemContentClickListener { _, originalPosition, currentPosition, entity ->
-            if (originalPosition >= 0) {
-                toast("选中:" + entity.staff_name + "  当前位置:" + currentPosition + "  原始所在数组位置:" + originalPosition)
+        mViewModel.mAdapter.setOnAttendanceListener(object : AttendanceAdapter.OnAttendanceListener {
+            override fun onCheckIn(entity: AttendanceBean) {
+                showCheckPopup(true, entity)
+            }
+
+            override fun onCheckOut(entity: AttendanceBean) {
+                showCheckPopup(false, entity)
+            }
+        })
+
+//        mViewModel.mAdapter.setOnItemContentClickListener { view, originalPosition, currentPosition, entity ->
+//            if (originalPosition >= 0) {
+//                MyLogUtils.w("选中:" + entity.staff_name + "  当前位置:" + currentPosition + "  原始所在数组位置:" + originalPosition +" view id:${view.id}")
+//
+//                //点击切换
+//                when (view.id) {
+//                    R.id.fl_sign_in_box -> {
+//                        toast("点击签到")
+//                    }
+//
+//                    R.id.fl_sign_out_box -> {
+//                        toast("点击签出")
+//                    }
+//
+//                    R.id.ll_name_box -> {
+//
+//                    }
+//                }
+//
+//            } else {
+//                MyLogUtils.w("选中Header/Footer:" + entity.staff_name + "  当前位置:" + currentPosition)
+//            }
+//
+//        }
+//
+//        mViewModel.mAdapter.setOnItemTitleClickListener { _, currentPosition, indexTitle ->
+//            MyLogUtils.w("选中:$indexTitle  当前位置:$currentPosition")
+//        }
+
+    }
+
+    /**
+     * 展示签到签出的弹窗
+     */
+    private fun showCheckPopup(isCheckIn: Boolean, entity: AttendanceBean) {
+        ShowCheckInUtil.showPopup(mActivity) { path ->
+            if (!CheckUtil.isEmpty(path)) {
+                doCheckInOut(isCheckIn, entity, path)
+            }
+        }
+    }
+
+    //调用接口签到签出
+    private fun doCheckInOut(checkIn: Boolean, entity: AttendanceBean, path: String?) {
+        mViewModel.requestCheckInOut(checkIn, entity.applied_id, path).observe(this) {
+            if (it != null) {
+                if (checkIn) {
+                    entity.check_in_img = it.check_img
+                    entity.check_in_time = it.check_time
+                } else {
+                    entity.check_out_img = it.check_img
+                    entity.check_out_time = it.check_time
+                }
+
+                mViewModel.mAdapter.notifyDataSetChanged()
+            }
+        }
+    }
+
+    //其他的监听
+    private fun initListener() {
 
+//        mBinding.refreshLayout.setOnRefreshListener {
+//            onRefresh()
+//        }
+
+        //退出登录事件
+        mBinding.btnLogout.click {
+            AreYouSureUtil.showPopup(mActivity, "Are you sure you need to exit the system?") {
+                doLogout()
+            }
+        }
+
+        //筛选开始时间和结束时间
+        mBinding.tvPickStart.click {
+            mViewModel.pickerDate(true, mActivity)
+        }
+
+        mBinding.tvPickEnd.click {
+            mViewModel.pickerDate(false, mActivity)
+        }
+
+        //搜索相关
+        mBinding.etSearch.setOnEditorActionListener { v, actionId, event ->
+            if (actionId == EditorInfo.IME_ACTION_SEARCH) {
+                // 执行搜索操作
+                performSearch()
+                true
             } else {
-                toast("选中Header/Footer:" + entity.staff_name + "  当前位置:" + currentPosition)
+                false
+            }
+        }
+        mBinding.ivSearch.click {
+            performSearch()
+        }
+
+        //重置
+        mBinding.btnReset.click {
+
+            AreYouSureUtil.showPopup(mActivity, "Are you sure you want to reset the filtering options?") {
+                resetFilteringOption()
             }
 
         }
+    }
 
-        mAdapter!!.setOnItemTitleClickListener { _, currentPosition, indexTitle ->
-            toast("选中:$indexTitle  当前位置:$currentPosition")
+    override fun startObserve() {
+        super.startObserve()
+        mViewModel.pickTimeLD.observe(this) { date ->
+            if (date != null) {
+                showSelectedDate(true)
+            }
         }
+    }
 
+    //重置筛选选项
+    private fun resetFilteringOption() {
+        mViewModel.mKeyword = null
+        mBinding.etSearch.text = null
+        mViewModel.mStartDateTimeStamp = System.currentTimeMillis()
+        mViewModel.mEndDateTimeStamp = System.currentTimeMillis()
+        showSelectedDate(true)
+    }
+
+    //执行搜索
+    private fun performSearch() {
+        KeyboardUtils.hideSoftInput(this)
+        val query = mBinding.etSearch.text.toString()
+        MyLogUtils.w("performSearch query:$query")
+        if (!CheckUtil.isEmpty(query)) {
+            mViewModel.mKeyword = query
+            onRefresh()
+        }
 
     }
 
-    //提供RV的数据
-    private fun initDatas(): MutableList<AttendanceBean> {
-        val list = arrayListOf<AttendanceBean>()
-        // 初始化数据
-        val contactArray = getResources().getStringArray(R.array.contact_array)
-        val mobileArray = getResources().getStringArray(R.array.mobile_array)
+    //展示选中的日期
+    private fun showSelectedDate(needRefresh: Boolean) {
+        mBinding.tvPickStart.text = mViewModel.mStartDateTimeStamp.timeStampFormat2Date()
+        mBinding.tvPickEnd.text = mViewModel.mEndDateTimeStamp.timeStampFormat2Date()
+        if (needRefresh) onRefresh()
+    }
 
-        contactArray.forEachIndexed { index, name ->
-            val contactEntity = AttendanceBean().apply {
-                staff_name = name
-                job_date = mobileArray[index]
+    private fun onRefresh() {
+//        mBinding.refreshLayout.isRefreshing = true
+        showStateProgress()
+        initData()
+    }
+
+    //执行登出系统
+    private fun doLogout() {
+        mViewModel.doLogout().observe(this) {
+            if (it != null && it) {
+                gotoActivity<LoginActivity>()
+                finish()
             }
-            list.add(contactEntity)
         }
+    }
 
-        MyLogUtils.w("list数据量:${list.size}")
+    override fun showStateLoading() {
+        mGLoadingHolder.showLoading()
+        mBinding.indexableLayout.visibility = View.GONE
+    }
 
-        return list
+    override fun showStateSuccess() {
+        mGLoadingHolder.showLoadSuccess()
+        mBinding.indexableLayout.visibility = View.VISIBLE
     }
 
-    //其他的监听
-    private fun initListener() {
+    override fun showStateError(message: String?) {
+        mGLoadingHolder.showLoadFailed(message)
+        mBinding.indexableLayout.visibility = View.GONE
+    }
 
+    override fun showStateNoData() {
+        mGLoadingHolder.showEmpty()
+        mBinding.indexableLayout.visibility = View.GONE
     }
 
 }

+ 25 - 2
app/src/main/java/vn/hongyegroup/yybusiness/ui/SplashActivity.kt

@@ -4,15 +4,22 @@ import android.annotation.SuppressLint
 import android.os.Build
 import android.os.Bundle
 import android.view.WindowManager
+import androidx.lifecycle.lifecycleScope
 import com.android.basiclib.base.activity.BaseVVDActivity
 import com.android.basiclib.base.vm.EmptyViewModel
+import com.android.basiclib.engine.preferences.EasyDataStore
 import com.android.basiclib.ext.countDown
 import com.android.basiclib.ext.gotoActivity
+import com.android.basiclib.utils.CheckUtil
 import com.android.basiclib.utils.StatusBarUtils
 import com.android.basiclib.utils.log.MyLogUtils
+import com.android.cs_service.Constants
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.cancel
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 import vn.hongyegroup.yybusiness.databinding.ActivitySplashBinding
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -39,8 +46,8 @@ class SplashActivity : BaseVVDActivity<EmptyViewModel, ActivitySplashBinding>()
             },
             end = {
                 //去登录页面还是去首页
-                gotoActivity<LoginActivity>()
-                finish()
+                checkPage()
+
             },
             next = { time ->
                 MyLogUtils.w("倒计时:$time")
@@ -48,6 +55,22 @@ class SplashActivity : BaseVVDActivity<EmptyViewModel, ActivitySplashBinding>()
 
     }
 
+    private fun checkPage() {
+
+        lifecycleScope.launch {
+            val token = withContext(Dispatchers.IO) {
+                EasyDataStore.get(Constants.KEY_TOKEN, "")
+            }
+
+            if (!CheckUtil.isEmpty(token)) {
+                gotoActivity<MainActivity>()
+            } else {
+                gotoActivity<LoginActivity>()
+            }
+            finish()
+        }
+    }
+
     override fun onDestroy() {
         super.onDestroy()
         MyLogUtils.w("onDestroy")

+ 29 - 0
app/src/main/java/vn/hongyegroup/yybusiness/widget/AreYouSureUtil.kt

@@ -0,0 +1,29 @@
+package vn.hongyegroup.yybusiness.widget
+
+import androidx.fragment.app.FragmentActivity
+import com.android.basiclib.engine.dialog.PopupType
+import com.android.basiclib.engine.dialog.showPopup
+import com.android.basiclib.ext.click
+import vn.hongyegroup.yybusiness.databinding.PopopAreYouSureBinding
+
+object AreYouSureUtil {
+
+    fun showPopup(activity: FragmentActivity, title: String, onConfirmAction: () -> Unit) {
+        activity.showPopup(
+            popupType = PopupType.CENTER,
+            viewBinding = PopopAreYouSureBinding::inflate,
+            onCreateListener = { binding, control ->
+                binding.tvTitle.text = title
+                binding.btnNo.click {
+                    control.dismiss()
+                }
+                binding.btnYes.click {
+                    control.dismiss()
+                    onConfirmAction()
+                }
+            }
+        )
+
+    }
+
+}

+ 61 - 0
app/src/main/java/vn/hongyegroup/yybusiness/widget/ShowCheckInUtil.kt

@@ -0,0 +1,61 @@
+package vn.hongyegroup.yybusiness.widget
+
+import androidx.fragment.app.FragmentActivity
+import com.android.basiclib.engine.dialog.PopupType
+import com.android.basiclib.engine.dialog.showPopup
+import com.android.basiclib.engine.toast.toast
+import com.android.basiclib.ext.click
+import com.android.basiclib.utils.BitmapUtils
+import com.android.basiclib.utils.CommUtils
+import com.android.basiclib.utils.log.MyLogUtils
+import vn.hongyegroup.yybusiness.databinding.PopopSignCheckInOutBinding
+
+
+object ShowCheckInUtil {
+
+    fun showPopup(activity: FragmentActivity, onConfirmAction: (path: String?) -> Unit) {
+
+        activity.showPopup(
+            popupType = PopupType.CENTER,
+            enableDismiss = false,
+            viewBinding = PopopSignCheckInOutBinding::inflate,
+            onCreateListener = { binding, control ->
+                binding.btnClearPad.click {
+                    binding.handWrite.clear()
+                }
+                binding.btnNo.click {
+                    control.dismiss()
+                }
+                binding.btnYes.click {
+                    if (binding.handWrite.isSign()) {
+                        control.dismiss()
+                        val bitmap = binding.handWrite.bitmap
+
+                        if (bitmap != null) {
+                            val bitmapUtils: BitmapUtils = BitmapUtils.getInstance(CommUtils.getContext())
+                            val path: String = bitmapUtils.sdpath
+                            val fileName = System.currentTimeMillis().toString() + ".JPEG"
+                            val signaturePath = path + fileName
+                            if (!bitmapUtils.isFileExist("")) {
+                                bitmapUtils.createSDDir("")
+                            }
+                            MyLogUtils.w("签名保存过后的路径:$path")
+                            //由于获取的是png格式,必须调用此方法把透明底变成白色
+                            bitmapUtils.savePng2JpgBitmap(bitmap, fileName)
+
+                            //压缩签名图片再上传,哈夫曼算法压缩
+                            onConfirmAction.invoke(signaturePath)
+
+                        } else {
+                            onConfirmAction.invoke(null)
+                        }
+                    } else {
+                        toast("Please Sign First!")
+                    }
+                }
+            }
+        )
+
+    }
+
+}

+ 18 - 0
app/src/main/java/vn/hongyegroup/yybusiness/widget/signWrite/DrawPoint.java

@@ -0,0 +1,18 @@
+package vn.hongyegroup.yybusiness.widget.signWrite;
+
+/**
+ * Created by guado on 2018/3/5.
+ */
+
+public class DrawPoint {
+    public float x;
+    public float y;
+    public float width;
+
+    public DrawPoint set(float x, float y, float width) {
+        this.x = x;
+        this.y = y;
+        this.width = width;
+        return this;
+    }
+}

+ 311 - 0
app/src/main/java/vn/hongyegroup/yybusiness/widget/signWrite/HandWriteView.java

@@ -0,0 +1,311 @@
+package vn.hongyegroup.yybusiness.widget.signWrite;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Stack;
+
+import vn.hongyegroup.yybusiness.R;
+
+/**
+ * 签名View
+ */
+
+public class HandWriteView extends View {
+
+    List<TimedPoint> points = new ArrayList<>();
+    Stack<TimedPoint> cachePoints = new Stack<>();
+    PointUtil pointUtil = new PointUtil();
+    public Bitmap mBitmap;
+    private Canvas mCanvas;
+    private Paint mPaint;
+    public boolean isSign = false;
+
+    private int mBackColor = Color.TRANSPARENT;
+
+    public HandWriteView(Context context) {
+        this(context, null);
+    }
+
+    public HandWriteView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public HandWriteView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HandWriteView);
+        int maxWidth = a.getDimensionPixelSize(R.styleable.HandWriteView_paintMaxWidth, 16);
+        int minWidth = a.getDimensionPixelSize(R.styleable.HandWriteView_paintMinWidth, 8);
+        int paintColor = a.getColor(R.styleable.HandWriteView_paintColor, Color.BLACK);
+        pointUtil.setWidth(minWidth, maxWidth);
+        mPaint = new Paint();
+        mPaint.setColor(paintColor);
+        mPaint.setStrokeWidth(10);
+        mPaint.setAntiAlias(true);
+        mPaint.setStyle(Paint.Style.STROKE);
+        mPaint.setStrokeCap(Paint.Cap.ROUND);
+        mPaint.setStrokeJoin(Paint.Join.ROUND);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        float x = event.getX();
+        float y = event.getY();
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+                //在此会掉,开始了绘制
+                if (mListener != null) mListener.onBeginSign();
+                getParent().requestDisallowInterceptTouchEvent(true);
+                points.clear();
+                addPoint(getNewPoint(x, y));
+                break;
+            case MotionEvent.ACTION_MOVE:
+                addPoint(getNewPoint(x, y));
+                break;
+            case MotionEvent.ACTION_UP:
+                isSign = true;
+                addPoint(getNewPoint(x, y));
+                getParent().requestDisallowInterceptTouchEvent(false);
+                break;
+        }
+        invalidate();
+        return true;
+    }
+
+    private TimedPoint getNewPoint(float x, float y) {
+        if (cachePoints.empty()) {
+            return new TimedPoint(x, y);
+        } else return cachePoints.pop().set(x, y);
+    }
+
+    private void recyclePoint(TimedPoint point) {
+        cachePoints.push(point);
+    }
+
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        //画背景 如果有需要的话
+        super.onDraw(canvas);
+        if (mBitmap != null) {
+            canvas.drawBitmap(mBitmap, 0, 0, null);
+        }
+    }
+
+    private void addPoint(TimedPoint point) {
+        points.add(point);
+        if (points.size() > 3) {
+            ensureSignatureBitmap();
+            TimedPoint s0 = points.get(0);
+            TimedPoint s1 = points.get(1);
+            TimedPoint s2 = points.get(2);
+            TimedPoint s3 = points.get(3);
+            float cx1 = s1.x + (s2.x - s0.x) / 4;
+            float cy1 = s1.y + (s2.y - s0.y) / 4;
+            float cx2 = s2.x - (s3.x - s1.x) / 4;
+            float cy2 = s2.y - (s3.y - s1.y) / 4;
+            pointUtil.set(s1, getNewPoint(cx1, cy1), getNewPoint(cx2, cy2), s2);
+            float originalWidth = mPaint.getStrokeWidth();
+            float drawSteps = (float) Math.floor(pointUtil.length());
+            for (int i = 0; i < drawSteps; i++) {
+                float t = (float) i / drawSteps;
+                DrawPoint drawPoint = pointUtil.calculate(t);
+                mPaint.setStrokeWidth(drawPoint.width);
+                mCanvas.drawPoint(drawPoint.x, drawPoint.y, mPaint);
+            }
+            mPaint.setStrokeWidth(originalWidth);
+            recyclePoint(points.remove(0));
+            recyclePoint(pointUtil.control1);
+            recyclePoint(pointUtil.control2);
+        } else if (points.size() == 1) {
+            points.add(getNewPoint(point.x, point.y));
+        }
+    }
+
+    private void ensureSignatureBitmap() {
+        if (mBitmap == null) {
+            mBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
+            mCanvas = new Canvas(mBitmap);
+        }
+    }
+
+
+    /**
+     * 保存画板
+     *
+     * @param path 保存到路劲
+     */
+
+    public void save(String path) throws IOException {
+        save(path, false, 0, false);
+    }
+
+    public void save(String path, boolean isEncrypt) throws IOException {
+        save(path, false, 0, isEncrypt);
+    }
+
+    /**
+     * 保存画板
+     *
+     * @param path       保存到路径
+     * @param clearBlank 是否清楚空白区域
+     * @param blank      边缘空白区域
+     * @param isEncrypt  加密存储,选择加密存储会自动追加后缀为.sign
+     */
+    public void save(String path, boolean clearBlank, int blank, boolean isEncrypt) throws IOException {
+        Bitmap bitmap = mBitmap;
+        if (clearBlank) {
+            bitmap = clearBlank(bitmap, blank);
+        }
+        if (isEncrypt) path = path + ".sign";
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos);
+        byte[] buffer = bos.toByteArray();
+        if (buffer != null) {
+            File file = new File(path);
+            if (file.exists()) {
+                file.delete();
+            }
+            OutputStream outputStream = isEncrypt ? new SignFileOutputStream(file) : new FileOutputStream(file);
+            outputStream.write(buffer);
+            outputStream.close();
+        }
+    }
+
+    public Bitmap getBitmap() {
+        return mBitmap;
+    }
+
+    /**
+     * 逐行扫描 清除边界空白。
+     *
+     * @param bp
+     * @param blank 边距留多少个像素
+     * @return
+     */
+    private Bitmap clearBlank(Bitmap bp, int blank) {
+        int HEIGHT = bp.getHeight();
+        int WIDTH = bp.getWidth();
+        int top = 0, left = 0, right = WIDTH, bottom = HEIGHT;
+        int[] pixs = new int[WIDTH];
+        boolean isStop;
+        for (int y = 0; y < HEIGHT; y++) {
+            bp.getPixels(pixs, 0, WIDTH, 0, y, WIDTH, 1);
+            isStop = false;
+            for (int pix : pixs) {
+                if (pix != mBackColor) {
+                    top = y;
+                    isStop = true;
+                    break;
+                }
+            }
+            if (isStop) {
+                break;
+            }
+        }
+        for (int y = HEIGHT - 1; y >= 0; y--) {
+            bp.getPixels(pixs, 0, WIDTH, 0, y, WIDTH, 1);
+            isStop = false;
+            for (int pix : pixs) {
+                if (pix != mBackColor) {
+                    bottom = y;
+                    isStop = true;
+                    break;
+                }
+            }
+            if (isStop) {
+                break;
+            }
+        }
+
+        int scanHeight = bottom - top;
+        pixs = new int[scanHeight];
+        for (int x = 0; x < WIDTH; x++) {
+            bp.getPixels(pixs, 0, 1, x, top, 1, scanHeight);
+            isStop = false;
+            for (int pix : pixs) {
+                if (pix != mBackColor) {
+                    left = x;
+                    isStop = true;
+                    break;
+                }
+            }
+            if (isStop) {
+                break;
+            }
+        }
+        for (int x = WIDTH - 1; x > 0; x--) {
+            bp.getPixels(pixs, 0, 1, x, top, 1, scanHeight);
+            isStop = false;
+            for (int pix : pixs) {
+                if (pix != mBackColor) {
+                    right = x;
+                    isStop = true;
+                    break;
+                }
+            }
+            if (isStop) {
+                break;
+            }
+        }
+        if (blank < 0) {
+            blank = 0;
+        }
+        left = left - blank > 0 ? left - blank : 0;
+        top = top - blank > 0 ? top - blank : 0;
+        right = right + blank > WIDTH - 1 ? WIDTH - 1 : right + blank;
+        bottom = bottom + blank > HEIGHT - 1 ? HEIGHT - 1 : bottom + blank;
+        return Bitmap.createBitmap(bp, left, top, right - left, bottom - top);
+    }
+
+    public void setPaintColor(int paintColor) {
+        mPaint.setColor(paintColor);
+    }
+
+    public void setPaintWidth(int mMinWidth, int mMaxWidth) {
+        if (mMinWidth > 0 && mMaxWidth > 0 && mMinWidth <= mMaxWidth)
+            pointUtil.setWidth(mMinWidth, mMaxWidth);
+    }
+
+    public void clear() {
+        if (mBitmap != null && !mBitmap.isRecycled()) {
+            mBitmap.recycle();
+        }
+        mBitmap = null;
+        ensureSignatureBitmap();
+        invalidate();
+        isSign = false;
+    }
+
+    public boolean isSign() {
+        return isSign;
+    }
+
+
+    //================== callback
+
+    private SignListener mListener;
+
+    public void SetSignListener(SignListener listener) {
+        mListener = listener;
+    }
+
+    public interface SignListener {
+        void onBeginSign();
+    }
+}

+ 114 - 0
app/src/main/java/vn/hongyegroup/yybusiness/widget/signWrite/PointUtil.java

@@ -0,0 +1,114 @@
+package vn.hongyegroup.yybusiness.widget.signWrite;
+
+/**
+ * Created by guado on 2018/3/5.
+ */
+
+public class PointUtil {
+
+    public TimedPoint startPoint;
+    public TimedPoint control1;
+    public TimedPoint control2;
+    public TimedPoint endPoint;
+    private int mMinWidth = 8;
+    private int mMaxWidth = 16;
+    private float mVelocityFilterWeight = 0.9f;
+    private float mLastVelocity;
+    private float mLastWidth;
+    private float mStartWidth;
+    private float widthDelta;
+    private DrawPoint drawPoint = new DrawPoint();
+
+    public PointUtil() {
+    }
+
+    public void setWidth(int mMinWidth, int mMaxWidth) {
+        this.mMinWidth = mMinWidth;
+        this.mMaxWidth = mMaxWidth;
+    }
+
+    public PointUtil set(TimedPoint startPoint, TimedPoint control1,
+                         TimedPoint control2, TimedPoint endPoint) {
+        this.startPoint = startPoint;
+        this.control1 = control1;
+        this.control2 = control2;
+        this.endPoint = endPoint;
+
+        float velocity = startPoint.velocityTo(endPoint);
+        velocity = Float.isNaN(velocity) ? 0.0f : velocity;
+
+        velocity = mVelocityFilterWeight * velocity
+                + (1 - mVelocityFilterWeight) * mLastVelocity;
+        float newWidth = mMinWidth + (mMaxWidth - mMinWidth) / (Math.max(1, velocity));
+        mLastVelocity = velocity;
+        widthDelta = newWidth - mLastWidth;
+        mStartWidth = mLastWidth;
+        mLastWidth = newWidth;
+        return this;
+    }
+
+    /**
+     * 获得贝塞尔曲线的长度
+     *
+     * @return
+     */
+    public float length() {
+        int steps = 10;
+        float length = 0;
+        double cx, cy, px = 0, py = 0, xDiff, yDiff;
+        for (int i = 0; i <= steps; i++) {
+            float t = (float) i / steps;
+            cx = point(t, this.startPoint.x, this.control1.x,
+                    this.control2.x, this.endPoint.x);
+            cy = point(t, this.startPoint.y, this.control1.y,
+                    this.control2.y, this.endPoint.y);
+            if (i > 0) {
+                xDiff = cx - px;
+                yDiff = cy - py;
+                length += Math.sqrt(xDiff * xDiff + yDiff * yDiff);
+            }
+            px = cx;
+            py = cy;
+        }
+        return length;
+
+    }
+
+    /**
+     * 求分段的贝塞尔曲线长度。
+     * //P(t)=p1(small_left-t)^3+3p2(small_left-t)^2t+3p3(small_left-t)t^small_right+p4t^3;
+     *
+     * @param t
+     * @param start
+     * @param c1
+     * @param c2
+     * @param end
+     * @return
+     */
+
+    public double point(float t, float start, float c1, float c2, float end) {
+        return start * (1.0 - t) * (1.0 - t) * (1.0 - t)
+                + 3.0 * c1 * (1.0 - t) * (1.0 - t) * t
+                + 3.0 * c2 * (1.0 - t) * t * t
+                + end * t * t * t;
+    }
+
+    public DrawPoint calculate(float t) {
+        float tt = t * t;
+        float ttt = tt * t;
+        float u = 1 - t;
+        float uu = u * u;
+        float uuu = uu * u;
+
+        float x = uuu * this.startPoint.x;
+        x += 3 * uu * t * this.control1.x;
+        x += 3 * u * tt * this.control2.x;
+        x += ttt * this.endPoint.x;
+
+        float y = uuu * this.startPoint.y;
+        y += 3 * uu * t * this.control1.y;
+        y += 3 * u * tt * this.control2.y;
+        y += ttt * this.endPoint.y;
+        return drawPoint.set(x, y, mStartWidth + ttt * widthDelta);
+    }
+}

+ 44 - 0
app/src/main/java/vn/hongyegroup/yybusiness/widget/signWrite/SignFileOutputStream.java

@@ -0,0 +1,44 @@
+package vn.hongyegroup.yybusiness.widget.signWrite;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Random;
+
+/**
+ * Created by guado on 2018/3/5.
+ */
+
+public class SignFileOutputStream extends FileOutputStream {
+    byte[] BYTE_MAP;
+
+    public SignFileOutputStream(String name) throws IOException {
+        this(new File(name));
+    }
+
+    public SignFileOutputStream(File file) throws IOException {
+        super(file);
+        BYTE_MAP = new byte[256];
+        for (int i = 0; i < 256; i++) {
+            BYTE_MAP[i] = (byte) i;
+        }
+        Random random = new Random();
+        for (int i = 0; i < BYTE_MAP.length; i++) {
+            int p = random.nextInt(256);
+            byte b = BYTE_MAP[i];
+            BYTE_MAP[i] = BYTE_MAP[p];
+            BYTE_MAP[p] = b;
+        }
+        //write magic num
+        super.write(new byte[]{0x1A, 0x2A}, 0, 2);
+        super.write(BYTE_MAP, 0, 256);
+    }
+
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException {
+        for (int i = off; i < off + len; i++) {
+            b[i] = BYTE_MAP[b[i] & 0xFF];
+        }
+        super.write(b, off, len);
+    }
+}

+ 34 - 0
app/src/main/java/vn/hongyegroup/yybusiness/widget/signWrite/TimedPoint.java

@@ -0,0 +1,34 @@
+package vn.hongyegroup.yybusiness.widget.signWrite;
+
+/**
+ * Created by guado on 2018/3/5.
+ */
+
+public class TimedPoint {
+    public float x;
+    public float y;
+    public long timestamp;
+
+    public TimedPoint(float x, float y) {
+        this.x = x;
+        this.y = y;
+        this.timestamp = System.currentTimeMillis();
+    }
+
+    public TimedPoint set(float x, float y) {
+        this.x = x;
+        this.y = y;
+        this.timestamp = System.currentTimeMillis();
+        return this;
+    }
+
+    public float distanceTo(TimedPoint point) {
+        return (float) Math.sqrt(Math.pow(point.x - x, 2) + Math.pow(point.y - y, 2));
+    }
+
+    public float velocityTo(TimedPoint point) {
+        long t = point.timestamp - timestamp;
+        if (t == 0) return 0;
+        else return distanceTo(point) / t;
+    }
+}

+ 8 - 0
app/src/main/res/drawable/shape_sign_blue_bg.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <corners android:radius="17.5dp"/>
+
+    <solid android:color="#56AAFF"/>
+
+</shape>

+ 8 - 0
app/src/main/res/drawable/shape_sign_green_bg.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <corners android:radius="17.5dp"/>
+
+    <solid android:color="#0AC074"/>
+
+</shape>

+ 8 - 0
app/src/main/res/drawable/shape_sign_white_bg.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <corners android:radius="17.5dp"/>
+
+    <solid android:color="@color/white"/>
+
+</shape>

+ 8 - 0
app/src/main/res/drawable/shape_sign_yellow_bg.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <corners android:radius="10.5dp"/>
+
+    <solid android:color="#FFBB1B"/>
+
+</shape>

+ 8 - 0
app/src/main/res/drawable/shape_white_10round.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <corners android:radius="@dimen/d_10dp"/>
+
+    <solid android:color="@color/white"/>
+
+</shape>

+ 2 - 0
app/src/main/res/layout/activity_login.xml

@@ -46,6 +46,7 @@
             android:hint="@string/please_enter_your_login_code"
             android:paddingTop="@dimen/d_3dp"
             android:paddingBottom="@dimen/d_3dp"
+            android:singleLine="true"
             android:layout_marginTop="@dimen/d_5dp"
             android:layout_marginBottom="@dimen/d_5dp"
             android:textColor="@color/white"
@@ -84,6 +85,7 @@
                 android:fontFamily="sans-serif-medium"
                 android:hint="@string/please_enter_your_password"
                 android:paddingTop="@dimen/d_8dp"
+                android:singleLine="true"
                 android:paddingBottom="@dimen/d_8dp"
                 android:inputType="textPassword"
                 android:textColor="@color/white"

+ 41 - 19
app/src/main/res/layout/activity_main.xml

@@ -3,9 +3,10 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:focusableInTouchMode="true"
-    android:focusable="true"
+    xmlns:tools="http://schemas.android.com/tools"
     android:background="@drawable/shape_page_bg"
+    android:focusable="true"
+    android:focusableInTouchMode="true"
     android:orientation="vertical">
 
     <View
@@ -35,24 +36,28 @@
 
 
             <EditText
+                android:id="@+id/et_search"
                 android:layout_width="0dp"
                 android:layout_height="wrap_content"
                 android:layout_marginLeft="@dimen/d_14dp"
                 android:layout_marginRight="@dimen/d_14dp"
                 android:layout_weight="1"
+                android:singleLine="true"
                 android:background="@color/transparent"
                 android:hint="@string/name_or_mobile"
                 android:textColor="@color/white"
+                android:imeOptions="actionSearch"
                 android:textColorHint="@color/hint_text"
                 android:textSize="@dimen/d_15sp"
                 android:textStyle="normal" />
 
 
             <ImageView
-                android:padding="@dimen/d_5dp"
+                android:id="@+id/iv_search"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_marginRight="@dimen/d_9dp"
+                android:padding="@dimen/d_5dp"
                 android:src="@drawable/home_search_icon" />
 
         </LinearLayout>
@@ -105,13 +110,14 @@
         android:orientation="horizontal">
 
         <TextView
+            android:id="@+id/tv_pick_start"
             android:layout_width="0dp"
-            android:layout_height="wrap_content"
+            android:layout_height="match_parent"
             android:layout_weight="1"
             android:gravity="center"
-            android:text="2024-6-27"
-            android:textSize="@dimen/d_16sp"
-            android:textColor="@color/white" />
+            tools:text="2024-6-27"
+            android:textColor="@color/white"
+            android:textSize="@dimen/d_16sp" />
 
         <View
             android:layout_width="@dimen/d_0.5dp"
@@ -121,23 +127,39 @@
             android:background="#52739C" />
 
         <TextView
+            android:id="@+id/tv_pick_end"
             android:layout_width="0dp"
-            android:layout_height="wrap_content"
+            android:layout_height="match_parent"
             android:layout_weight="1"
             android:gravity="center"
-            android:textSize="@dimen/d_16sp"
-            android:text="2024-6-27"
-            android:textColor="@color/white" />
+            tools:text="2024-6-27"
+            android:textColor="@color/white"
+            android:textSize="@dimen/d_16sp" />
 
     </LinearLayout>
 
-    <me.yokeyword.indexablerv.IndexableLayout
-        android:id="@+id/indexableLayout"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        app:indexBar_selectedTextColor="@color/selected_text"
-        app:indexBar_textColor="@color/white"
-        app:indexBar_textSize="15sp"
-        app:indexBar_textSpace="6dp" />
+<!--    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout-->
+<!--        android:id="@+id/refresh_layout"-->
+<!--        android:layout_width="match_parent"-->
+<!--        android:layout_height="match_parent"-->
+<!--        android:focusable="true">-->
+
+        <FrameLayout
+            android:id="@+id/fl_load_box"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <me.yokeyword.indexablerv.IndexableLayout
+                android:id="@+id/indexableLayout"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                app:indexBar_selectedTextColor="@color/selected_text"
+                app:indexBar_textColor="@color/white"
+                app:indexBar_textSize="15sp"
+                app:indexBar_textSpace="6dp" />
+
+        </FrameLayout>
+
+<!--    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>-->
 
 </LinearLayout>

+ 212 - 29
app/src/main/res/layout/item_contact.xml

@@ -1,42 +1,225 @@
 <?xml version="1.0" encoding="utf-8"?>
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:paddingBottom="16dp"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_marginLeft="@dimen/d_15dp"
     android:layout_marginRight="@dimen/d_30dp"
     android:layout_marginBottom="@dimen/d_10dp"
     android:background="@drawable/shape_input_bg_round"
-    android:paddingLeft="16dp"
-    android:paddingRight="16dp"
-    android:paddingTop="8dp">
-
-    <ImageView
-        android:id="@+id/img_avatar"
-        android:layout_width="wrap_content"
-        android:layout_marginTop="@dimen/d_7dp"
-        android:layout_height="wrap_content"
-        android:layout_marginRight="16dp"
-        android:src="@drawable/home_export_icon" />
+    android:orientation="vertical"
+    android:paddingLeft="@dimen/d_23dp"
+    android:paddingRight="@dimen/d_13dp">
+
 
-    <TextView
-        android:id="@+id/tv_name"
+    <LinearLayout
+        android:id="@+id/ll_name_box"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_toRightOf="@id/img_avatar"
-        tools:text="张三"
-        android:textColor="@color/white"
-        android:textSize="16sp" />
-
-    <TextView
-        android:id="@+id/tv_mobile"
-        android:layout_width="wrap_content"
+        android:gravity="center_vertical"
+        android:orientation="horizontal"
+        android:paddingTop="@dimen/d_18dp"
+        android:paddingBottom="@dimen/d_18dp">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Name:"
+            android:textColor="@color/hint_text"
+            android:textSize="14sp" />
+
+        <TextView
+            android:id="@+id/tv_name"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="@dimen/d_5dp"
+            android:layout_weight="1"
+            android:textColor="@color/white"
+            android:textSize="14sp"
+            tools:text="张三" />
+
+        <ImageView
+            android:id="@+id/iv_drop_down"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/home_down_icon" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/ll_more_box"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_below="@id/tv_name"
-        android:layout_marginTop="12dp"
-        android:layout_toRightOf="@id/img_avatar"
-        tools:text="18712345678"
-        android:textColor="@color/white" />
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Job Date:"
+                android:textColor="@color/hint_text"
+                android:textSize="14sp" />
+
+            <TextView
+                android:id="@+id/tv_job_date"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/d_5dp"
+                android:layout_weight="1"
+                android:textColor="@color/white"
+                android:textSize="14sp"
+                tools:text="2024-06-27" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/d_16dp"
+            android:orientation="horizontal">
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Start Time:"
+                android:textColor="@color/hint_text"
+                android:textSize="14sp" />
+
+            <TextView
+                android:id="@+id/tv_start_time"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/d_5dp"
+                android:layout_weight="1"
+                android:textColor="@color/white"
+                android:textSize="14sp"
+                tools:text="09:00" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/d_16dp"
+            android:orientation="horizontal">
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="End Time:"
+                android:textColor="@color/hint_text"
+                android:textSize="14sp" />
+
+            <TextView
+                android:id="@+id/tv_end_time"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/d_5dp"
+                android:layout_weight="1"
+                android:textColor="@color/white"
+                android:textSize="14sp"
+                tools:text="19:00" />
+
+        </LinearLayout>
+
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/d_18dp"
+            android:orientation="horizontal">
+
+
+            <FrameLayout
+                app:layout_constraintLeft_toLeftOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                android:id="@+id/fl_sign_in_box"
+                android:layout_width="@dimen/d_90dp"
+                android:layout_height="@dimen/d_35dp"
+                android:background="@drawable/shape_sign_blue_bg">
+
+                <ImageView
+                    android:id="@+id/iv_in"
+                    android:layout_gravity="center"
+                    android:layout_width="71.5dp"
+                    android:layout_height="25dp"/>
+
+                <TextView
+                    android:id="@+id/tv_in"
+                    android:text="Check In"
+                    android:textColor="@color/white"
+                    android:layout_gravity="center"
+                    android:fontFamily="sans-serif-medium"
+                    android:textSize="@dimen/d_14sp"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"/>
+
+            </FrameLayout>
+
+            <FrameLayout
+                app:layout_constraintLeft_toRightOf="@id/fl_sign_in_box"
+                app:layout_constraintTop_toTopOf="parent"
+                android:id="@+id/fl_sign_out_box"
+                android:layout_width="@dimen/d_90dp"
+                android:layout_height="@dimen/d_35dp"
+                android:layout_marginLeft="@dimen/d_10dp"
+                android:background="@drawable/shape_sign_green_bg">
+
+                <ImageView
+                    android:id="@+id/iv_out"
+                    android:layout_gravity="center"
+                    android:layout_width="71.5dp"
+                    android:layout_height="25dp"/>
+
+                <TextView
+                    android:id="@+id/tv_out"
+                    android:text="Check Out"
+                    android:textColor="@color/white"
+                    android:layout_gravity="center"
+                    android:fontFamily="sans-serif-medium"
+                    android:textSize="@dimen/d_14sp"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"/>
+
+            </FrameLayout>
+
+            <TextView
+                android:id="@+id/tv_in_time"
+                android:layout_marginTop="@dimen/d_8dp"
+                app:layout_constraintLeft_toLeftOf="@id/fl_sign_in_box"
+                app:layout_constraintTop_toBottomOf="@id/fl_sign_in_box"
+                app:layout_constraintRight_toRightOf="@id/fl_sign_in_box"
+                android:textSize="@dimen/d_14sp"
+                android:textColor="@color/white"
+                tools:text="08:50"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"/>
+
+            <TextView
+                android:id="@+id/tv_out_time"
+                android:layout_marginTop="@dimen/d_8dp"
+                app:layout_constraintLeft_toLeftOf="@id/fl_sign_out_box"
+                app:layout_constraintTop_toBottomOf="@id/fl_sign_out_box"
+                app:layout_constraintRight_toRightOf="@id/fl_sign_out_box"
+                android:textSize="@dimen/d_14sp"
+                android:textColor="@color/white"
+                tools:text="08:50"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"/>
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+
+        <View
+            android:layout_width="1dp"
+            android:layout_height="20dp"/>
+
+    </LinearLayout>
+
 
-</RelativeLayout>
+</LinearLayout>

+ 65 - 0
app/src/main/res/layout/popop_are_you_sure.xml

@@ -0,0 +1,65 @@
+<?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:layout_width="280dp"
+    android:layout_height="wrap_content"
+    android:background="@drawable/shape_white_10round"
+    android:orientation="vertical">
+
+
+    <TextView
+        android:id="@+id/tv_title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:fontFamily="sans-serif-medium"
+        android:gravity="center"
+        android:minHeight="125dp"
+        android:padding="@dimen/d_20dp"
+        android:textColor="@color/black"
+        android:textSize="@dimen/d_15sp"
+        tools:text="Are you sure you need to exit the system?" />
+
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/d_0.5dp"
+        android:background="@color/dialog_divider" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/d_47dp"
+        android:orientation="horizontal">
+
+
+        <TextView
+            android:id="@+id/btn_no"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:background="@drawable/ios_dialog_button_no"
+            android:gravity="center"
+            android:text="No"
+            android:textColor="@color/text_blue"
+            android:textSize="@dimen/d_17sp" />
+
+
+        <View
+            android:layout_width="@dimen/d_0.5dp"
+            android:layout_height="match_parent"
+            android:background="@color/dialog_divider" />
+
+
+        <TextView
+            android:id="@+id/btn_yes"
+            android:layout_width="0dp"
+            android:background="@drawable/ios_dialog_button_yes"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:gravity="center"
+            android:text="Yes"
+            android:textColor="@color/text_blue"
+            android:textSize="@dimen/d_17sp" />
+
+    </LinearLayout>
+
+</LinearLayout>

+ 97 - 0
app/src/main/res/layout/popop_sign_check_in_out.xml

@@ -0,0 +1,97 @@
+<?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:layout_width="280dp"
+    android:layout_height="wrap_content"
+    android:background="@drawable/shape_white_10round"
+    android:orientation="vertical">
+
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
+        android:layout_marginTop="@dimen/d_16dp"
+        android:layout_marginBottom="@dimen/d_14dp"
+        android:fontFamily="sans-serif-medium"
+        android:gravity="center"
+        android:text="Sign Here"
+        android:textColor="@color/black"
+        android:textSize="@dimen/d_19sp" />
+
+
+    <FrameLayout
+        android:layout_width="240dp"
+        android:layout_height="@dimen/d_200dp"
+        android:layout_gravity="center_horizontal">
+
+        <vn.hongyegroup.yybusiness.widget.signWrite.HandWriteView
+            android:id="@+id/hand_write"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:background="#F7F7F7"
+            app:paintMaxWidth="3dp" />
+
+        <TextView
+            android:id="@+id/btn_clear_pad"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="left|bottom"
+            android:layout_marginLeft="@dimen/d_10dp"
+            android:layout_marginBottom="10dp"
+            android:background="@drawable/shape_sign_yellow_bg"
+            android:paddingLeft="12dp"
+            android:paddingTop="3dp"
+            android:paddingRight="12dp"
+            android:paddingBottom="4dp"
+            android:text="Clean"
+            android:textColor="@color/white"
+            android:textSize="12sp" />
+
+    </FrameLayout>
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/d_0.5dp"
+        android:layout_marginTop="@dimen/d_20dp"
+        android:background="@color/dialog_divider" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/d_47dp"
+        android:orientation="horizontal">
+
+
+        <TextView
+            android:id="@+id/btn_no"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:background="@drawable/ios_dialog_button_no"
+            android:gravity="center"
+            android:text="Cancel"
+            android:textColor="@color/text_blue"
+            android:textSize="@dimen/d_17sp" />
+
+
+        <View
+            android:layout_width="@dimen/d_0.5dp"
+            android:layout_height="match_parent"
+            android:background="@color/dialog_divider" />
+
+
+        <TextView
+            android:id="@+id/btn_yes"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:background="@drawable/ios_dialog_button_yes"
+            android:gravity="center"
+            android:text="Submit"
+            android:textColor="@color/text_blue"
+            android:textSize="@dimen/d_17sp" />
+
+    </LinearLayout>
+
+</LinearLayout>

+ 11 - 0
app/src/main/res/values/attrs.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <!--  手写板  -->
+    <declare-styleable name="HandWriteView">
+        <attr name="paintColor" format="color" />
+        <attr name="paintMaxWidth" format="dimension" />
+        <attr name="paintMinWidth" format="dimension" />
+    </declare-styleable>
+
+</resources>

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

@@ -7,4 +7,6 @@
     <color name="button_box_bg">#802BA9F9</color>
     <color name="selected_text">#2BA9F9</color>
     <color name="hint_text">#AECAE5</color>
+    <color name="text_blue">#0085C4</color>
+    <color name="dialog_divider">#2109141F</color>
 </resources>

+ 1 - 1
buildSrc/src/main/kotlin/Dependencies.kt

@@ -1,4 +1,4 @@
-import org.gradle.api.artifacts.dsl.DependencyHandler
+ import org.gradle.api.artifacts.dsl.DependencyHandler
 
 /**
  *  @author Newki

+ 2 - 2
cs-baselib/build.gradle.kts

@@ -20,8 +20,8 @@ dependencies {
     implementation(VersionThirdPart.xxPermission)
 
     //不限制范围,全局使用
-    refresh()
-    work()
+//    refresh()
+//    work()
 
     api(VersionThirdPart.liveEventBus)
 }

+ 2 - 1
cs-baselib/src/main/java/com/android/basiclib/base/activity/AbsActivity.kt

@@ -8,6 +8,7 @@ import android.content.res.Resources
 import android.graphics.Color
 import android.os.Bundle
 import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.FragmentActivity
 import com.android.basiclib.receiver.ConnectivityReceiver
 import com.android.basiclib.utils.ActivityManage
 import com.android.basiclib.utils.StatusBarUtils
@@ -21,7 +22,7 @@ abstract class AbsActivity : AppCompatActivity(), ConnectivityReceiver.Connectiv
     /**
      * 获取Context对象
      */
-    protected lateinit var mActivity: Activity
+    protected lateinit var mActivity: FragmentActivity
     protected lateinit var mContext: Context
 
 

+ 8 - 1
cs-baselib/src/main/java/com/android/basiclib/engine/dialog/DialogExt.kt

@@ -23,13 +23,15 @@ fun <VB : ViewBinding> FragmentActivity.showPopup(
     maxHeight: Int = 0,
     offsetX: Int = 0,   //弹框在 X,Y 方向的偏移值
     offsetY: Int = 0,
+    enableDismiss: Boolean = true,
     isCenterHorizontal: Boolean = false,  //布局是否水平居中
     onCreateListener: ((VB, IPopupController) -> Unit)? = null,   //创建的回调
     onDismissListener: (() -> Unit)? = null,     //消失的回调
 ) {
 
     //根据类型转换对应的PopupView继承
-    val creator = PopupViewCreatorFactory.getCreator<VB>(popupType, this, width, height, maxWidth, maxHeight, onCreateListener, onDismissListener)
+    val creator =
+        PopupViewCreatorFactory.getCreator<VB>(popupType, this, width, height, maxWidth, maxHeight, onCreateListener, onDismissListener)
     val popupView = creator.create(viewBinding)
 
     val builder = XPopup.Builder(this)
@@ -48,6 +50,11 @@ fun <VB : ViewBinding> FragmentActivity.showPopup(
             .moveUpToKeyboard(true)
     }
 
+    if (!enableDismiss) {
+        builder.dismissOnBackPressed(false)
+            .dismissOnTouchOutside(false)
+    }
+
     //转换动画效果,是否需要添加动画
     if (transformAnimationType(popupAnimationType) != null) {
         builder.popupAnimation(transformAnimationType(popupAnimationType))

+ 65 - 0
cs-baselib/src/main/java/com/android/basiclib/engine/picker/PickerExt.kt

@@ -0,0 +1,65 @@
+package com.android.basiclib.engine.picker
+
+import android.view.View
+import androidx.fragment.app.FragmentActivity
+import com.lxj.xpopup.XPopup
+import com.lxj.xpopupext.listener.TimePickerListener
+import com.lxj.xpopupext.popup.TimePickerPopup
+import java.util.Calendar
+import java.util.Date
+
+
+/**
+ * 展示时间的筛选Picker
+ */
+fun FragmentActivity.pickDate(
+    selectedDateTimeStamp: Long? = null,
+    startRange: Int = -10, //时间筛选范围,前10年
+    endRange: Int = 10,  //时间筛选范围,后10年
+    onChangeAction: ((date: Date) -> Unit)? = null,  //改变
+    onConfirmAction: ((date: Date) -> Unit)? = null,  //确认
+    onCancelAction: (() -> Unit)? = null,     //取消回调
+) {
+
+    val popup = TimePickerPopup(this)
+        .setItemTextSize(16)
+        .setShowLabel(true)
+        .setCyclic(false)
+
+
+    //设置默认选中日期
+    if (selectedDateTimeStamp != null) {
+        popup.setDefaultDate(Calendar.getInstance().apply {
+            timeInMillis = selectedDateTimeStamp
+        })
+    }
+
+    //设置时间筛选的范围
+    popup.setDateRange(Calendar.getInstance().apply {
+        add(Calendar.YEAR, startRange)
+    }, Calendar.getInstance().apply {
+        add(Calendar.YEAR, endRange)
+    })
+
+    popup.setTimePickerListener(object : TimePickerListener {
+        override fun onTimeChanged(date: Date) {
+            //时间改变
+            onChangeAction?.invoke(date)
+        }
+
+        override fun onTimeConfirm(date: Date, view: View?) {
+            //点击确认时间
+            onConfirmAction?.invoke(date)
+        }
+
+        override fun onCancel() {
+            //取消
+            onCancelAction?.invoke()
+        }
+    })
+
+    XPopup.Builder(this)
+        .asCustom(popup)
+        .show()
+
+}

+ 5 - 5
cs-baselib/src/main/java/com/android/basiclib/ext/DateTimeExt.kt

@@ -15,7 +15,7 @@ import java.util.*
  *  @param format 时间的格式,默认是按照yyyy-MM-dd HH:mm:ss来转换,如果格式不一样,则需要传入对应的格式
  */
 @SuppressLint("SimpleDateFormat")
-fun String.dateFormat2TimeStamp(format: String = "yyyy-MM-dd HH:mm:ss"): Long {
+fun String.dateFormat2TimeStamp(format: String = "yyyy-MM-dd"): Long {
 
     return try {
         SimpleDateFormat(format).parse(this)?.time ?: 0
@@ -35,7 +35,7 @@ fun String.dateFormat2TimeStamp(format: String = "yyyy-MM-dd HH:mm:ss"): Long {
  * @param format 时间的格式,默认是按照yyyy-MM-dd HH:mm:ss来转换,如果格式不一样,则需要传入对应的格式
  */
 @SuppressLint("SimpleDateFormat")
-fun Long.timeStampFormat2Date(format: String = "yyyy-MM-dd HH:mm:ss"): String {
+fun Long.timeStampFormat2Date(format: String = "yyyy-MM-dd"): String {
 
     return try {
 
@@ -57,9 +57,9 @@ fun Long.timeStampFormat2Date(format: String = "yyyy-MM-dd HH:mm:ss"): String {
 
 /**
  * String类型时间戳转日期
- * @param format 时间的格式,默认是按照yyyy-MM-dd HH:mm:ss来转换,如果格式不一样,则需要传入对应的格式
+ * @param format 时间的格式,默认是按照yyyy-MM-dd来转换,如果格式不一样,则需要传入对应的格式
  */
-fun String.timeStampFormat2Date(format: String = "yyyy-MM-dd HH:mm:ss"): String {
+fun String.timeStampFormat2Date(format: String = "yyyy-MM-dd"): String {
     return this.toLong().timeStampFormat2Date(format)
 }
 
@@ -67,6 +67,6 @@ fun String.timeStampFormat2Date(format: String = "yyyy-MM-dd HH:mm:ss"): String
  * Int类型时间戳转日期
  * @param format 时间的格式,默认是按照yyyy-MM-dd HH:mm:ss来转换,如果格式不一样,则需要传入对应的格式
  */
-fun Int.timeStampFormat2Date(format: String = "yyyy-MM-dd HH:mm:ss"): String {
+fun Int.timeStampFormat2Date(format: String = "yyyy-MM-dd"): String {
     return this.toLong().timeStampFormat2Date(format)
 }

+ 6 - 6
cs-baselib/src/main/java/com/android/basiclib/view/gloading/GloadingGlobalStatusView.java

@@ -69,7 +69,7 @@ public class GloadingGlobalStatusView extends LinearLayout implements View.OnCli
         int image = R.drawable.anim_gloading;
         loading_view.setVisibility(VISIBLE);
         mImageView.setVisibility(GONE);
-        String str = "加载中...";
+        String str = "Loading...";
 
         switch (status) {
             case STATUS_LOAD_SUCCESS:
@@ -82,14 +82,14 @@ public class GloadingGlobalStatusView extends LinearLayout implements View.OnCli
             case STATUS_NORMAL:
                 loading_view.setVisibility(VISIBLE);
                 mImageView.setVisibility(GONE);
-                str = "加载中...";
+                str = "Loading...";
                 break;
 
             case STATUS_LOADING:
                 loading_view.setVisibility(VISIBLE);
                 loading_view.startLoading(0);
                 mImageView.setVisibility(GONE);
-                str = "加载中...";
+                str = "Loading...";
                 break;
 
             case STATUS_LOAD_FAILED:
@@ -102,7 +102,7 @@ public class GloadingGlobalStatusView extends LinearLayout implements View.OnCli
 //                    str = "NetWork Error";
 //                    image = R.mipmap.page_icon_network;
 //                } else {
-                str = TextUtils.isEmpty(msg) ? "加载错误" : msg;
+                str = TextUtils.isEmpty(msg) ? "Data loading failed! Please refresh and try again" : msg;
                 image = R.mipmap.loading_error;
 //                }
 
@@ -113,8 +113,8 @@ public class GloadingGlobalStatusView extends LinearLayout implements View.OnCli
                 loading_view.setVisibility(GONE);
                 loading_view.stopLoading();
                 mImageView.setVisibility(VISIBLE);
-                str = "没有数据";
-                image = R.mipmap.loading_error;
+                str = "There is currently no content available";
+                image = R.mipmap.loading_no_data;
                 break;
 
             default:

+ 2 - 3
cs-baselib/src/main/res/layout/view_gloading_global_status.xml

@@ -16,7 +16,6 @@
         android:layout_width="match_parent"
         android:layout_height="0dp"
         android:layout_weight="1"
-        android:background="@color/white"
         android:gravity="center_horizontal"
         android:orientation="vertical">
 
@@ -41,9 +40,9 @@
             android:id="@+id/text"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_marginTop="15dp"
+            android:layout_marginTop="18dp"
             android:gravity="center_horizontal"
-            android:textColor="#999999"
+            android:textColor="#D6E9F1"
             android:textSize="16sp" />
 
         <View

BIN
cs-baselib/src/main/res/mipmap-xhdpi/loading_error.webp


BIN
cs-baselib/src/main/res/mipmap-xhdpi/loading_no_data.webp