+ extends RxAppCompatActivity implements IView {
+ //P层主导
+ protected Dialog loadingDialog;
+ private NetworkChangeReceiver receiver;
+ public static IView iView;
+ public TextView tipTextView;
+ protected T viewBinding;
+ protected BaseActivityNoP mContext;
+
+ @Override protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ try {
+ mContext = this;
+ ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
+ Class cls = (Class) type.getActualTypeArguments()[0];
+ Method inflate = cls.getDeclaredMethod("inflate", LayoutInflater.class);
+ viewBinding = (T) inflate.invoke(null, getLayoutInflater());
+ setContentView(viewBinding.getRoot());
+ //在界面初始化之前初始化窗口
+ initWindows();
+ int layoutResID = getLayoutResId(savedInstanceState);
+ if (layoutResID != 0) {
+ getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
+// setContentView(layoutResID);
+
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ initWidget();
+ initData(savedInstanceState);
+ }
+
+ /**
+ * 初始化窗口
+ */
+ protected void initWindows() {
+ //StatusBarUtils.setWindowStatusBarColor(this, R.color.color_blue01);
+// StatusBarUtil.setAndroidNativeLightStatusBar(this, true);
+ }
+
+ protected void initWidget() {
+ ActivityManager.addActivity(this, this.getClass().getName());
+ iView = this;
+ if (receiver == null) {
+ receiver = new NetworkChangeReceiver();
+ }
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+ registerReceiver(receiver, filter);
+ }
+
+ @Override protected void onDestroy() {
+ unregisterReceiver(receiver);
+ hideDialogLoading();
+ super.onDestroy();
+ }
+
+ /***
+ * 关闭等待页面
+ */
+ public void hideDialogLoading() {
+ LoadingDialog dialog = mLoadingDialog;
+ try {
+ if (dialog != null && mContext != null) {
+ mLoadingDialog = null;
+ dialog.dismiss();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ //每个activity都需要创建setLayoutId
+ public abstract int getLayoutResId(Bundle savedInstanceState);
+
+ public void initData(Bundle savedInstanceState) {
+ createLoadingDialog(this, "加载中...");
+ }
+
+ /**
+ * 模拟显示加载对话框
+ */
+ @Override public void showLoading() {
+ loadingDialog.show();
+ }
+
+ //模拟处理统一的数据
+ @Override public void handleError(Exception e) {
+ e.printStackTrace();
+ hideLoading();
+ showMessage(e.getMessage());
+ }
+
+ @Override public void hideLoading() {
+ loadingDialog.dismiss();
+ }
+
+ @Override public void showMessage(String message) {
+ ToastUtil.showShort(this, message);
+ }
+
+ //加载进度
+ protected void createLoadingDialog(Context context, String msg) {
+ LayoutInflater inflater = LayoutInflater.from(context);
+ @SuppressLint("InflateParams") View v = inflater.inflate(R.layout.dialog_loading, null);
+
+ LinearLayout layout = (LinearLayout) v.findViewById(R.id.dialog_loading_view);
+ tipTextView = (TextView) v.findViewById(R.id.tipTextView);
+ tipTextView.setText(msg);
+ ImageView imgLoading = v.findViewById(R.id.img_loading);
+ loadingDialog = new Dialog(context, R.style.MyDialogStyle);
+ loadingDialog.setCanceledOnTouchOutside(false);
+ loadingDialog.setContentView(layout,
+ new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.MATCH_PARENT));
+ Window window = loadingDialog.getWindow();
+ if (window != null) {
+ WindowManager.LayoutParams lp = window.getAttributes();
+ lp.width = WindowManager.LayoutParams.MATCH_PARENT;
+ lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ window.setGravity(Gravity.CENTER);
+ window.setAttributes(lp);
+ window.setWindowAnimations(R.style.PopWindowAnimStyle);
+ }
+ loadingDialog.setCanceledOnTouchOutside(false);
+ }
+
+
+ protected LoadingDialog mLoadingDialog;
+ public void showLoadingDialog() {
+ LoadingDialog dialog = mLoadingDialog;
+ if (dialog == null&& mContext != null) {
+ dialog = new LoadingDialog(mContext);
+ // 不可触摸取消
+ dialog.setCanceledOnTouchOutside(false);
+ // 强制取消关闭界面
+ dialog.setCancelable(true);
+ dialog.setOnCancelListener(dialog1 -> finish());
+ mLoadingDialog = dialog;
+ } else {
+ dialog.dismiss();
+ }
+ if (dialog != null && mContext != null) {
+ try {
+ dialog.show();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+
+ @Override public void onNetChange(int netModel) {
+
+ }
+
+ @Override public boolean dispatchTouchEvent(MotionEvent ev) {
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ View view = getCurrentFocus();
+ TextUtil.hideKeyboard(ev, view, this);//调用方法判断是否需要隐藏键盘
+ break;
+ default:
+ break;
+ }
+ return super.dispatchTouchEvent(ev);
+ }
+
+ /**
+ * activity跳转
+ */
+ protected void goTo(Context from, Class extends BaseActivityNoP> to, Bundle data) {
+ Intent i = new Intent();
+ i.setClass(from, to);
+ if (data != null) i.putExtras(data);
+ from.startActivity(i);
+ }
+
+ /**
+ * activity跳转
+ */
+ protected void goToWihoutNoBase(Context from, Class extends Activity> to, Bundle data) {
+ Intent i = new Intent();
+ i.setClass(from, to);
+ if (data != null) i.putExtras(data);
+ from.startActivity(i);
+ }
+
+ /**
+ * 跳转返回值
+ */
+ protected void goToForResult(Context from, Class extends BaseActivityNoP> to, int questCode,
+ Bundle bundle) {
+ Intent i = new Intent();
+ i.setClass(from, to);
+ if (bundle != null) i.putExtras(bundle);
+ startActivityForResult(i, questCode);
+ }
+
+ protected void requestPermission(String... permissions) {
+ if (PermissionUtils.isPermissionsGranted(this, ConstantUtil.PERMISSION_QUEST, permissions)) {
+ Log.d("permission", "all");
+ nextStep();
+ }
+ }
+
+ public void noAllowPermission() {
+
+ }
+
+ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
+ @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ switch (requestCode) {
+ case ConstantUtil.PERMISSION_QUEST:
+ if (PermissionUtils.verifyPermissions(grantResults)) {
+ nextStep();
+ }else {
+ noAllowPermission();
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ public interface OnDialogClick {
+ void onCancelClick();
+ void onConfirmClisk();
+ void onView1Click();
+ }
+
+ /**+
+ *
+ * @param title 标题
+ * @param content 提示内容
+ * @param hint 提示内容2
+ * @param type 弹窗类型
+ * @param cancelText 取消 文案
+ * @param confirmText 确定 文案
+ * @param click
+ */
+ public void showDialog(String title, String content, String hint, String type,String cancelText,String confirmText, final OnDialogClick click) {
+
+ }
+
+
+
+
+
+
+ public Dialog dialog = null;
+ // 弹出一个确认对话框 并可以设置取消按钮事件
+ public void showDialogtext(String title, String content, String okText, String cancelText, final OnDialogClick click) {
+ View view = getLayoutInflater().inflate(R.layout.public_dialog_confirm, null);
+ setTextView(R.id.tv_title, title, view);
+ setTextView(R.id.tv_content, content, view);
+ // ((EditText) view.findViewById(R.id.txtContent)).setText(cancelText);
+ if(!TextUtils.isEmpty(okText) && !TextUtils.isEmpty(cancelText)){
+ ((TextView) view.findViewById(R.id.btnLeft)).setText(cancelText);
+ ((TextView) view.findViewById(R.id.btnRight)).setText(okText);
+ }
+
+ view.findViewById(R.id.btnLeft).setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ if (dialog != null && dialog.isShowing())
+ dialog.dismiss();
+ if (click != null)
+ click.onCancelClick();
+ }
+ });
+ view.findViewById(R.id.btnRight).setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ if (dialog != null && dialog.isShowing())
+ dialog.dismiss();
+ if (click != null)
+ click.onConfirmClisk();
+ }
+ });
+ view.findViewById(R.id.tv_click).setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ if (dialog != null && dialog.isShowing())
+ if (click != null)
+ click.onView1Click();
+ }
+ });
+ AlertDialog.Builder builder = null;//new AlertDialog.Builder(this, android.R.style.Theme_Holo_Dialog);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ builder = new AlertDialog.Builder(this, AlertDialog.THEME_HOLO_LIGHT);
+ } else {
+ builder = new AlertDialog.Builder(this);
+ }
+ builder.setInverseBackgroundForced(true);
+ builder.setView(view);
+ dialog = builder.show();
+ try {
+ ((View) dialog.getWindow().getDecorView().findViewById(R.id.contentDialog).getParent().getParent()).setBackgroundColor(getResources().getColor(R.color.transparent));
+ } catch (Exception e) {
+ }
+
+ }
+
+
+ // 弹出一个确认对话框 并可以设置取消按钮事件
+ public void showDialogtext(Drawable imgRes, boolean isIconShow, boolean isTextShow, String title, String content, String okText, String cancelText,
+ final BaseFragment.OnDialogClick click) {
+ View view = getLayoutInflater().inflate(R.layout.public_dialog_index_permis, null);
+ ImageView imgIcon = view.findViewById(R.id.img_dialog_icon);
+ TextView tvOther = view.findViewById(R.id.tv_click);
+ if(isIconShow){
+ imgIcon.setVisibility(View.VISIBLE);
+ if(imgRes != null ){
+ imgIcon.setImageDrawable(imgRes);
+ }
+ }else{
+ imgIcon.setVisibility(View.GONE);
+ }
+ if(isTextShow){
+ tvOther.setVisibility(View.VISIBLE);
+ }else{
+ tvOther.setVisibility(View.GONE);
+ }
+
+ setTextView(R.id.tv_title, title, view);
+ setTextView(R.id.tv_content, content, view);
+ // ((EditText) view.findViewById(R.id.txtContent)).setText(cancelText);
+ if (!TextUtils.isEmpty(okText) && !TextUtils.isEmpty(cancelText)) {
+ ((TextView) view.findViewById(R.id.btnLeft)).setText(okText);
+ ((TextView) view.findViewById(R.id.btnRight)).setText(cancelText);
+ }
+ view.findViewById(R.id.btnLeft).setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ if (dialog != null && dialog.isShowing())
+ dialog.dismiss();
+ if (click != null)
+ click.onCancelClick();
+ }
+ });
+ view.findViewById(R.id.btnRight).setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ if (dialog != null && dialog.isShowing())
+ dialog.dismiss();
+ if (click != null)
+ click.onConfirmClick();
+ }
+ });
+ AlertDialog.Builder builder = null;//new AlertDialog.Builder(this, android.R.style.Theme_Holo_Dialog);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ builder = new AlertDialog.Builder(mContext, AlertDialog.THEME_HOLO_LIGHT);
+ } else {
+ builder = new AlertDialog.Builder(mContext);
+ }
+ builder.setInverseBackgroundForced(true);
+ builder.setView(view);
+ dialog = builder.show();
+ try {
+ ((View) dialog.getWindow().getDecorView().findViewById(R.id.contentDialog).getParent().getParent()).setBackgroundColor(getResources().getColor(R.color.transparent));
+ } catch (Exception e) {
+ }
+
+ }
+
+
+ /**
+ * 设置指定TextView的文本
+ *
+ * @param id
+ * @param value textView的值
+ */
+ public void setTextView(int id, String value, View view) {
+ try {
+ if (!TextUtils.isEmpty(value)) {
+ value = value.replaceAll("null", "");
+ ((TextView) view.findViewById(id)).setText(value);
+ } else {
+ ((TextView) view.findViewById(id)).setText("");
+ ((TextView) view.findViewById(id)).setVisibility(View.GONE);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void nextStep() {
+
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/base/BaseFragment.java b/app/src/main/java/com/ckkj/water/base/BaseFragment.java
new file mode 100644
index 0000000..072de83
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/base/BaseFragment.java
@@ -0,0 +1,890 @@
+package com.ckkj.water.base;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.app.ActivityCompat;
+import androidx.fragment.app.Fragment;
+import androidx.viewbinding.ViewBinding;
+
+import com.esafirm.imagepicker.features.ImagePicker;
+import com.ckkj.water.AppConfig;
+import com.ckkj.water.ConstantUtil;
+import com.ckkj.water.EventMessage;
+import com.ckkj.water.R;
+import com.ckkj.water.dialog.LoadingDialog;
+import com.ckkj.water.login.LoginActivity;
+import com.ckkj.water.utils.dataUtil.SharePreUtil;
+import com.ckkj.water.utils.manager.ActivityManager;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import de.greenrobot.event.EventBus;
+import de.greenrobot.event.Subscribe;
+import de.greenrobot.event.ThreadMode;
+import pub.devrel.easypermissions.EasyPermissions;
+
+/**
+ * 所有Fragment基类
+ * @param
+ */
+
+@SuppressWarnings({ "unchecked" })
+public abstract class BaseFragment extends Fragment implements IView, EasyPermissions.PermissionCallbacks {
+ protected String TAG = "MING_DENG";
+ protected Activity activity;
+ protected P mPresenter;
+ protected Dialog loadingDialog;
+ protected T viewBinding;
+ protected Activity mContext;
+ protected final int REQUEST_CODE_100 = 100;
+ protected final int REQUEST_CODE_200 = 200;
+ protected static final int REQUEST_EVENT_CODE_10001 = 10001;
+ protected static final int REQUEST_EVENT_CODE_10002 = 10002;
+ protected static final int REQUEST_EVENT_CODE_10003 = 10003;
+
+ private static final AtomicBoolean isDialogShown = new AtomicBoolean(false); // 使用 AtomicBoolean 保证线程安全
+
+
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ initArgs(getArguments());
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ onResumeInitData();
+ }
+
+ public boolean hasRecordPermission(String permission) {
+ return EasyPermissions.hasPermissions(mContext, permission);
+ }
+
+ /**
+ * 获取权限
+ *
+ * @param permis
+ * @param title
+ * @param content
+ * @param requestCode String... permis
+ */
+ public void getPermissionRequst(String permis, String title, String content, int requestCode) {
+ if (EasyPermissions.hasPermissions(mContext, permis)) {
+ // 已有权限,不需要处理
+ return;
+ }
+ // 还没权限,弹自定义确认弹窗
+ showDialogtext(mContext.getResources().getDrawable(R.mipmap.img_location),
+ false, false,
+ title,
+ content,
+ "不允许", "允许",
+ new BaseFragment.OnDialogClick() {
+ @Override
+ public void onCancelClick() {
+ // 用户点了"不允许",什么都不用做
+ }
+
+ @Override
+ public void onConfirmClick() {
+ showPermissionDialog(mContext.getResources().getDrawable(R.mipmap.img_permission_file),
+ true, title, content);
+ // 用户点了"允许",继续判断
+ if (ActivityCompat.shouldShowRequestPermissionRationale(mContext, permis)) {
+ // 用户之前拒绝过,但没勾"不再询问" ➔ 直接请求权限
+ // 修复:第一个参数应该是Fragment本身,而不是Activity的context
+ EasyPermissions.requestPermissions(BaseFragment.this, content, requestCode, permis);
+ } else {
+ // 可能是第一次申请,也可能是勾了"不再询问"
+ // 用EasyPermissions继续请求,系统自己决定弹不弹
+ // 修复:第一个参数应该是Fragment本身,而不是Activity的context
+ EasyPermissions.requestPermissions(BaseFragment.this, content, requestCode, permis);
+ }
+ }
+ });
+ }
+
+ /**
+ * 判断Fragment是否真正对用户可见
+ */
+ protected boolean isFragmentVisible() {
+ return isVisible() && isResumed();
+ }
+
+ /**
+ * 可见时初始化数据
+ */
+ protected void onResumeInitData() {
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ createLoadingDialog(getActivity());
+ mPresenter = createPresenter();//创建presenter
+ }
+ @Nullable
+ @Override
+ public View onCreateView(@Nullable LayoutInflater inflater,@Nullable ViewGroup container,@Nullable Bundle state) {
+ mContext = getActivity();
+ activity = getActivity();
+ EventBus.getDefault().register(this);
+ ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
+ Class cls = (Class) type.getActualTypeArguments()[0];
+ try {
+ Method inflate = cls.getDeclaredMethod("inflate", LayoutInflater.class, ViewGroup.class, boolean.class);
+ viewBinding = (T) inflate.invoke(null, inflater, container, false);
+ } catch (NoSuchMethodException | IllegalAccessException| InvocationTargetException e) {
+ e.printStackTrace();
+ }
+ initView();
+ return viewBinding.getRoot();
+ }
+
+
+ @Subscribe(threadMode = ThreadMode.MainThread)
+ public void onGetMessage(EventMessage message) {
+ Log.d("DINGDING", "BaseFragment收到EventBus消息: " + message.msg);
+
+ if (message.msg.equals(EventMessage.USER_LOGIN_LOST)) {
+
+ Log.d("DINGDING","Fragment接收一次");
+ Log.d("DINGDING", "当前Fragment状态: isDetached=" + isDetached() + ", isRemoving=" + isRemoving());
+
+ // 检查Fragment状态,如果正在销毁则不处理
+ if (isDetached() || isRemoving()) {
+ Log.d("DINGDING", "Fragment正在销毁,跳过处理");
+ return;
+ }
+
+ // 检查Activity状态,如果Activity正在销毁则不处理
+ if (mContext == null || mContext.isFinishing() || mContext.isDestroyed()) {
+ Log.d("DINGDING", "Fragment所属Activity正在销毁,跳过处理");
+ return;
+ }
+
+ if (isDialogShown.compareAndSet(false, true)) {
+ Log.d("DINGDING","Fragment触发弹窗");
+ AppConfig.USER_LOGIN = false;
+ AppConfig.MEMBER_ID = 0;
+ AppConfig.USER_TOKEN = "";
+ AppConfig.USER_ID = "";
+ SharePreUtil.putBoolean(ConstantUtil.SHARE_PRE_NAME, mContext, ConstantUtil.USER_LOGIN, AppConfig.USER_LOGIN);
+
+ // 确保在主线程中执行
+ mContext.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ // 再次检查Fragment和Activity状态
+ if (isDetached() || isRemoving() || mContext == null || mContext.isFinishing() || mContext.isDestroyed()) {
+ Log.d("DINGDING", "Fragment或Activity已销毁,取消弹窗");
+ isDialogShown.set(false);
+ return;
+ }
+
+ showDialogtextInterBackKey(null, false, false,
+ "提示",
+ "检测到您的账号在异地登录。若非本人操作,请立即修改密码并检查账号安全!",
+ "我知道了", "", // 交换参数位置
+ new OnDialogClick() {
+ @Override
+ public void onCancelClick() {
+ Log.d("DINGDING", "用户点击取消");
+ }
+
+ @Override
+ public void onConfirmClick() {
+ Log.d("DINGDING", "用户点击确定");
+ ActivityManager.removeAllActivityExceptMain();
+ goTo(mContext, LoginActivity.class, null);
+ isDialogShown.set(false);
+ }
+ }, true);
+ Log.d("DINGDING", "Fragment弹窗显示成功");
+ } catch (Exception e) {
+ Log.e("DINGDING", "Fragment弹窗显示失败", e);
+ isDialogShown.set(false);
+ }
+ }
+ });
+ } else {
+ Log.d("DINGDING", "Fragment弹窗已显示,跳过");
+ }
+ } else if (message.msg.equals(EventMessage.CODE_LOGIN_LOST)) {
+ // 检查是否满足跳转条件
+ // if (MyApplication.canJump()) { // MyApplication is not defined in this file
+ // }
+ }
+ }
+
+
+ /**
+ * 初始化控件
+ */
+ protected void initView() {
+ }
+
+
+ /**
+ * activity跳转
+ */
+ protected void goToWihoutNoPresent(Context from, Class extends BaseActivityNoP> to, Bundle data) {
+ Intent i = new Intent();
+ i.setClass(from, to);
+ if (data != null) i.putExtras(data);
+ from.startActivity(i);
+ }
+
+
+ protected LoadingDialog mLoadingDialog;
+ public void showLoadingDialog() {
+ LoadingDialog dialog = mLoadingDialog;
+ if (dialog == null&& mContext != null) {
+ dialog = new LoadingDialog(mContext);
+ // 不可触摸取消
+ dialog.setCanceledOnTouchOutside(false);
+ // 强制取消关闭界面
+ dialog.setCancelable(true);
+// dialog.setOnCancelListener(dialog1 -> finish());
+ mLoadingDialog = dialog;
+ } else {
+ dialog.dismiss();
+ }
+ if (dialog != null && mContext != null) {
+ try {
+ dialog.show();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+
+
+ protected void createLoadingDialog(Context context){
+ LayoutInflater inflater = LayoutInflater.from(context);
+ @SuppressLint("InflateParams") View v = inflater.inflate(R.layout.dialog_loading, null);
+ LinearLayout layout = (LinearLayout) v.findViewById(R.id.dialog_loading_view);
+ ImageView imgLoading = v.findViewById(R.id.img_loading);
+ loadingDialog = new Dialog(context, R.style.MyDialogStyle);
+ loadingDialog.setCanceledOnTouchOutside(false);
+ loadingDialog.setContentView(layout,
+ new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.MATCH_PARENT));
+ Window window = loadingDialog.getWindow();
+ if (window != null) {
+ WindowManager.LayoutParams lp = window.getAttributes();
+ lp.width = WindowManager.LayoutParams.MATCH_PARENT;
+ lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ window.setGravity(Gravity.CENTER);
+ window.setAttributes(lp);
+ window.setWindowAnimations(R.style.PopWindowAnimStyle);
+ }
+ loadingDialog.setCanceledOnTouchOutside(false);
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ if (mPresenter != null) {
+ mPresenter.attachView(this);
+ }
+ initData(savedInstanceState);
+ }
+
+
+ public void imagePicker(int limitCount,int requestCode,Fragment fragment){
+ //获取相册
+ ImagePicker.create(fragment)
+ .folderMode(false)
+ .showCamera(false).limit(limitCount)
+ .start(requestCode);
+ String[] permis = {Manifest.permission.READ_EXTERNAL_STORAGE};
+// if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
+// //获取到了权限,可以请求地址显示
+// if (EasyPermissions.hasPermissions(mContext, permis)) {
+//
+// }else{
+// showDialogtext(mContext.getResources().getDrawable(R.mipmap.img_location), false, false,
+// "相册访问权限", "超越仓需要您的授权,访问相册来上传资料",
+// "不允许", "允许", new BaseFragment.OnDialogClick() {
+// @Override
+// public void onCancelClick() {
+//
+// }
+//
+// @Override
+// public void onConfirmClick() {
+// if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
+//// Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
+//// mContext.startActivity(intent);
+// EasyPermissions.requestPermissions(mContext, "应用需要获相册授权",
+// ConstantUtil.READ_EXTERNAL_STORAGE
+// , Manifest.permission.READ_EXTERNAL_STORAGE);
+// } else {
+// EasyPermissions.requestPermissions(mContext, "应用需要获相册授权",
+// ConstantUtil.READ_EXTERNAL_STORAGE
+// , Manifest.permission.READ_EXTERNAL_STORAGE);
+// }
+// }
+// });
+// }
+//// XXPermissions.with(mContext)
+//// // 申请单个权限
+//// .permission(Permission.READ_EXTERNAL_STORAGE)
+//// .interceptor(new PermissionInterceptor())
+//// .request(new OnPermissionCallback() {
+//// @Override
+//// public void onGranted(List permissions, boolean all) {
+////
+//// }
+////
+//// @Override
+//// public void onDenied(List permissions, boolean never) {
+//// if (never) {
+//// Toast.makeText(mContext, "被永久拒绝授权,请手动授予权限", Toast.LENGTH_SHORT).show();
+//// // 如果是被永久拒绝就跳转到应用权限系统设置页面
+//// XXPermissions.startPermissionActivity(mContext, permissions);
+//// } else {
+//// Toast.makeText(mContext, "获取权限失败", Toast.LENGTH_SHORT).show();
+//// }
+//// return;
+//// }
+//// });
+//
+// } else {
+// //获取到了权限,可以请求地址显示
+// if (EasyPermissions.hasPermissions(mContext, permis)) {
+// //获取相册
+// ImagePicker.create(fragment)
+// .folderMode(false)
+// .showCamera(false).limit(limitCount)
+// .start(requestCode);
+// } else {
+// showDialogtext(mContext.getResources().getDrawable(R.mipmap.img_location), false, false,
+// "相册访问权限", "超越仓需要您的授权,访问相册来上传资料",
+// "不允许", "允许", new BaseFragment.OnDialogClick() {
+// @Override
+// public void onCancelClick() {
+//
+// }
+//
+// @Override
+// public void onConfirmClick() {
+// if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
+// Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
+// mContext.startActivity(intent);
+// } else {
+// EasyPermissions.requestPermissions(mContext, "应用需要获相册授权",
+// ConstantUtil.READ_EXTERNAL_STORAGE
+// , Manifest.permission.READ_EXTERNAL_STORAGE);
+// }
+// }
+// });
+// }
+// }
+ }
+
+
+ public Dialog dialog = null;
+ // 弹出一个确认对话框 并可以设置取消按钮事件
+ public void showDialogtext(Drawable imgRes,boolean isIconShow, boolean isTextShow, String title,
+ String content, String okText, String cancelText,
+ final OnDialogClick click) {
+ View view = getLayoutInflater().inflate(R.layout.public_dialog_index_permis, null);
+ ImageView imgIcon = view.findViewById(R.id.img_dialog_icon);
+ TextView tvOther = view.findViewById(R.id.tv_click);
+ TextView tvLeft = view.findViewById(R.id.btnLeft);
+ if(isIconShow){
+ imgIcon.setVisibility(View.VISIBLE);
+ if(imgRes != null ){
+ imgIcon.setImageDrawable(imgRes);
+ }
+ }else{
+ imgIcon.setVisibility(View.GONE);
+ }
+ if(isTextShow){
+ tvOther.setVisibility(View.VISIBLE);
+ }else{
+ tvOther.setVisibility(View.GONE);
+ }
+
+
+
+ setTextView(R.id.tv_title, title, view);
+ setTextView(R.id.tv_content, content, view);
+ // ((EditText) view.findViewById(R.id.txtContent)).setText(cancelText);
+ if (!TextUtils.isEmpty(okText) && !TextUtils.isEmpty(cancelText)) {
+ ((TextView) view.findViewById(R.id.btnLeft)).setText(okText);
+ ((TextView) view.findViewById(R.id.btnRight)).setText(cancelText);
+ }
+ view.findViewById(R.id.btnLeft).setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ if (dialog != null && dialog.isShowing())
+ dialog.dismiss();
+ if (click != null)
+ click.onCancelClick();
+ }
+ });
+ view.findViewById(R.id.btnRight).setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ if (dialog != null && dialog.isShowing())
+ dialog.dismiss();
+ if (click != null)
+ click.onConfirmClick();
+ }
+ });
+ if(!TextUtils.isEmpty(cancelText)){
+ tvLeft.setVisibility(View.VISIBLE);
+ }else{
+ tvLeft.setVisibility(View.GONE);
+
+ }
+ AlertDialog.Builder builder = null;//new AlertDialog.Builder(this, android.R.style.Theme_Holo_Dialog);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ builder = new AlertDialog.Builder(mContext, AlertDialog.THEME_HOLO_LIGHT);
+ } else {
+ builder = new AlertDialog.Builder(mContext);
+ }
+ builder.setInverseBackgroundForced(true);
+ builder.setView(view);
+ dialog = builder.show();
+ dialog.setCanceledOnTouchOutside(false);
+ try {
+ ((View) dialog.getWindow().getDecorView().findViewById(R.id.contentDialog).getParent().getParent()).setBackgroundColor(getResources().getColor(R.color.transparent));
+ } catch (Exception e) {
+ }
+ }
+
+ /**
+ * 强制登出,并且拦截返回键
+ * @param imgRes
+ * @param isIconShow
+ * @param isTextShow
+ * @param title
+ * @param content
+ * @param okText
+ * @param cancelText
+ * @param click
+ * @param interceptBackKey
+ */
+ public void showDialogtextInterBackKey(Drawable imgRes, boolean isIconShow, boolean isTextShow, String title,
+ String content, String okText, String cancelText,
+ final OnDialogClick click, boolean interceptBackKey) {
+ View view = getLayoutInflater().inflate(R.layout.public_dialog_index_permis, null);
+ ImageView imgIcon = view.findViewById(R.id.img_dialog_icon);
+ TextView tvOther = view.findViewById(R.id.tv_click);
+ TextView tvLeft = view.findViewById(R.id.btnLeft);
+
+ if (isIconShow) {
+ imgIcon.setVisibility(View.VISIBLE);
+ if (imgRes != null) {
+ imgIcon.setImageDrawable(imgRes);
+ }
+ } else {
+ imgIcon.setVisibility(View.GONE);
+ }
+
+ tvOther.setVisibility(isTextShow ? View.VISIBLE : View.GONE);
+
+ setTextView(R.id.tv_title, title, view);
+ setTextView(R.id.tv_content, content, view);
+
+ if (!TextUtils.isEmpty(okText) && !TextUtils.isEmpty(cancelText)) {
+ ((TextView) view.findViewById(R.id.btnLeft)).setText(okText);
+ ((TextView) view.findViewById(R.id.btnRight)).setText(cancelText);
+ }
+
+ view.findViewById(R.id.btnLeft).setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ if (dialog != null && dialog.isShowing())
+ dialog.dismiss();
+ if (click != null)
+ click.onCancelClick();
+ }
+ });
+
+ view.findViewById(R.id.btnRight).setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ if (dialog != null && dialog.isShowing())
+ dialog.dismiss();
+ if (click != null)
+ click.onConfirmClick();
+ }
+ });
+
+ tvLeft.setVisibility(TextUtils.isEmpty(cancelText) ? View.GONE : View.VISIBLE);
+
+ AlertDialog.Builder builder;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ builder = new AlertDialog.Builder(mContext, AlertDialog.THEME_HOLO_LIGHT);
+ } else {
+ builder = new AlertDialog.Builder(mContext);
+ }
+
+ builder.setInverseBackgroundForced(true);
+ builder.setView(view);
+
+ dialog = builder.show();
+ dialog.setCanceledOnTouchOutside(false);
+
+ // 🔒 返回键拦截逻辑
+ if (interceptBackKey) {
+ dialog.setCancelable(false);
+ dialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
+ @Override
+ public boolean onKey(DialogInterface dialogInterface, int keyCode, KeyEvent event) {
+ return keyCode == KeyEvent.KEYCODE_BACK;
+ }
+ });
+ }
+ try {
+ ((View) dialog.getWindow().getDecorView()
+ .findViewById(R.id.contentDialog)
+ .getParent().getParent())
+ .setBackgroundColor(getResources().getColor(R.color.transparent));
+ } catch (Exception e) {
+ // 安全兜底
+ }
+ }
+
+
+
+
+
+
+ public void showPermissionDialog(Drawable imgRes,boolean isIconShow, String title,
+ String content) {
+ View view = getLayoutInflater().inflate(R.layout.permisson_top_dialog, null);
+ ImageView imgIcon = view.findViewById(R.id.img_permission_icon);
+ TextView tvTitle = view.findViewById(R.id.tv_permission_title);
+ TextView tvContent = view.findViewById(R.id.tv_permission_content);
+ tvTitle.setText(title);
+ tvContent.setText(content);
+ if(isIconShow){
+ imgIcon.setVisibility(View.VISIBLE);
+ if(imgRes != null ){
+ imgIcon.setImageDrawable(imgRes);
+ }
+ }else{
+ imgIcon.setVisibility(View.GONE);
+ }
+ AlertDialog.Builder builder = null;//new AlertDialog.Builder(this, android.R.style.Theme_Holo_Dialog);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ builder = new AlertDialog.Builder(mContext, AlertDialog.THEME_HOLO_LIGHT);
+ } else {
+ builder = new AlertDialog.Builder(mContext);
+ }
+ builder.setInverseBackgroundForced(true);
+ builder.setView(view);
+ dialog = builder.create();
+ // 获取 dialog 的 Window 对象
+ Window window = dialog.getWindow();
+ if (window != null) {
+ // 设置 dialog 在屏幕顶部显示
+ WindowManager.LayoutParams params = window.getAttributes();
+ params.gravity = Gravity.TOP; // 设置对话框顶部显示
+ window.setAttributes(params);
+ }
+ // 显示 AlertDialog
+ dialog.show();
+ // dialog = builder.show();
+ try {
+ ((View) dialog.getWindow().getDecorView().findViewById(R.id.contentDialog).getParent().getParent()).setBackgroundColor(getResources().getColor(R.color.transparent));
+ } catch (Exception e) {
+ }
+ }
+
+
+ /**
+ * 设置指定TextView的文本
+ *
+ * @param id
+ * @param value textView的值
+ */
+ public void setTextView(int id, String value, View view) {
+ try {
+ if (!TextUtils.isEmpty(value)) {
+ value = value.replaceAll("null", "");
+ ((TextView) view.findViewById(id)).setText(value);
+ ((TextView) view.findViewById(id)).setVisibility(View.VISIBLE);
+ } else {
+ ((TextView) view.findViewById(id)).setText("");
+ ((TextView) view.findViewById(id)).setVisibility(View.GONE);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ public interface OnDialogClick {
+ public void onCancelClick();
+
+ public void onConfirmClick();
+ }
+
+ /**
+ * 初始化参数
+ * @param bundle 需要初始化的参数
+ */
+ protected void initArgs(Bundle bundle) {
+ }
+
+ /**
+ * 初始化控件
+ */
+ protected void initWidget(View root) {
+ }
+ /**
+ * 初始化views
+ *
+ * @param state
+ */
+ public abstract void initData(Bundle state);
+ public abstract P createPresenter();
+
+ @Override
+ public void onDestroy() {
+ EventBus.getDefault().unregister(this);
+ if (mPresenter != null) {
+ mPresenter.onDestroy();//释放资源
+ }
+ this.mPresenter = null;
+ hideDialogLoading();
+
+ // 重置弹窗状态,让其他Fragment可以显示弹窗
+ isDialogShown.set(false);
+
+ super.onDestroy();
+ }
+
+ @Override public void onDestroyView() {
+ super.onDestroyView();
+ //此处try catch 是因为unbinder.unbind() 在清空状态下,会造成崩溃
+ //其他处不影响使用
+ try {
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ this.activity = null;
+ }
+ @Override
+ public void showLoading() {
+ loadingDialog.show();
+ }
+ @Override
+ public void hideLoading() {
+ loadingDialog.dismiss();
+ }
+
+ @Override
+ public void showMessage(String message) {
+ }
+
+ @Override
+ public void handleError(Exception e) {
+ hideLoading();
+ showMessage(e.getMessage());
+ e.printStackTrace();
+ }
+
+ /**
+ * 跳转
+ *
+ * @param data 携带数据
+ */
+ protected void goTo(Context from, Class extends BaseActivity> to, Bundle data) {
+ Intent i = new Intent();
+ i.setClass(from, to);
+ if(data!=null) i.putExtras(data);
+ from.startActivity(i);
+ }
+ /**
+ * 跳转返回
+ *
+ *
+ */
+ protected void goToForResult(Context from, Class extends BaseActivity> to, int questCode, Bundle data) {
+ Intent i = new Intent();
+ i.setClass(from, to);
+ if(data!=null) i.putExtras(data);
+ startActivityForResult(i,questCode);
+ }
+
+
+ /***
+ * 关闭等待页面
+ */
+ public void hideDialogLoading() {
+ LoadingDialog dialog = mLoadingDialog;
+ try {
+ if (dialog != null && mContext != null) {
+ mLoadingDialog = null;
+ dialog.dismiss();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void hidePermissionDialog(){
+ if(dialog != null && dialog.isShowing()){
+ dialog.dismiss();
+ }
+ }
+
+
+ public void clearLoginInfo(){
+ AppConfig.USER_LOGIN = false;
+ SharePreUtil.putString(ConstantUtil.SHARE_PRE_NAME,mContext,ConstantUtil.USER_INFO_PHONE_NUMBER,"");
+ SharePreUtil.putString(ConstantUtil.SHARE_PRE_NAME,mContext,ConstantUtil.USER_UPLOAD_COMPANY_NAME,"未登录");
+ }
+
+
+ /**
+ * 处理登录丢失的统一方法
+ * 子类可以直接调用此方法来处理401错误
+ */
+ protected void handleLoginLost() {
+ Log.d("DINGDING", "BaseFragment.handleLoginLost() 被调用");
+
+ // 检查Fragment状态
+ if (isDetached() || isRemoving()) {
+ Log.d("DINGDING", "Fragment正在销毁,跳过处理");
+ return;
+ }
+
+ // 检查Activity状态
+ if (mContext == null || mContext.isFinishing() || mContext.isDestroyed()) {
+ Log.d("DINGDING", "Fragment所属Activity正在销毁,跳过处理");
+ return;
+ }
+
+ // 重置登录信息
+ resetLoginInfo();
+
+ // 显示401弹窗
+ show401Dialog();
+ }
+
+ /**
+ * 重置登录信息
+ */
+ private void resetLoginInfo() {
+ AppConfig.USER_LOGIN = false;
+ AppConfig.MEMBER_ID = 0;
+ AppConfig.USER_TOKEN = "";
+ AppConfig.USER_ID = "";
+ SharePreUtil.putBoolean(ConstantUtil.SHARE_PRE_NAME, mContext, ConstantUtil.USER_LOGIN, AppConfig.USER_LOGIN);
+ Log.d("DINGDING", "登录信息已重置");
+ }
+
+ /**
+ * 显示401弹窗
+ */
+ private void show401Dialog() {
+ if (mContext == null || mContext.isFinishing() || mContext.isDestroyed()) {
+ Log.d("DINGDING", "Activity状态异常,无法显示弹窗");
+ return;
+ }
+
+ mContext.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ // 再次检查状态
+ if (isDetached() || isRemoving() || mContext == null || mContext.isFinishing() || mContext.isDestroyed()) {
+ Log.d("DINGDING", "Fragment或Activity已销毁,取消弹窗");
+ return;
+ }
+
+ showDialogtextInterBackKey(null, false, false,
+ "提示",
+ "检测到您的账号在异地登录。若非本人操作,请立即修改密码并检查账号安全!",
+ "我知道了", "",
+ new OnDialogClick() {
+ @Override
+ public void onCancelClick() {
+ Log.d("DINGDING", "用户点击取消");
+ }
+
+ @Override
+ public void onConfirmClick() {
+ Log.d("DINGDING", "用户点击确定");
+ ActivityManager.removeAllActivityExceptMain();
+ goTo(mContext, LoginActivity.class, null);
+ }
+ }, true);
+ Log.d("DINGDING", "BaseFragment 401弹窗显示成功");
+ } catch (Exception e) {
+ Log.e("DINGDING", "BaseFragment 401弹窗显示失败", e);
+ }
+ }
+ });
+ }
+
+ /**
+ * 权限请求结果回调
+ */
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ // 将权限结果传递给EasyPermissions处理
+ EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
+ }
+
+ /**
+ * 权限被授予时的回调
+ */
+ @Override
+ public void onPermissionsGranted(int requestCode, @NonNull List perms) {
+ // 这个方法会在子Fragment中重写,这里可以留空或者提供默认实现
+ }
+
+ /**
+ * 权限被拒绝时的回调
+ */
+ @Override
+ public void onPermissionsDenied(int requestCode, @NonNull List perms) {
+ // 这个方法会在子Fragment中重写,这里可以留空或者提供默认实现
+ }
+
+
+}
diff --git a/app/src/main/java/com/ckkj/water/base/BasePresenter.java b/app/src/main/java/com/ckkj/water/base/BasePresenter.java
new file mode 100644
index 0000000..8188ede
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/base/BasePresenter.java
@@ -0,0 +1,31 @@
+package com.ckkj.water.base;
+
+public abstract class BasePresenter implements IPresenter {
+
+ //View 显示回显的接口
+ protected V mRootView;
+
+ public BasePresenter() {
+ onStart();
+ }
+
+ @Override
+ public void onStart() {
+ }
+
+ @Override
+ public void onDestroy() {
+ this.mRootView = null;
+ }
+
+ @Override
+ public void attachView(V view) {
+ this.mRootView = view;
+ }
+
+ @Override
+ public V getView() {
+ return mRootView;
+ }
+
+}
diff --git a/app/src/main/java/com/ckkj/water/base/IPresenter.java b/app/src/main/java/com/ckkj/water/base/IPresenter.java
new file mode 100644
index 0000000..20a3bbd
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/base/IPresenter.java
@@ -0,0 +1,21 @@
+package com.ckkj.water.base;
+
+public interface IPresenter {
+
+ /**
+ * 做一些初始化操作
+ */
+ void onStart();
+
+ /**
+ * 销毁
+ */
+ void onDestroy();
+
+ /**
+ * 绑定View
+ */
+ void attachView(T view);
+
+ T getView();
+}
diff --git a/app/src/main/java/com/ckkj/water/base/IView.java b/app/src/main/java/com/ckkj/water/base/IView.java
new file mode 100644
index 0000000..0f36771
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/base/IView.java
@@ -0,0 +1,47 @@
+package com.ckkj.water.base;
+
+import com.ckkj.water.network.BaseBean;
+
+public interface IView {
+
+ /**
+ * 网络状态改变时的回调
+ * @param netModel
+ */
+ void onNetChange(int netModel);
+
+ /**
+ * 显示加载
+ */
+ void showLoading();
+
+ /**
+ * 隐藏加载
+ */
+ void hideLoading();
+
+ /**
+ * 显示信息
+ * @param message
+ */
+ void showMessage(String message);
+
+
+ /**
+ * 显示错误信息
+ * @param message
+ */
+ void showError(String message);
+
+ /**
+ * 统一消息处理
+ * @param e
+ */
+ void handleError(Exception e);
+
+ /**
+ * 登录失效
+ * @param
+ */
+ void logOut(BaseBean bean);
+}
diff --git a/app/src/main/java/com/ckkj/water/base/entity/MineUserInfoBean.java b/app/src/main/java/com/ckkj/water/base/entity/MineUserInfoBean.java
new file mode 100644
index 0000000..4a05672
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/base/entity/MineUserInfoBean.java
@@ -0,0 +1,51 @@
+package com.ckkj.water.base.entity;
+
+import java.io.Serializable;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/28
+ * @description TODO: 描述该类的作用
+ */
+
+public class MineUserInfoBean implements Serializable {
+ private UserInfoEntity userInfo;
+ private boolean hasEightCharacters;
+ private String constellation;
+ private String birthTime;
+
+
+
+ public String getConstellation() {
+
+ return constellation;
+ }
+
+ public void setConstellation(String constellation) {
+ this.constellation = constellation;
+ }
+
+ public String getBirthTime() {
+ return birthTime;
+ }
+
+ public void setBirthTime(String birthTime) {
+ this.birthTime = birthTime;
+ }
+
+ public UserInfoEntity getUserInfo() {
+ return userInfo;
+ }
+
+ public void setUserInfo(UserInfoEntity userInfo) {
+ this.userInfo = userInfo;
+ }
+
+ public boolean isHasEightCharacters() {
+ return hasEightCharacters;
+ }
+
+ public void setHasEightCharacters(boolean hasEightCharacters) {
+ this.hasEightCharacters = hasEightCharacters;
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/base/entity/UserInfoEntity.java b/app/src/main/java/com/ckkj/water/base/entity/UserInfoEntity.java
new file mode 100644
index 0000000..3b78506
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/base/entity/UserInfoEntity.java
@@ -0,0 +1,279 @@
+package com.ckkj.water.base.entity;
+
+import java.io.Serializable;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/21
+ * @description TODO: 描述该类的作用
+ */
+
+public class UserInfoEntity implements Serializable {
+ private String name;
+ private String gender;
+ private String genderText;
+ private String birthType;
+ private String birthTypeText;
+ private String birthTime;
+ private String birthPlace;
+ private String currentPlace;
+ private String trueSolarTime;
+ private String longitude;
+ private String latitude;
+ private String constellation;
+ private BaziSizhu baziSizhu;
+ private long userId;
+ private String mobile;
+ private String username;
+ private String nickname;
+ private String email;
+ private int status;
+ private String registerTime;
+ private String updateTime;
+ private int membershipStatus;
+ private String membershipExpireTime;
+ private String inviteCode;
+ private String userRole;
+ private String avatar;
+
+
+ public String getAvatar() {
+ return avatar;
+ }
+
+ public void setAvatar(String avatar) {
+ this.avatar = avatar;
+ }
+
+ public long getUserId() {
+ return userId;
+ }
+
+ public void setUserId(long userId) {
+ this.userId = userId;
+ }
+
+ public String getMobile() {
+ return mobile;
+ }
+
+ public void setMobile(String mobile) {
+ this.mobile = mobile;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getNickname() {
+ return nickname;
+ }
+
+ public void setNickname(String nickname) {
+ this.nickname = nickname;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public int getStatus() {
+ return status;
+ }
+
+ public void setStatus(int status) {
+ this.status = status;
+ }
+
+ public String getRegisterTime() {
+ return registerTime;
+ }
+
+ public void setRegisterTime(String registerTime) {
+ this.registerTime = registerTime;
+ }
+
+ public String getUpdateTime() {
+ return updateTime;
+ }
+
+ public void setUpdateTime(String updateTime) {
+ this.updateTime = updateTime;
+ }
+
+ public int getMembershipStatus() {
+ return membershipStatus;
+ }
+
+ public void setMembershipStatus(int membershipStatus) {
+ this.membershipStatus = membershipStatus;
+ }
+
+ public String getMembershipExpireTime() {
+ return membershipExpireTime;
+ }
+
+ public void setMembershipExpireTime(String membershipExpireTime) {
+ this.membershipExpireTime = membershipExpireTime;
+ }
+
+ public String getInviteCode() {
+ return inviteCode;
+ }
+
+ public void setInviteCode(String inviteCode) {
+ this.inviteCode = inviteCode;
+ }
+
+ public String getUserRole() {
+ return userRole;
+ }
+
+ public void setUserRole(String userRole) {
+ this.userRole = userRole;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+ public String getName() {
+ return name;
+ }
+
+ public void setGender(String gender) {
+ this.gender = gender;
+ }
+ public String getGender() {
+ return gender;
+ }
+
+ public void setGenderText(String genderText) {
+ this.genderText = genderText;
+ }
+ public String getGenderText() {
+ return genderText;
+ }
+
+ public void setBirthType(String birthType) {
+ this.birthType = birthType;
+ }
+ public String getBirthType() {
+ return birthType;
+ }
+
+ public void setBirthTypeText(String birthTypeText) {
+ this.birthTypeText = birthTypeText;
+ }
+ public String getBirthTypeText() {
+ return birthTypeText;
+ }
+
+ public void setBirthTime(String birthTime) {
+ this.birthTime = birthTime;
+ }
+ public String getBirthTime() {
+ return birthTime;
+ }
+
+ public void setBirthPlace(String birthPlace) {
+ this.birthPlace = birthPlace;
+ }
+ public String getBirthPlace() {
+ return birthPlace;
+ }
+
+ public void setCurrentPlace(String currentPlace) {
+ this.currentPlace = currentPlace;
+ }
+ public String getCurrentPlace() {
+ return currentPlace;
+ }
+
+ public void setTrueSolarTime(String trueSolarTime) {
+ this.trueSolarTime = trueSolarTime;
+ }
+ public String getTrueSolarTime() {
+ return trueSolarTime;
+ }
+
+ public void setLongitude(String longitude) {
+ this.longitude = longitude;
+ }
+ public String getLongitude() {
+ return longitude;
+ }
+
+ public void setLatitude(String latitude) {
+ this.latitude = latitude;
+ }
+ public String getLatitude() {
+ return latitude;
+ }
+
+ public void setConstellation(String constellation) {
+ this.constellation = constellation;
+ }
+ public String getConstellation() {
+ return constellation;
+ }
+
+ public void setBaziSizhu(BaziSizhu baziSizhu) {
+ this.baziSizhu = baziSizhu;
+ }
+ public BaziSizhu getBaziSizhu() {
+ return baziSizhu;
+ }
+
+ /**
+ * Copyright 2025 bejson.com
+ */
+ /**
+ * Auto-generated: 2025-07-21 17:23:17
+ *
+ * @author bejson.com (i@bejson.com)
+ * @website http://www.bejson.com/java2pojo/
+ */
+ public static class BaziSizhu implements Serializable{
+
+ private String nianzhu;
+ private String yuezhu;
+ private String rizhu;
+ private String shizhu;
+ public void setNianzhu(String nianzhu) {
+ this.nianzhu = nianzhu;
+ }
+ public String getNianzhu() {
+ return nianzhu;
+ }
+
+ public void setYuezhu(String yuezhu) {
+ this.yuezhu = yuezhu;
+ }
+ public String getYuezhu() {
+ return yuezhu;
+ }
+
+ public void setRizhu(String rizhu) {
+ this.rizhu = rizhu;
+ }
+ public String getRizhu() {
+ return rizhu;
+ }
+
+ public void setShizhu(String shizhu) {
+ this.shizhu = shizhu;
+ }
+ public String getShizhu() {
+ return shizhu;
+ }
+
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/base/inter/UploadCallback.java b/app/src/main/java/com/ckkj/water/base/inter/UploadCallback.java
new file mode 100644
index 0000000..e62fc63
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/base/inter/UploadCallback.java
@@ -0,0 +1,11 @@
+package com.ckkj.water.base.inter;
+
+import com.alibaba.sdk.android.oss.ClientException;
+import com.alibaba.sdk.android.oss.ServiceException;
+
+import java.util.List;
+
+public interface UploadCallback {
+ void onSuccess(List urls);
+ void onFailure(ClientException clientException, ServiceException serviceException);
+}
diff --git a/app/src/main/java/com/ckkj/water/chat/ChatFragment.java b/app/src/main/java/com/ckkj/water/chat/ChatFragment.java
new file mode 100644
index 0000000..3bd95a4
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/chat/ChatFragment.java
@@ -0,0 +1,900 @@
+package com.ckkj.water.chat;
+
+import static android.app.Activity.RESULT_OK;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.Handler;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+import androidx.core.widget.NestedScrollView;
+import androidx.recyclerview.widget.GridLayoutManager;
+
+import com.chad.library.adapter.base.BaseQuickAdapter;
+import com.ckkj.water.AppConfig;
+import com.ckkj.water.EventMessage;
+import com.ckkj.water.R;
+import com.ckkj.water.base.BaseFragment;
+import com.ckkj.water.chat.adapter.ChatQuestionDetailAdapter;
+import com.ckkj.water.chat.adapter.ChatZixunAdapter;
+import com.ckkj.water.chat.entity.ChatQuestionEntity;
+import com.ckkj.water.custom.GridSpacingItemDecoration;
+import com.ckkj.water.databinding.IndexLayoutBinding;
+import com.ckkj.water.chat.adapter.IndexChatAdapter;
+import com.ckkj.water.chat.adapter.IndexOptionAdapter;
+import com.ckkj.water.chat.adapter.IndexEnterHintAdapter;
+import com.ckkj.water.chat.adapter.IndexQuickAskAdapter;
+import com.ckkj.water.chat.entity.CharHistoryEntity;
+import com.ckkj.water.chat.entity.ChatAiMsgEntity;
+import com.ckkj.water.chat.entity.ChatMultiItem;
+import com.ckkj.water.chat.entity.IndexTemplateEntity;
+import com.ckkj.water.chat.mvp.IndexContract;
+import com.ckkj.water.chat.mvp.IndexImpl;
+import com.ckkj.water.chat.mvp.IndexPresenter;
+import com.ckkj.water.dialog.ContentReportDialog;
+import com.ckkj.water.login.LoginActivity;
+import com.ckkj.water.network.BaseBean;
+import com.ckkj.water.plate.entity.BaziResultBean;
+import com.ckkj.water.plate.entity.PaipanCache;
+import com.ckkj.water.utils.click.ClickUtil;
+import com.ckkj.water.utils.manager.ColorManage;
+import com.ckkj.water.utils.show.ToastUtil;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import de.greenrobot.event.Subscribe;
+import de.greenrobot.event.ThreadMode;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/7
+ * @description TODO: 描述该类的作用
+ */
+
+public class ChatFragment extends BaseFragment implements IndexContract.IndexView {
+
+ //顶部 adapter
+ private IndexOptionAdapter mOptionAdapter;
+ private ChatZixunAdapter mZixunQuestAdapter;
+ private ChatQuestionDetailAdapter mQuesDetailAdapter;
+ //快速提问 adapter
+ private IndexQuickAskAdapter mQuickAskAdapter;
+ private List mQuickAskList = new ArrayList<>();
+
+ private List mChatMsgList = new ArrayList<>();
+ //聊天 adapter
+ private IndexChatAdapter mChatAdapter;
+ //欢迎语 adapter
+ private IndexEnterHintAdapter mHintAdapter;
+ //输入的内容
+ private String mCurrentInputContent = "";
+ //当前options 的 pos
+ private int mCurrentStreamPos = 0;
+ //模板数据 list
+ private List mIndexTemList = new ArrayList<>();
+ //输出是否完成
+ private boolean mIsStreamStop = true;
+ //当前选中 option 的 typeID
+ private String mSelectTypeID = "1";
+ //会话 ID
+ private String mSessionId = "";
+
+ // 添加流式数据累积变量
+ private StringBuilder mStreamContentBuilder = new StringBuilder();
+ private ChatAiMsgEntity mCurrentStreamEntity = null;
+
+ // 智能滚动管理
+ private boolean mIsUserScrolling = false;
+
+ // 优化防抖机制
+ private Handler mUpdateHandler = new Handler();
+ private Runnable mUpdateRunnable;
+
+ private List mQuesTypeList = new ArrayList<>();
+ private boolean mIsShowQuestions = true;
+
+ @Override
+ public void initData(Bundle state) {
+
+ mOptionAdapter = new IndexOptionAdapter(mContext, R.layout.index_device_list_item, mIndexTemList);
+ viewBinding.rvOptions.setAdapter(mOptionAdapter);
+ mQuickAskAdapter = new IndexQuickAskAdapter(mContext, R.layout.index_home_quick_ask_item, mQuickAskList);
+ viewBinding.rvQuickAsk.setAdapter(mQuickAskAdapter);
+ mChatAdapter = new IndexChatAdapter(mContext, mChatMsgList, viewBinding.rvChat);
+ viewBinding.rvChat.setAdapter(mChatAdapter);
+ //提问类型
+ GridLayoutManager manager = new GridLayoutManager(mContext, 2);
+ viewBinding.rvChatQuick.setLayoutManager(manager);
+ List subList = new ArrayList<>();
+ mQuesDetailAdapter = new ChatQuestionDetailAdapter(R.layout.chat_quest_detail_item, subList);
+ viewBinding.rvChatQuick.setAdapter(mQuesDetailAdapter);
+ viewBinding.rvChatQuick.addItemDecoration(new GridSpacingItemDecoration(2, 10, true));
+ mZixunQuestAdapter = new ChatZixunAdapter(mContext, R.layout.chat_zixun_type_item, mQuesTypeList);
+ viewBinding.rvChatType.setAdapter(mZixunQuestAdapter);
+
+ // 设置聊天RecyclerView的LayoutManager(禁用滚动)
+ androidx.recyclerview.widget.LinearLayoutManager chatLayoutManager =
+ new androidx.recyclerview.widget.LinearLayoutManager(mContext);
+ chatLayoutManager.setStackFromEnd(false);
+ viewBinding.rvChat.setLayoutManager(chatLayoutManager);
+
+ // 禁用RecyclerView的滚动,让NestedScrollView处理滚动
+ viewBinding.rvChat.setNestedScrollingEnabled(false);
+
+ mHintAdapter = new IndexEnterHintAdapter(mContext, R.layout.index_enter_hint_item, new ArrayList<>());
+ viewBinding.rvEnterHint.setAdapter(mHintAdapter);
+
+ setAdapterClick();
+
+ // 添加EditText的焦点监听器
+ viewBinding.editInput.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (hasFocus) {
+ // EditText获取焦点时,延迟滚动到底部,确保键盘弹出后再滚动
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ scrollNestedScrollViewToBottom();
+ }
+ }, 300); // 延迟300ms,等待键盘弹出
+ }
+ }
+ });
+
+ viewBinding.editInput.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (s != null) {
+ mCurrentInputContent = s.toString().trim();
+ if (TextUtils.isEmpty(s.toString().trim())) {
+ viewBinding.imgSend.setImageDrawable(ContextCompat.getDrawable(mContext, R.mipmap.icon_index_send));
+ viewBinding.editInput.setHint("请输入您的问题");
+ } else {
+ viewBinding.imgSend.setImageDrawable(ContextCompat.getDrawable(mContext, R.mipmap.icon_index_send_b));
+ }
+ }
+ }
+ });
+
+ //请求页面模板数据
+ mPresenter.getIndexTemplateDatas();
+ mPresenter.getChatTemplateDatas();
+ //组件增加点击区域
+ ClickUtil.addClickPart(viewBinding.imgCloseQuick, 30);
+ ClickUtil.addClickPart(viewBinding.imgRefresh, 30);
+ ClickUtil.addClickPart(viewBinding.imgHistory, 30);
+
+ // 设置智能滚动监听
+ setupSmartScrollListener();
+ // 设置页面点击监听,用于隐藏键盘
+ setupPageClickListener();
+ // 检查是否有传递过来的参数
+ checkAndHandleBundle();
+ }
+
+ /**
+ * 检查并处理传递过来的Bundle参数
+ */
+ private void checkAndHandleBundle() {
+ Bundle args = getArguments();
+ if (args != null) {
+ handleBirthInfoBundle(args);
+ }
+ }
+
+ /**
+ * 处理出生信息Bundle参数
+ *
+ * @param bundle 包含出生信息的Bundle
+ */
+ public void handleBirthInfoBundle(Bundle bundle) {
+ if (bundle != null) {
+ BaziResultBean baseEntity = PaipanCache.get();
+ // 获取出生信息
+ BaziResultBean.BaseInfoDTO birthInfo = baseEntity.getBaseInfo();
+ BaziResultBean.DetailInfoDTO.SizhuInfoDTO sizhuInfoDTO = baseEntity.getDetailInfo().getSizhuInfo();
+ BaziResultBean.DetailInfoDTO.DayunInfoDTO defaulLiunian = (BaziResultBean.DetailInfoDTO.DayunInfoDTO) bundle.getSerializable("defaul_liunian");
+ if (birthInfo != null) {
+ viewBinding.editInput.setText("我叫" + birthInfo.getName() + "," +
+ ((birthInfo.getSex().equals("乾造") ? "男" : "女") + "," +
+ "生于公历" + birthInfo.getGongli() + "," + "当前大运为" + defaulLiunian.getDayunStartYear() + "--" +
+ defaulLiunian.getDayunEndYear() + "," + defaulLiunian.getDayunStartGanzhi() + "大运,四柱为"
+ + sizhuInfoDTO.getYear().getTg() + sizhuInfoDTO.getYear().getDz() + "、" +
+ sizhuInfoDTO.getMonth().getTg() + sizhuInfoDTO.getMonth().getDz() + "、" +
+ sizhuInfoDTO.getDay().getTg() + sizhuInfoDTO.getDay().getDz() + "、" +
+ sizhuInfoDTO.getHour().getTg() + sizhuInfoDTO.getHour().getDz() + "," +
+ "现在需要你根据我的八字,给我具体分析一下我的事业、感情、健康、财富、格局、喜用神"));
+ }
+ }
+ }
+
+ /**
+ * 当Fragment参数更新时调用(用于处理onNewIntent的情况)
+ */
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // 检查是否有新的参数需要处理
+ Bundle args = getArguments();
+ if (args != null && args.getBoolean("goto_index_fragment", false)) {
+ // 处理参数
+ handleBirthInfoBundle(args);
+
+ // 清除标记,避免重复处理
+ args.remove("goto_index_fragment");
+ }
+ }
+
+ @Subscribe(threadMode = ThreadMode.MainThread)
+ public void onGetMessage(EventMessage message) {
+ //用户添加地址成功
+ if (message.getEventStrKey().equals(EventMessage.HISTORY_SESSIONID_DELETE_SUCCESS)) {
+ if (message.getMsg().equals(mSessionId)) {
+// viewBinding.linearOptionHint.setVisibility(View.VISIBLE);
+ mChatMsgList.clear();
+ mChatAdapter.setNewData(mChatMsgList);
+ mSessionId = "";
+ }
+ }
+ }
+
+
+ /**
+ * 设置页面点击监听器
+ */
+ private void setupPageClickListener() {
+ viewBinding.rvChat.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ // 当用户触摸NestedScrollView时,隐藏键盘并清除焦点
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ hideKeyboardAndClearFocus();
+ }
+ return false;
+ }
+ });
+ }
+
+ /**
+ * 隐藏键盘并清除EditText焦点
+ */
+ private void hideKeyboardAndClearFocus() {
+ // 清除EditText焦点
+ viewBinding.editInput.clearFocus();
+
+ // 隐藏键盘
+ InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
+ if (imm != null) {
+ imm.hideSoftInputFromWindow(viewBinding.editInput.getWindowToken(), 0);
+ }
+ }
+
+ /**
+ * 滚动NestedScrollView到底部
+ */
+ private void scrollNestedScrollViewToBottom() {
+ if (viewBinding.nestedScrollChat != null) {
+ // 获取NestedScrollView的子视图(RecyclerView)
+ View childView = viewBinding.nestedScrollChat.getChildAt(0);
+ if (childView != null) {
+ // 滚动到底部
+ viewBinding.nestedScrollChat.smoothScrollTo(0, childView.getHeight());
+ }
+ }
+ }
+
+ /**
+ * 设置智能滚动监听器(修改为监听NestedScrollView)
+ */
+ private void setupSmartScrollListener() {
+ // 监听NestedScrollView的滚动
+ viewBinding.nestedScrollChat.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
+ @Override
+ public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
+ // 检测用户是否向上滑动
+ if (scrollY < oldScrollY) {
+ // 用户向上滑动
+ mIsUserScrolling = true;
+ } else if (scrollY > oldScrollY) {
+ // 用户向下滑动
+ mIsUserScrolling = true;
+ }
+
+ // 检查是否滚动到底部
+ if (scrollY >= (v.getChildAt(0).getHeight() - v.getHeight())) {
+ // 滚动到底部,恢复自动滚动
+ mIsUserScrolling = false;
+ }
+ }
+ });
+ }
+
+
+ private void setAdapterClick() {
+ //顶部的选项 adapter 点击事件
+ mOptionAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
+ @Override
+ public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
+ if (mCurrentStreamPos != position) {
+ if (mIsStreamStop) {
+ viewBinding.rlQuickAsk.setVisibility(View.GONE);
+ mOptionAdapter.setSelectPos(position);
+ setOptionSelStyle(position);
+ mCurrentStreamPos = position;
+ mSessionId = "";
+ mSelectTypeID = mIndexTemList.get(position).getId();
+ } else {
+ ToastUtil.showShort(mContext, "Ai解答中,请稍后操作");
+ }
+ }
+ }
+ });
+
+ //咨询类型 adapter
+ mZixunQuestAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
+ @Override
+ public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
+ if (mIsStreamStop) {
+ mZixunQuestAdapter.setSelectPos(position);
+ mQuesDetailAdapter.setNewData(mQuesTypeList.get(position).getSubheadings());
+ } else {
+ ToastUtil.showShort(mContext, "Ai解答中,请稍后操作");
+ }
+ }
+ });
+
+ //快速提问
+ mQuesDetailAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
+ @Override
+ public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
+ if (!AppConfig.USER_LOGIN) {
+ goTo(mContext, LoginActivity.class, null);
+ return;
+ }
+ if (!mIsStreamStop) {
+ ToastUtil.showShort(mContext, "Ai解答中,请稍后操作");
+ return;
+ }
+ String quickAskContent = mQuesTypeList.get(mZixunQuestAdapter.getmSelPos()).
+ getSubheadings().get(position).getContent();
+ sendMsgToAi(quickAskContent);
+ }
+ });
+
+ //流式输出,是否完成
+ mChatAdapter.setOnStreamCompleteListener(new IndexChatAdapter.OnStreamCompleteListener() {
+ @Override
+ //此时已经流式输出完成
+ public void onStreamComplete(int position) {
+ mCurrentStreamPos = position;
+ viewBinding.imgSend.setVisibility(View.VISIBLE);
+ viewBinding.imgStop.setVisibility(View.GONE);
+ mIsStreamStop = true;
+ }
+
+ @Override
+ public void onStreamStop() {
+ viewBinding.imgSend.setVisibility(View.VISIBLE);
+ viewBinding.imgStop.setVisibility(View.GONE);
+ mIsStreamStop = true;
+ }
+ });
+ mChatAdapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
+ @Override
+ public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {
+ switch (view.getId()) {
+ case R.id.img_jb:
+ ContentReportDialog cancelDialog = new ContentReportDialog(mContext, R.layout.dialog_report_reason, "");
+ cancelDialog.show();
+ cancelDialog.setOnDialogListener(new ContentReportDialog.DialogListener() {
+ @Override
+ public void callBackDatas(String reason) {
+ showLoadingDialog();
+ //举报
+ mPresenter.commitReport(reason);
+ }
+ });
+ break;
+ }
+ }
+ });
+
+ // 设置长按菜单监听器
+ mChatAdapter.setOnItemLongClickMenuListener(new IndexChatAdapter.OnItemLongClickMenuListener() {
+ @Override
+ public void onCopyClick(ChatMultiItem item, int position) {
+ // 处理复制操作
+ String content = item.getMessage().getContent();
+ if (!TextUtils.isEmpty(content)) {
+ ClipboardManager clipboard = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
+ ClipData clip = ClipData.newPlainText("chat_content", content);
+ clipboard.setPrimaryClip(clip);
+ ToastUtil.showShort(mContext, "内容已复制");
+ }
+ }
+
+ @Override
+ public void onReportClick(ChatMultiItem item, int position) {
+ // 处理举报操作
+ ContentReportDialog cancelDialog = new ContentReportDialog(mContext, R.layout.dialog_report_reason, "");
+ cancelDialog.show();
+ cancelDialog.setOnDialogListener(new ContentReportDialog.DialogListener() {
+ @Override
+ public void callBackDatas(String reason) {
+ showLoadingDialog();
+ //举报
+ mPresenter.commitReport(reason);
+ }
+ });
+ }
+ });
+
+ // 添加长按监听器
+// mChatAdapter.setOnItemChildLongClickListener(new BaseQuickAdapter.OnItemChildLongClickListener() {
+// @Override
+// public boolean onItemChildLongClick(BaseQuickAdapter adapter, View view, int position) {
+// switch (view.getId()) {
+// case R.id.tv_text: // 文本内容的view id
+// XPopup.fixLongClick(view); // 能保证弹窗弹出后,下层的View无法滑动
+//
+// // 获取当前消息内容
+// ChatMultiItem item = mChatMsgList.get(position);
+// String content = item.getMessage().getContent();
+//
+// // 使用链式调用,确保atView在asAttachList之前
+// new XPopup.Builder(getContext())
+// .hasShadowBg(false)
+// .atView(view) // 使用atView而不是watchView
+// .asAttachList(new String[]{"复制", "举报"},
+// null,
+// new OnSelectListener() {
+// @Override
+// public void onSelect(int selectPosition, String text) {
+// switch (selectPosition) {
+// case 0: // 复制
+// if (!TextUtils.isEmpty(content)) {
+// ClipboardManager clipboard = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
+// ClipData clip = ClipData.newPlainText("chat_content", content);
+// clipboard.setPrimaryClip(clip);
+// ToastUtil.showShort(mContext, "内容已复制");
+// }
+// break;
+// case 1: // 举报
+// ContentReportDialog cancelDialog = new ContentReportDialog(mContext, R.layout.dialog_report_reason, "");
+// cancelDialog.show();
+// cancelDialog.setOnDialogListener(new ContentReportDialog.DialogListener() {
+// @Override
+// public void callBackDatas(String reason) {
+// showLoadingDialog();
+// //举报
+// mPresenter.commitReport(reason);
+// }
+// });
+// break;
+// }
+// }
+// })
+// .show();
+// return true;
+// }
+// return false;
+// }
+// });
+ }
+
+
+ /**
+ * 停止输出
+ */
+ private void stopSteamOutput() {
+ // 停止当前网络请求
+ mPresenter.stopCurrentRequest();
+
+ // 获取当前正在流式输出的位置
+ int currentPosition = mChatMsgList.size() - 1;
+
+ // 标记当前消息为被停止状态
+ mChatAdapter.markAsStopped(currentPosition);
+
+ // 停止适配器中的流式输出
+ mChatAdapter.setStreamerStop(currentPosition);
+
+ // 清除当前流式输出位置
+ mChatAdapter.setCurrentStreamingPosition(-1);
+
+ // 强制更新当前item以显示停止标识
+ mChatAdapter.notifyItemChanged(currentPosition);
+
+ // 恢复UI状态
+ viewBinding.imgSend.setVisibility(View.VISIBLE);
+ viewBinding.imgStop.setVisibility(View.GONE);
+ mIsStreamStop = true;
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (data != null) {
+ if (resultCode == RESULT_OK) {
+ switch (requestCode) {
+ case REQUEST_EVENT_CODE_10001:
+ String sessionId = data.getStringExtra("session_id");
+ String numerologyType = data.getStringExtra("numerology_type");
+ int showSelPos = -1;
+ for (int i = 0; i < mIndexTemList.size(); i++) {
+ if (numerologyType.equals(mIndexTemList.get(i).getId())) {
+ showSelPos = i;
+ }
+ }
+ if (mCurrentStreamPos != showSelPos) {
+
+ }
+ if (mIsStreamStop) {
+ viewBinding.rlQuickAsk.setVisibility(View.GONE);
+ mOptionAdapter.setSelectPos(showSelPos);
+ setOptionSelStyle(showSelPos);
+ mCurrentStreamPos = showSelPos;
+ mSelectTypeID = mIndexTemList.get(showSelPos).getId();
+ if (!TextUtils.isEmpty(sessionId)) {
+ mPresenter.getChatDetailByID(sessionId);
+ }
+ } else {
+ ToastUtil.showShort(mContext, "Ai解答中,请稍后操作");
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * 发送消息给 AI
+ */
+ private void sendMsgToAi(String senMsg) {
+ if (!AppConfig.USER_LOGIN) {
+ goTo(mContext, LoginActivity.class, null);
+ return;
+ }
+ viewBinding.cardQuestion.setVisibility(View.GONE);
+ viewBinding.imgClose.setImageDrawable(ContextCompat.getDrawable(mContext, R.mipmap.icon_chat_down));
+ mIsShowQuestions = false;
+ viewBinding.linearOptionHint.setVisibility(View.GONE);
+ // 重置滚动状态
+ mIsUserScrolling = false;
+ // 清理适配器缓存
+ mChatAdapter.clearCache();
+
+ // 创建用户消息
+ ChatAiMsgEntity chatMsgEntity = new ChatAiMsgEntity();
+ chatMsgEntity.setSessionId(mSessionId);
+ chatMsgEntity.setType(mSelectTypeID);
+ chatMsgEntity.setContent(senMsg);
+ ChatMultiItem userEntity = new ChatMultiItem(ChatMultiItem.TYPE_TEXT_RIGHT, chatMsgEntity);
+ mChatMsgList.add(userEntity);
+ mChatAdapter.notifyItemInserted(mChatMsgList.size() - 1);
+
+ // 创建AI回复消息(初始为空)
+ mCurrentStreamEntity = new ChatAiMsgEntity();
+ mCurrentStreamEntity.setSessionId(mSessionId);
+ mCurrentStreamEntity.setType(mSelectTypeID);
+ mCurrentStreamEntity.setContent(""); // 初始为空
+ ChatMultiItem aiEntity = new ChatMultiItem(ChatMultiItem.TYPE_TEXT_LEFT, mCurrentStreamEntity);
+ mChatMsgList.add(aiEntity);
+
+ // 设置当前流式输出位置
+ int aiMessagePosition = mChatMsgList.size() - 1;
+ mChatAdapter.setCurrentStreamingPosition(aiMessagePosition);
+ mChatAdapter.notifyItemInserted(aiMessagePosition);
+
+ // 重置流式数据累积器
+ mStreamContentBuilder.setLength(0);
+
+ // 清空输入框
+ viewBinding.editInput.setText("");
+
+ // 设置UI状态
+ viewBinding.imgSend.setVisibility(View.GONE);
+ viewBinding.imgStop.setVisibility(View.VISIBLE);
+
+ // 发送消息后自动滚动到底部
+ scrollToBottomAfterSend();
+
+ // 发起流式请求
+ mPresenter.talkToAi(senMsg, mSelectTypeID, mSessionId);
+ }
+
+ @Override
+ public IndexPresenter createPresenter() {
+ return new IndexPresenter(new IndexImpl());
+ }
+
+ @Override
+ public void getTalkResultSuccess(ChatAiMsgEntity bean) {
+ hideDialogLoading();
+ }
+
+ /**
+ * 首页数据获取成功
+ *
+ * @param list
+ */
+ @Override
+ public void getIndexTemplateSuccess(List list) {
+ if (list != null && list.size() != 0) {
+ mIndexTemList = list;
+ mOptionAdapter.setNewData(mIndexTemList);
+ setAdapterClick();
+ setOptionSelStyle(0);
+ mSelectTypeID = mIndexTemList.get(0).getId();
+ }
+ }
+
+ /**
+ * 切换设置显示内容
+ *
+ * @param pos
+ */
+ private void setOptionSelStyle(int pos) {
+ try {
+ mChatMsgList.clear();
+ mChatAdapter.setNewData(mChatMsgList);
+ // 清理缓存
+ mChatAdapter.clearCache();
+// viewBinding.linearOptionHint.setVisibility(View.VISIBLE);
+ IndexTemplateEntity defaulEntity = mIndexTemList.get(pos);
+ viewBinding.tvTitleExpert.setText(defaulEntity.getTitle() + "专家");
+ viewBinding.tvSelectOptionLable.setText(defaulEntity.getTitle() + "专家");
+ viewBinding.tvSelectOptionSlogan.setText(defaulEntity.getSubheading());
+ if (ColorManage.getInstance().isParsableColor(defaulEntity.getColor())) {
+ viewBinding.tvSelectOptionSlogan.setTextColor(Color.parseColor(defaulEntity.getColor()));
+ } else {
+ viewBinding.tvSelectOptionSlogan.setTextColor(ContextCompat.getColor(mContext, R.color.color_zhu));
+ }
+ //提示语
+ List hintMsgList = new ArrayList<>();
+ hintMsgList = Arrays.asList(defaulEntity.getIntermediateContent().split("#"));
+ mHintAdapter.setNewData(hintMsgList);
+ //快速提问
+ mQuickAskList.clear();
+ List quickAskList = new ArrayList<>();
+ quickAskList = Arrays.asList(defaulEntity.getQuickQuestions().split("#"));
+ for (String content : quickAskList) {
+ IndexTemplateEntity entity = new IndexTemplateEntity();
+ entity.setColor(defaulEntity.getColor());
+ entity.setBlackcolor(defaulEntity.getBlackcolor());
+ entity.setSpliteItemContent(content);
+ mQuickAskList.add(entity);
+ }
+ mQuickAskAdapter.setNewData(mQuickAskList);
+ } catch (Exception e) {
+ }
+ }
+
+ @Override
+ public void onNetChange(int netModel) {
+
+ }
+
+ @Override
+ public void showError(String message) {
+ hideDialogLoading();
+ ToastUtil.showShort(mContext, message);
+ // 流式传输完成,恢复UI状态
+ viewBinding.imgSend.setVisibility(View.VISIBLE);
+ viewBinding.imgStop.setVisibility(View.GONE);
+ mIsStreamStop = true;
+
+ // 可以在这里处理会话ID等
+ if (mCurrentStreamEntity != null && !TextUtils.isEmpty(mCurrentStreamEntity.getSessionId())) {
+ mSessionId = mCurrentStreamEntity.getSessionId();
+ }
+
+ // 清理当前流式实体
+ mCurrentStreamEntity = null;
+ mStreamContentBuilder.setLength(0);
+ }
+
+ @Override
+ public void logOut(BaseBean bean) {
+
+ }
+
+ // 实现流式消息接收回调
+ @Override
+ public void onStreamMessageReceived(String messageChunk) {
+ mIsStreamStop = false;
+ if (mCurrentStreamEntity != null) {
+ // 更新当前AI消息的内容
+ mCurrentStreamEntity.setContent(messageChunk);
+
+ // 取消之前的更新任务
+ if (mUpdateRunnable != null) {
+ mUpdateHandler.removeCallbacks(mUpdateRunnable);
+ }
+
+ mUpdateRunnable = () -> {
+ // 只更新最后一个item
+ int lastPosition = mChatMsgList.size() - 1;
+ if (lastPosition >= 0) {
+ // 使用更轻量的更新方式,避免重新布局
+ mChatAdapter.notifyItemChanged(lastPosition, "content_only");
+
+ // 流式输出时,如果用户没有手动滑动,则自动滚动NestedScrollView
+ if (!mIsUserScrolling) {
+ smoothScrollNestedScrollView();
+ }
+ }
+ };
+
+ // 增加延迟时间,减少更新频率
+ mUpdateHandler.postDelayed(mUpdateRunnable, 100);
+ }
+ }
+
+
+ // 实现流式完成回调
+ @Override
+ public void onStreamComplete(String complateMsg) {
+ // 流式传输完成,恢复UI状态
+ viewBinding.imgSend.setVisibility(View.VISIBLE);
+ viewBinding.imgStop.setVisibility(View.GONE);
+ mIsStreamStop = true;
+
+ // 清除当前流式输出位置
+ mChatAdapter.setCurrentStreamingPosition(-1);
+
+ // 注意:这里不标记为停止状态,因为这是正常完成
+
+ // 可以在这里处理会话ID等
+ if (mCurrentStreamEntity != null && !TextUtils.isEmpty(mCurrentStreamEntity.getSessionId())) {
+ mSessionId = mCurrentStreamEntity.getSessionId();
+ }
+
+ // 清理当前流式实体
+ mCurrentStreamEntity = null;
+ mStreamContentBuilder.setLength(0);
+ // 假设你最后一个消息是流式的 placeholder,现在替换它
+ ChatMultiItem lastMessage = mChatMsgList.get(mChatMsgList.size() - 1);
+ lastMessage.getMessage().setContent(complateMsg); // 你可以解析 Markdown 变 PlainText,或保留 Markdown 渲染
+// mChatAdapter.notifyItemChanged(mChatMsgList.size() - 1); // 更新 UI
+ int lastPos = mChatAdapter.getItemCount() - 1;
+ mChatAdapter.updateTipsVisibility(lastPos);
+ }
+
+
+ /**
+ * 根据会话 id 查询对话详情
+ *
+ * @param list
+ */
+ @Override
+ public void getChatDetailSuccess(List list) {
+ mChatMsgList.clear();
+ if (list != null && list.size() != 0) {
+ mSessionId = list.get(0).getSessionId();
+ }
+ for (ChatAiMsgEntity entity : list) {
+ ChatMultiItem item;
+ if (entity.getRole().equals("user")) {
+ item = new ChatMultiItem(ChatMultiItem.TYPE_TEXT_RIGHT, entity);
+ } else {
+ item = new ChatMultiItem(ChatMultiItem.TYPE_TEXT_LEFT, entity);
+ }
+ mChatMsgList.add(item);
+ }
+ mChatAdapter.setNewData(mChatMsgList);
+ viewBinding.linearOptionHint.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void loginLost() {
+ if (isFragmentVisible()) {
+ handleLoginLost();
+ }
+ stopSteamOutput();
+ if (mChatAdapter != null) {
+ mChatAdapter.remove(mChatMsgList.size() - 1);
+ }
+ }
+
+ /**
+ * 获取咨询模板成功
+ *
+ * @param list
+ */
+ @Override
+ public void getChatZixunSuccess(List list) {
+ this.mQuesTypeList = list;
+ if (list != null) {
+ mZixunQuestAdapter.setNewData(list);
+ mQuesDetailAdapter.setNewData(list.get(0).getSubheadings());
+ setAdapterClick();
+ }
+ }
+
+ /**
+ * 举报成功
+ * @param bean
+ */
+ @Override
+ public void commitReportSuccess(BaseBean bean) {
+ ToastUtil.showShort(mContext, "星语明灯感谢您的反馈");
+ hideDialogLoading();
+ }
+
+ // 实现sessionId接收回调
+ @Override
+ public void onSessionReceived(String sessionId) {
+ // 保存sessionId
+ mSessionId = sessionId;
+ }
+
+ @Override
+ public void getHistoryDataSuccess(CharHistoryEntity entity) {
+
+ }
+
+ @Override
+ public void deleteHistorySuccess(BaseBean bean, int delPos) {
+
+ }
+
+
+ /**
+ * 平滑滚动NestedScrollView到底部
+ */
+ private void smoothScrollNestedScrollView() {
+ if (mChatAdapter != null && mChatAdapter.getItemCount() > 0) {
+ // 直接滚动NestedScrollView到底部
+ viewBinding.nestedScrollChat.post(() -> {
+ viewBinding.nestedScrollChat.fullScroll(View.FOCUS_DOWN);
+ });
+ }
+ }
+
+
+ /**
+ * 发送消息后滚动到底部
+ */
+ private void scrollToBottomAfterSend() {
+ new Handler().postDelayed(() -> {
+ if (isFragmentVisible()) {
+ viewBinding.nestedScrollChat.fullScroll(View.FOCUS_DOWN);
+ }
+ }, 100);
+ }
+
+ @Override
+ public void onPermissionsGranted(int requestCode, @NonNull List perms) {
+
+ }
+
+ @Override
+ public void onPermissionsDenied(int requestCode, @NonNull List perms) {
+
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/chat/ChatHistoryActivity.java b/app/src/main/java/com/ckkj/water/chat/ChatHistoryActivity.java
new file mode 100644
index 0000000..0e1108f
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/chat/ChatHistoryActivity.java
@@ -0,0 +1,225 @@
+package com.ckkj.water.chat;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+
+import com.chad.library.adapter.base.BaseQuickAdapter;
+import com.gyf.immersionbar.ImmersionBar;
+import com.ckkj.water.chat.entity.ChatQuestionEntity;
+import com.scwang.smart.refresh.footer.ClassicsFooter;
+import com.scwang.smart.refresh.layout.api.RefreshHeader;
+import com.ckkj.water.R;
+import com.ckkj.water.base.BaseActivity;
+import com.ckkj.water.custom.view.CustomRefreshHeader;
+import com.ckkj.water.databinding.IndexHistoryLayoutBinding;
+import com.ckkj.water.chat.adapter.ChatHistoryAdapter;
+import com.ckkj.water.chat.entity.CharHistoryEntity;
+import com.ckkj.water.chat.entity.ChatAiMsgEntity;
+import com.ckkj.water.chat.entity.IndexTemplateEntity;
+import com.ckkj.water.chat.mvp.IndexContract;
+import com.ckkj.water.chat.mvp.IndexImpl;
+import com.ckkj.water.chat.mvp.IndexPresenter;
+import com.ckkj.water.network.BaseBean;
+import com.ckkj.water.utils.show.ToastUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/11
+ * @description 历史记录
+ */
+
+public class ChatHistoryActivity extends BaseActivity
+ implements IndexContract.IndexView {
+ private int mCurrentPage = 1;
+ private ChatHistoryAdapter mAdapter;
+ //历史记录
+ private List mChatList = new ArrayList<>();
+
+ @Override
+ public IndexPresenter createPresenter() {
+ return new IndexPresenter(new IndexImpl());
+ }
+
+ @Override
+ public int getLayoutResId(Bundle savedInstanceState) {
+ return 0;
+ }
+
+ @Override
+ public void initData(Bundle savedInstanceState) {
+ super.initData(savedInstanceState);
+ ImmersionBar.with(this)
+ .statusBarColor(R.color.white)
+ .statusBarDarkFont(true)
+ .titleBar(viewBinding.rlTitle) // 替换为你的标题栏View
+ .keyboardEnable(true)
+ .navigationBarDarkIcon(true)
+ .init();
+ viewBinding.includedTitle.tvTitle.setText("历史记录");
+ viewBinding.includedTitle.imgBack.setOnClickListener(c ->finish());
+ mAdapter = new ChatHistoryAdapter(mContext, R.layout.history_item_layout, mChatList);
+ viewBinding.rvData.setAdapter(mAdapter);
+ setAdapterClick();
+ // 设置自定义刷新头
+ RefreshHeader header = new CustomRefreshHeader(mContext);
+ viewBinding.refresh.setRefreshHeader(header);
+ viewBinding.refresh.setRefreshFooter(new ClassicsFooter(mContext));
+ viewBinding.refresh.setOnRefreshListener(layout -> {
+ mCurrentPage = 1;
+ getHistoryDatas();
+ });
+ viewBinding.refresh.setOnLoadMoreListener(layout -> {
+ mCurrentPage++;
+ getHistoryDatas();
+ });
+ showLoadingDialog();
+ getHistoryDatas();
+ }
+
+ private void setAdapterClick() {
+ mAdapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
+ @Override
+ public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {
+ switch (view.getId()){
+ case R.id.img_delete:
+ showLoadingDialog();
+ mPresenter.deleteHistoryBySessionID(mChatList.get(position).getSessionId(),position);
+ break;
+ }
+ }
+ });
+ mAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
+ @Override
+ public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
+ Intent intent = new Intent();
+ intent.putExtra("session_id",mChatList.get(position).getSessionId());
+ intent.putExtra("numerology_type",mChatList.get(position).getNumerologyType());
+ setResult(RESULT_OK,intent);
+ finish();
+ }
+ });
+
+ }
+
+
+ private void getHistoryDatas() {
+ mPresenter.getHistoryDatas(mCurrentPage);
+ }
+
+ /**
+ * 查询历史数据成功 g
+ *
+ * @param entity
+ */
+ @Override
+ public void getHistoryDataSuccess(CharHistoryEntity entity) {
+ hideDialogLoading();
+ int totalPage = entity.getPages();
+ viewBinding.refresh.finishRefresh(300);
+ viewBinding.refresh.finishLoadMore(300);
+ List list = entity.getSessions();
+ if (mCurrentPage == 1) {
+ mChatList.clear(); // 只在刷新时清除
+ mChatList = list;
+ } else {
+ mChatList.addAll(list);
+ }
+ mAdapter.setNewData(mChatList);
+ // 4. 判断是否还有更多数据
+ if (totalPage == mCurrentPage || list == null || list.size() == 0) {
+ // ✅ 告诉 SmartRefreshLayout 没有更多数据了
+ viewBinding.refresh.setNoMoreData(true);
+ viewBinding.relNoMore.setVisibility(View.VISIBLE); // 自定义底部提示“暂无更多数据”
+ } else {
+ viewBinding.refresh.setNoMoreData(false); // 重置状态
+ viewBinding.relNoMore.setVisibility(View.GONE);
+ }
+
+ // 5. 控制空布局和内容显示
+ if (mAdapter.getItemCount() != 0) {
+ viewBinding.refresh.setVisibility(View.VISIBLE);
+ viewBinding.relNoresult.setVisibility(View.GONE);
+ } else {
+ viewBinding.refresh.setVisibility(View.GONE);
+ viewBinding.relNoresult.setVisibility(View.VISIBLE);
+ }
+ }
+
+ /**
+ * 删除历史会话记录成功
+ * @param bean
+ */
+ @Override
+ public void deleteHistorySuccess(BaseBean bean, int delPos) {
+ hideDialogLoading();
+ mAdapter.remove(delPos);
+ if(mAdapter.getItemCount() == 0){
+ viewBinding.refresh.setVisibility(View.GONE);
+ viewBinding.relNoresult.setVisibility(View.VISIBLE);
+ viewBinding.relNoMore.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public void getChatDetailSuccess(List list) {
+
+ }
+
+ @Override
+ public void loginLost() {
+
+ }
+
+ @Override
+ public void getChatZixunSuccess(List list) {
+
+ }
+
+ @Override
+ public void commitReportSuccess(BaseBean bean) {
+
+ }
+
+ @Override
+ public void getTalkResultSuccess(ChatAiMsgEntity bean) {
+
+ }
+
+ @Override
+ public void getIndexTemplateSuccess(List list) {
+
+ }
+
+ @Override
+ public void onStreamMessageReceived(String messageChunk) {
+
+ }
+
+ @Override
+ public void onStreamComplete(String complateMsg) {
+
+ }
+
+ @Override
+ public void onSessionReceived(String sessionId) {
+
+ }
+
+
+ @Override
+ public void showError(String message) {
+ viewBinding.refresh.finishRefresh(500);
+ viewBinding.refresh.finishLoadMore(200);
+ ToastUtil.showShort(mContext, message);
+ hideDialogLoading();
+ }
+
+ @Override
+ public void logOut(BaseBean bean) {
+
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/chat/adapter/ChatHistoryAdapter.java b/app/src/main/java/com/ckkj/water/chat/adapter/ChatHistoryAdapter.java
new file mode 100644
index 0000000..30ff612
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/chat/adapter/ChatHistoryAdapter.java
@@ -0,0 +1,75 @@
+package com.ckkj.water.chat.adapter;
+
+import android.content.Context;
+import android.widget.ImageView;
+
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+
+import com.chad.library.adapter.base.BaseQuickAdapter;
+import com.chad.library.adapter.base.BaseViewHolder;
+import com.ckkj.water.R;
+import com.ckkj.water.chat.entity.CharHistoryEntity;
+import com.ckkj.water.utils.time.ZYTimeUtils;
+
+import java.util.List;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/11
+ * @description TODO: 描述该类的作用
+ */
+
+public class ChatHistoryAdapter extends BaseQuickAdapter {
+ private Context mContext;
+
+ public ChatHistoryAdapter(Context context,int layoutResId, @Nullable List data) {
+ super(layoutResId, data);
+ this.mContext = context;
+ }
+
+ @Override
+ protected void convert(BaseViewHolder helper, CharHistoryEntity.Sessions item) {
+ ImageView imgType = helper.getView(R.id.img_logo);
+ switch (item.getNumerologyType()){
+ case "1"://八字命理
+ imgType.setImageDrawable(ContextCompat.getDrawable(mContext,R.mipmap.icon_index_bzml));
+ helper.setBackgroundRes(R.id.tv_type_name,R.drawable.shape_history_type_bzml_bg);
+ helper.setTextColor(R.id.tv_type_name,ContextCompat.getColor(mContext,R.color.color_319EF4));
+ break;
+ case "2"://紫微斗数
+ imgType.setImageDrawable(ContextCompat.getDrawable(mContext,R.mipmap. icon_index_zwds));
+ helper.setBackgroundRes(R.id.tv_type_name,R.drawable.shape_history_type_zwds_bg);
+ helper.setTextColor(R.id.tv_type_name,ContextCompat.getColor(mContext,R.color.color_B85CE6));
+ break;
+ case "3"://风水堪舆
+ imgType.setImageDrawable(ContextCompat.getDrawable(mContext,R.mipmap.icon_index_fsky));
+ helper.setBackgroundRes(R.id.tv_type_name,R.drawable.shape_history_type_fsky_bg);
+ helper.setTextColor(R.id.tv_type_name,ContextCompat.getColor(mContext,R.color.color_24B326));
+ break;
+ case "6"://奇门遁甲
+ imgType.setImageDrawable(ContextCompat.getDrawable(mContext,R.mipmap.icon_index_qmdj));
+ helper.setBackgroundRes(R.id.tv_type_name,R.drawable.shape_history_type_qmdj_bg);
+ helper.setTextColor(R.id.tv_type_name,ContextCompat.getColor(mContext,R.color.color_2A5CAA));
+ break;
+ case "10"://五行起名
+ imgType.setImageDrawable(ContextCompat.getDrawable(mContext,R.mipmap. icon_index_wxqm));
+ helper.setBackgroundRes(R.id.tv_type_name,R.drawable.shape_history_type_wxqm_bg);
+ helper.setTextColor(R.id.tv_type_name,ContextCompat.getColor(mContext,R.color.color_E69900));
+ break;
+ default:
+ imgType.setImageDrawable(ContextCompat.getDrawable(mContext,R.mipmap.icon_index_bzml));
+ helper.setBackgroundRes(R.id.tv_type_name,R.drawable.shape_history_type_bzml_bg);
+ helper.setTextColor(R.id.tv_type_name,ContextCompat.getColor(mContext,R.color.color_319EF4));
+ break;
+ }
+ helper.setText(R.id.tv_type_content,item.getFirstUserMessage());
+ helper.setText(R.id.tv_type_name,item.getSessionName());
+ String chatTime = ZYTimeUtils.formatDateOrOneYearAgo(item.getCreateTime());
+ helper.setText(R.id.tv_time,chatTime);
+ helper.addOnClickListener(R.id.img_delete);
+// helper.addOnClickListener(R.id.rl_title);
+// helper.addOnClickListener(R.id.tv_type_content);
+// helper.addOnClickListener(R.id.img_logo);
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/chat/adapter/ChatQuestionDetailAdapter.java b/app/src/main/java/com/ckkj/water/chat/adapter/ChatQuestionDetailAdapter.java
new file mode 100644
index 0000000..b52bc84
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/chat/adapter/ChatQuestionDetailAdapter.java
@@ -0,0 +1,29 @@
+package com.ckkj.water.chat.adapter;
+
+import androidx.annotation.Nullable;
+
+import com.chad.library.adapter.base.BaseQuickAdapter;
+import com.chad.library.adapter.base.BaseViewHolder;
+import com.ckkj.water.R;
+import com.ckkj.water.chat.entity.ChatQuestionEntity;
+
+import java.util.List;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/8/19
+ * @description TODO: 描述该类的作用
+ */
+
+public class ChatQuestionDetailAdapter extends BaseQuickAdapter {
+
+ public ChatQuestionDetailAdapter(int layoutResId, @Nullable List data) {
+ super(layoutResId, data);
+ }
+
+ @Override
+ protected void convert(BaseViewHolder helper, ChatQuestionEntity.SubheadingsDTO item) {
+ helper.setText(R.id.tv_title,item.getTitle());
+ helper.setText(R.id.tv_content,item.getContent());
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/chat/adapter/ChatZixunAdapter.java b/app/src/main/java/com/ckkj/water/chat/adapter/ChatZixunAdapter.java
new file mode 100644
index 0000000..6e27721
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/chat/adapter/ChatZixunAdapter.java
@@ -0,0 +1,49 @@
+package com.ckkj.water.chat.adapter;
+
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+
+import com.chad.library.adapter.base.BaseQuickAdapter;
+import com.chad.library.adapter.base.BaseViewHolder;
+import com.ckkj.water.R;
+import com.ckkj.water.chat.entity.ChatQuestionEntity;
+
+import java.util.List;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/7
+ * @description TODO: 描述该类的作用
+ */
+
+public class ChatZixunAdapter extends BaseQuickAdapter {
+ private Context mContext;
+ private int mSelPos = 0;
+
+ public ChatZixunAdapter(Context context, int layoutResId, @Nullable List data) {
+ super(layoutResId, data);
+ this.mContext = context;
+ }
+
+ @Override
+ protected void convert(BaseViewHolder helper, ChatQuestionEntity item) {
+ helper.setText(R.id.tv_content,item.getTeb());
+ if(helper.getLayoutPosition() == mSelPos){
+ helper.setTextColor(R.id.tv_content,mContext.getColor(R.color.color_zhu));
+ helper.setBackgroundRes(R.id.tv_content, R.drawable.shape_chat_zixun_type);
+ }else{
+ helper.setTextColor(R.id.tv_content,mContext.getColor(R.color.color_7A8699));
+ helper.setBackgroundRes(R.id.tv_content, R.drawable.shape_trans_touming);
+ }
+ }
+
+ public void setSelectPos(int position) {
+ this.mSelPos = position;
+ notifyDataSetChanged();
+ }
+
+ public int getmSelPos() {
+ return mSelPos;
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/chat/adapter/IndexChatAdapter.java b/app/src/main/java/com/ckkj/water/chat/adapter/IndexChatAdapter.java
new file mode 100644
index 0000000..64955d1
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/chat/adapter/IndexChatAdapter.java
@@ -0,0 +1,244 @@
+package com.ckkj.water.chat.adapter;
+
+import android.content.Context;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.chad.library.adapter.base.BaseMultiItemQuickAdapter;
+import com.chad.library.adapter.base.BaseViewHolder;
+import com.ckkj.water.R;
+import com.ckkj.water.chat.entity.ChatAiMsgEntity;
+import com.ckkj.water.chat.entity.ChatMultiItem;
+import com.ckkj.water.utils.show.ToastUtil;
+import com.ckkj.water.utils.stream.MarkdownStreamer;
+import com.lxj.xpopup.XPopup;
+import com.lxj.xpopup.interfaces.OnSelectListener;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import io.noties.markwon.Markwon;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/7
+ * @description TODO: 描述该类的作用
+ */
+
+public class IndexChatAdapter extends BaseMultiItemQuickAdapter {
+ private Context mContext;
+ private RecyclerView recyclerView;
+ private OnStreamCompleteListener streamCompleteListener;
+ //convert() 中每次绑定 item 时缓存 MarkdownStreamer 实例
+ private final Map streamerMap = new HashMap<>();
+
+ // 新增:Markdown 解析器
+ private Markwon markwon;
+
+ // 新增:当前正在流式输出的位置
+ private int mCurrentStreamingPosition = -1;
+ // 用于标记哪个 position 需要显示 llTips
+ // 在 IndexChatAdapter 类中新增字段
+ private Integer pendingTipsCheckPos = null;
+ // 新增:记录被停止的位置
+ private final Map stoppedPositions = new HashMap<>();
+
+ public IndexChatAdapter(Context context, List data, RecyclerView recyclerView) {
+ super(data);
+ this.mContext = context;
+ this.recyclerView = recyclerView;
+ this.markwon = Markwon.create(context);
+ addItemType(ChatMultiItem.TYPE_TEXT_LEFT, R.layout.item_text_left);
+ addItemType(ChatMultiItem.TYPE_TEXT_RIGHT, R.layout.item_text_right);
+ }
+
+ public void setStreamerStop(int position) {
+ MarkdownStreamer streamer = streamerMap.get(position);
+ if (streamer != null) {
+ streamer.stopStreaming();
+ streamerMap.remove(position);
+ }
+ if (mCurrentStreamingPosition == position) {
+ mCurrentStreamingPosition = -1;
+ }
+ }
+
+
+ /**
+ * 标记某个位置为被停止的状态
+ */
+ public void markAsStopped(int position) {
+ stoppedPositions.put(position, true);
+ // 通知该位置更新显示
+ notifyItemChanged(position);
+ }
+
+ public interface OnStreamCompleteListener {
+ void onStreamComplete(int position);
+ void onStreamStop();
+ }
+
+ public void setOnStreamCompleteListener(OnStreamCompleteListener listener) {
+ this.streamCompleteListener = listener;
+ }
+
+ // 新增:长按菜单项点击回调接口
+ public interface OnItemLongClickMenuListener {
+ void onCopyClick(ChatMultiItem item, int position);
+ void onReportClick(ChatMultiItem item, int position);
+ }
+
+ private OnItemLongClickMenuListener mLongClickMenuListener;
+
+ public void setOnItemLongClickMenuListener(OnItemLongClickMenuListener listener) {
+ this.mLongClickMenuListener = listener;
+ }
+
+ /**
+ * 设置当前正在流式输出的位置
+ */
+ public void setCurrentStreamingPosition(int position) {
+ mCurrentStreamingPosition = position;
+ }
+
+
+ /**
+ * 对外暴露的方法:检查某个 position 是否为最后一条,并据此显示/隐藏 llTips。
+ * 仅记录“待检查的位置”并触发该 position 的刷新;实际显示逻辑在 convert() 末尾统一处理。
+ */
+ public void updateTipsVisibility(int position) {
+ if (position < 0 || position >= getItemCount()) return;
+ pendingTipsCheckPos = position;
+// notifyItemChanged(position);
+ notifyDataSetChanged();
+ }
+
+ public void setPendingTipsCheckPos(Integer pendingTipsCheckPos) {
+ this.pendingTipsCheckPos = pendingTipsCheckPos;
+ }
+
+ @Override
+ protected void convert(BaseViewHolder helper, ChatMultiItem item) {
+ ChatAiMsgEntity msgEntity = item.getMessage();
+ TextView tvContent = helper.getView(R.id.tv_text);
+ LinearLayout llThinking = helper.getView(R.id.linear_thinking);
+ LinearLayout llStop = helper.getView(R.id.linear_stop);
+ LinearLayout llTips = helper.getView(R.id.linear_tips);
+ int position = helper.getBindingAdapterPosition();
+ helper.addOnClickListener(R.id.img_jb);
+// helper.addOnLongClickListener(R.id.tv_text);
+
+ if (item.getItemType() == ChatMultiItem.TYPE_TEXT_LEFT) {
+ String content = msgEntity.getContent();
+ if(TextUtils.isEmpty(content)){
+ llThinking.setVisibility(View.VISIBLE);
+ tvContent.setText("");
+ llStop.setVisibility(View.GONE);
+ } else {
+ llThinking.setVisibility(View.GONE);
+
+ // 优化流式输出更新逻辑
+ if (position == mCurrentStreamingPosition) {
+ // 当前正在流式输出的位置
+ try {
+ Spanned spanned = markwon.toMarkdown(content);
+ tvContent.setText(spanned);
+ } catch (Exception e) {
+ tvContent.setText(content);
+ }
+
+ // 显示停止标识(如果被停止)
+ if (stoppedPositions.containsKey(position) && stoppedPositions.get(position)) {
+ llStop.setVisibility(View.VISIBLE);
+ llTips.setVisibility(View.VISIBLE);
+ } else {
+ llStop.setVisibility(View.GONE);
+ }
+ } else {
+ // 历史消息,直接显示完整内容
+ try {
+ Spanned spanned = markwon.toMarkdown(content);
+ tvContent.setText(spanned);
+ } catch (Exception e) {
+ tvContent.setText(content);
+ }
+
+ // 历史消息也检查是否被停止
+ if (stoppedPositions.containsKey(position) && stoppedPositions.get(position)) {
+ llStop.setVisibility(View.VISIBLE);
+ llTips.setVisibility(View.VISIBLE);
+ } else {
+ llStop.setVisibility(View.GONE);
+ }
+ }
+ }
+ Log.i("mingdenglog","XXXXX---position======" + pendingTipsCheckPos);
+ if (pendingTipsCheckPos != null && position == pendingTipsCheckPos) {
+ Log.i("mingdenglog","position======" + position);
+ Log.i("mingdenglog","getItemCount() - 1 ------" + (getItemCount() - 1) );
+ // 仅当是最后一条才显示 llTips,否则隐藏
+ llTips.setVisibility(position == getItemCount() - 1 ? View.VISIBLE : View.GONE);
+ // 用一次即清
+// pendingTipsCheckPos = null;
+ }else{
+ llTips.setVisibility(View.GONE);
+ }
+ }
+
+ switch (item.getItemType()) {
+ case ChatMultiItem.TYPE_TEXT_RIGHT:
+ tvContent.setText(msgEntity.getContent());
+ break;
+ }
+
+// //必须要在事件发生之前就watch
+ final XPopup.Builder builder = new XPopup.Builder(mContext)
+ .hasShadowBg(false).watchView(tvContent);
+ tvContent.setOnLongClickListener(new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ // 如果正在流式输出,则拦截长按事件,不执行任何操作
+ if (mCurrentStreamingPosition != -1) {
+ // 可以选择性地提示用户
+ ToastUtil.showShort(mContext, "Ai解答中,请稍后操作");
+ return true; // 拦截事件
+ }
+
+ builder.asAttachList(new String[]{"复制", "举报"}, null, new OnSelectListener() {
+ @Override
+ public void onSelect(int menuPosition, String text) {
+ // 通过接口回调处理菜单项点击事件
+ if (mLongClickMenuListener != null) {
+ switch (menuPosition){
+ case 0: // 复制
+ mLongClickMenuListener.onCopyClick(item, position);
+ break;
+ case 1: // 举报
+ mLongClickMenuListener.onReportClick(item, position);
+ break;
+ }
+ }
+ }
+ }).show();
+ return false;
+ }
+ });
+ }
+
+ /**
+ * 清理缓存
+ */
+ public void clearCache() {
+ streamerMap.clear();
+ stoppedPositions.clear();
+ mCurrentStreamingPosition = -1;
+ }
+
+}
diff --git a/app/src/main/java/com/ckkj/water/chat/adapter/IndexEnterHintAdapter.java b/app/src/main/java/com/ckkj/water/chat/adapter/IndexEnterHintAdapter.java
new file mode 100644
index 0000000..a67b432
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/chat/adapter/IndexEnterHintAdapter.java
@@ -0,0 +1,38 @@
+package com.ckkj.water.chat.adapter;
+
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+
+import com.chad.library.adapter.base.BaseQuickAdapter;
+import com.chad.library.adapter.base.BaseViewHolder;
+import com.ckkj.water.R;
+
+import java.util.List;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/7
+ * @description TODO: 描述该类的作用
+ */
+
+public class IndexEnterHintAdapter extends BaseQuickAdapter {
+ private Context mContext;
+ private int mSelPos = 0;
+ private String textColor;
+ private String bgColor;
+
+ public void setColor(String textColor,String bgColor){
+ this.textColor = textColor;
+ this.bgColor = bgColor;
+ }
+ public IndexEnterHintAdapter(Context context, int layoutResId, @Nullable List data) {
+ super(layoutResId, data);
+ this.mContext = context;
+ }
+
+ @Override
+ protected void convert(BaseViewHolder helper, String item) {
+ helper.setText(R.id.tv_quick_ask_name,item);
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/chat/adapter/IndexOptionAdapter.java b/app/src/main/java/com/ckkj/water/chat/adapter/IndexOptionAdapter.java
new file mode 100644
index 0000000..a21a145
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/chat/adapter/IndexOptionAdapter.java
@@ -0,0 +1,45 @@
+package com.ckkj.water.chat.adapter;
+
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+
+import com.chad.library.adapter.base.BaseQuickAdapter;
+import com.chad.library.adapter.base.BaseViewHolder;
+import com.ckkj.water.R;
+import com.ckkj.water.chat.entity.IndexTemplateEntity;
+
+import java.util.List;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/7
+ * @description TODO: 描述该类的作用
+ */
+
+public class IndexOptionAdapter extends BaseQuickAdapter {
+ private Context mContext;
+ private int mSelPos = 0;
+
+ public IndexOptionAdapter(Context context,int layoutResId, @Nullable List data) {
+ super(layoutResId, data);
+ this.mContext = context;
+ }
+
+ @Override
+ protected void convert(BaseViewHolder helper, IndexTemplateEntity item) {
+// helper.setText(R.id.tv_option_name,item.getTitle());
+// if(helper.getLayoutPosition() == mSelPos){
+// helper.setTextColor(R.id.tv_option_name,mContext.getColor(R.color.white));
+// helper.setBackgroundRes(R.id.tv_option_name, R.drawable.shape_index_option_selected);
+// }else{
+// helper.setTextColor(R.id.tv_option_name,mContext.getColor(R.color.color_zhu));
+// helper.setBackgroundRes(R.id.tv_option_name, R.drawable.shape_index_option_un_selected);
+// }
+ }
+
+ public void setSelectPos(int position) {
+ this.mSelPos = position;
+ notifyDataSetChanged();
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/chat/adapter/IndexQuickAskAdapter.java b/app/src/main/java/com/ckkj/water/chat/adapter/IndexQuickAskAdapter.java
new file mode 100644
index 0000000..6eb9f15
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/chat/adapter/IndexQuickAskAdapter.java
@@ -0,0 +1,58 @@
+package com.ckkj.water.chat.adapter;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.GradientDrawable;
+import android.util.TypedValue;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+
+import com.chad.library.adapter.base.BaseQuickAdapter;
+import com.chad.library.adapter.base.BaseViewHolder;
+import com.ckkj.water.R;
+import com.ckkj.water.chat.entity.IndexTemplateEntity;
+import com.ckkj.water.utils.manager.ColorManage;
+
+import java.util.List;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/8
+ * @description TODO: 描述该类的作用
+ */
+
+public class IndexQuickAskAdapter extends BaseQuickAdapter {
+ private Context context;
+ public IndexQuickAskAdapter(Context context,int layoutResId, @Nullable List data) {
+ super(layoutResId, data);
+ this.context = context;
+ }
+
+ @Override
+ protected void convert(BaseViewHolder helper, IndexTemplateEntity item) {
+ helper.setText(R.id.tv_quick_ask_name,item.getSpliteItemContent());
+ if(ColorManage.getInstance().isParsableColor(item.getColor())){
+ helper.setTextColor(R.id.tv_quick_ask_name, Color.parseColor(item.getColor()));
+ }else{
+ helper.setTextColor(R.id.tv_quick_ask_name, ContextCompat.getColor(mContext,R.color.color_zhu));
+ }
+ if(ColorManage.getInstance().isParsableColor(item.getBlackcolor())){
+ setRoundedBackground(helper.getView(R.id.tv_quick_ask_name),Color.parseColor(item.getBlackcolor()),30);
+ }else{
+ setRoundedBackground(helper.getView(R.id.tv_quick_ask_name),ContextCompat.getColor(mContext,R.color.color_E5F2FB),30);
+ }
+ }
+
+ private void setRoundedBackground(View view, int fillColor, float cornerRadiusDp) {
+ GradientDrawable drawable = new GradientDrawable();
+ drawable.setShape(GradientDrawable.RECTANGLE);
+ drawable.setColor(fillColor); // 设置填充色
+ float radiusPx = TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP, cornerRadiusDp, view.getResources().getDisplayMetrics()
+ );
+ drawable.setCornerRadius(radiusPx); // 设置圆角半径(单位:px)
+ view.setBackground(drawable); // 应用背景
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/chat/entity/CharHistoryEntity.java b/app/src/main/java/com/ckkj/water/chat/entity/CharHistoryEntity.java
new file mode 100644
index 0000000..b6ea997
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/chat/entity/CharHistoryEntity.java
@@ -0,0 +1,122 @@
+package com.ckkj.water.chat.entity;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/11
+ * @description TODO: 描述该类的作用
+ */
+
+public class CharHistoryEntity implements Serializable {
+ private int total;
+ private List sessions;
+ private int pages;
+ private int pageSize;
+ private long userId;
+ private int pageNum;
+ public void setTotal(int total) {
+ this.total = total;
+ }
+ public int getTotal() {
+ return total;
+ }
+
+ public void setSessions(List sessions) {
+ this.sessions = sessions;
+ }
+ public List getSessions() {
+ return sessions;
+ }
+
+ public void setPages(int pages) {
+ this.pages = pages;
+ }
+ public int getPages() {
+ return pages;
+ }
+
+ public void setPageSize(int pageSize) {
+ this.pageSize = pageSize;
+ }
+ public int getPageSize() {
+ return pageSize;
+ }
+
+ public void setUserId(long userId) {
+ this.userId = userId;
+ }
+ public long getUserId() {
+ return userId;
+ }
+
+ public void setPageNum(int pageNum) {
+ this.pageNum = pageNum;
+ }
+ public int getPageNum() {
+ return pageNum;
+ }
+
+
+ public static class Sessions implements Serializable{
+
+ private String sessionId;
+ private long userId;
+ private String sessionName;
+ private String numerologyType;
+ private String firstUserMessage;
+ private Date createTime;
+ private Date updateTime;
+ public void setSessionId(String sessionId) {
+ this.sessionId = sessionId;
+ }
+ public String getSessionId() {
+ return sessionId;
+ }
+
+ public void setUserId(long userId) {
+ this.userId = userId;
+ }
+ public long getUserId() {
+ return userId;
+ }
+
+ public void setSessionName(String sessionName) {
+ this.sessionName = sessionName;
+ }
+ public String getSessionName() {
+ return sessionName;
+ }
+
+ public void setNumerologyType(String numerologyType) {
+ this.numerologyType = numerologyType;
+ }
+ public String getNumerologyType() {
+ return numerologyType;
+ }
+
+ public void setFirstUserMessage(String firstUserMessage) {
+ this.firstUserMessage = firstUserMessage;
+ }
+ public String getFirstUserMessage() {
+ return firstUserMessage;
+ }
+
+ public void setCreateTime(Date createTime) {
+ this.createTime = createTime;
+ }
+ public Date getCreateTime() {
+ return createTime;
+ }
+
+ public void setUpdateTime(Date updateTime) {
+ this.updateTime = updateTime;
+ }
+ public Date getUpdateTime() {
+ return updateTime;
+ }
+
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/chat/entity/ChatAiMsgEntity.java b/app/src/main/java/com/ckkj/water/chat/entity/ChatAiMsgEntity.java
new file mode 100644
index 0000000..6b9ff93
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/chat/entity/ChatAiMsgEntity.java
@@ -0,0 +1,75 @@
+package com.ckkj.water.chat.entity;
+
+import java.io.Serializable;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/7
+ * @description TODO: 描述该类的作用
+ */
+
+public class ChatAiMsgEntity implements Serializable {
+ private int id;
+ private String sessionId;
+ private String type;
+ private String content;
+ private String role;
+ private String messageOrder;
+ private String createTime;
+
+ public String getSessionId() {
+ return sessionId;
+ }
+
+ public void setSessionId(String sessionId) {
+ this.sessionId = sessionId;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getContent() {
+ return content;
+ }
+
+ public void setContent(String content) {
+ this.content = content;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public String getRole() {
+ return role;
+ }
+
+ public void setRole(String role) {
+ this.role = role;
+ }
+
+ public String getMessageOrder() {
+ return messageOrder;
+ }
+
+ public void setMessageOrder(String messageOrder) {
+ this.messageOrder = messageOrder;
+ }
+
+ public String getCreateTime() {
+ return createTime;
+ }
+
+ public void setCreateTime(String createTime) {
+ this.createTime = createTime;
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/chat/entity/ChatMultiItem.java b/app/src/main/java/com/ckkj/water/chat/entity/ChatMultiItem.java
new file mode 100644
index 0000000..8743863
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/chat/entity/ChatMultiItem.java
@@ -0,0 +1,44 @@
+package com.ckkj.water.chat.entity;
+
+import com.chad.library.adapter.base.entity.MultiItemEntity;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/7
+ * @description TODO: 描述该类的作用
+ */
+
+public class ChatMultiItem implements MultiItemEntity {
+ public static final int TYPE_TEXT_LEFT = 1;
+ public static final int TYPE_TEXT_RIGHT = 2;
+ public static final int TYPE_IMAGE_LEFT = 3;
+ public static final int TYPE_IMAGE_RIGHT = 4;
+ public static final int TYPE_VOICE_LEFT = 5;
+ public static final int TYPE_VOICE_RIGHT = 6;
+ public static final int TYPE_TIME = 7; // 新增时间类型
+
+ private int itemType;
+ private ChatAiMsgEntity message;
+
+ public ChatMultiItem(int itemType, ChatAiMsgEntity message) {
+ this.itemType = itemType;
+ this.message = message;
+ }
+
+ @Override
+ public int getItemType() {
+ return itemType;
+ }
+
+ public void setItemType(int itemType) {
+ this.itemType = itemType;
+ }
+
+ public ChatAiMsgEntity getMessage() {
+ return message;
+ }
+
+ public void setMessage(ChatAiMsgEntity message) {
+ this.message = message;
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/chat/entity/ChatQuestionEntity.java b/app/src/main/java/com/ckkj/water/chat/entity/ChatQuestionEntity.java
new file mode 100644
index 0000000..631f792
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/chat/entity/ChatQuestionEntity.java
@@ -0,0 +1,36 @@
+package com.ckkj.water.chat.entity;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.io.Serializable;
+import java.util.List;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/8/19
+ * @description TODO: 描述该类的作用
+ */
+
+@NoArgsConstructor
+@Data
+public class ChatQuestionEntity implements Serializable {
+
+ @SerializedName("teb")
+ private String teb;
+ @SerializedName("subheadings")
+ private List subheadings;
+ @SerializedName("id")
+ private String id;
+
+ @NoArgsConstructor
+ @Data
+ public static class SubheadingsDTO {
+ @SerializedName("title")
+ private String title;
+ @SerializedName("content")
+ private String content;
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/chat/entity/IndexTemplateEntity.java b/app/src/main/java/com/ckkj/water/chat/entity/IndexTemplateEntity.java
new file mode 100644
index 0000000..d0dfd3d
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/chat/entity/IndexTemplateEntity.java
@@ -0,0 +1,111 @@
+package com.ckkj.water.chat.entity;
+
+import java.io.Serializable;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/8
+ * @description TODO: 描述该类的作用
+ */
+
+public class IndexTemplateEntity implements Serializable {
+ private String id;
+ private String title;
+ private String titletp;
+ private String color;
+ private String blackcolor;
+ private String subheading;
+ private String intermediateContent;
+ private String quickQuestions;
+ private String hide;
+ private String type;
+ private String spliteItemContent;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getTitletp() {
+ return titletp;
+ }
+
+ public void setTitletp(String titletp) {
+ this.titletp = titletp;
+ }
+
+ public String getColor() {
+ return color;
+ }
+
+ public void setColor(String color) {
+ this.color = color;
+ }
+
+ public String getBlackcolor() {
+ return blackcolor;
+ }
+
+ public void setBlackcolor(String blackcolor) {
+ this.blackcolor = blackcolor;
+ }
+
+ public String getSubheading() {
+ return subheading;
+ }
+
+ public void setSubheading(String subheading) {
+ this.subheading = subheading;
+ }
+
+ public String getIntermediateContent() {
+ return intermediateContent;
+ }
+
+ public void setIntermediateContent(String intermediateContent) {
+ this.intermediateContent = intermediateContent;
+ }
+
+ public String getQuickQuestions() {
+ return quickQuestions;
+ }
+
+ public void setQuickQuestions(String quickQuestions) {
+ this.quickQuestions = quickQuestions;
+ }
+
+ public String getHide() {
+ return hide;
+ }
+
+ public void setHide(String hide) {
+ this.hide = hide;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getSpliteItemContent() {
+ return spliteItemContent;
+ }
+
+ public void setSpliteItemContent(String spliteItemContent) {
+ this.spliteItemContent = spliteItemContent;
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/chat/mvp/IndexContract.java b/app/src/main/java/com/ckkj/water/chat/mvp/IndexContract.java
new file mode 100644
index 0000000..7456709
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/chat/mvp/IndexContract.java
@@ -0,0 +1,78 @@
+package com.ckkj.water.chat.mvp;
+
+import com.ckkj.water.base.IView;
+import com.ckkj.water.chat.entity.CharHistoryEntity;
+import com.ckkj.water.chat.entity.ChatAiMsgEntity;
+import com.ckkj.water.chat.entity.ChatQuestionEntity;
+import com.ckkj.water.chat.entity.IndexTemplateEntity;
+import com.ckkj.water.network.BaseBean;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/7
+ * @description TODO: 描述该类的作用
+ */
+
+public class IndexContract {
+ public interface DataModel{
+ //发送消息给 AI
+ void sendMsgToAi(Map map, CallBack callBack);
+ //发送消息给 AI
+ void sendMsgToAi(Map map, AiStreamCallback callBack);
+ //停止当前请求
+ void stopCurrentRequest();
+ //获取首页数据
+ void getIndexTemplateDatas(CallBack callBack);
+ //获取会话历史
+ void getHistoryChatDatas(Map map, CallBack callBack);
+ //删除会话
+ void deleteHistoryBySessionID(Map map, CallBack callBack);
+ //根据会话 id,查询记录详情
+ void getChatDetailById(Map map, CallBack callBack);
+ //首页咨询模板
+ void getChatemplateDatas(CallBack callBack);
+ //举报好友
+ void commitReport(Map map,CallBack callBack);
+ }
+
+ public interface CallBack {
+ void success(T msg);
+ void fail(String msg);
+ }
+
+ public interface AiStreamCallback {
+ void onMessage(String messageChunk); // 每次返回一段内容
+ void onError(String error);
+ void onComplete(String complateContent); // 结束
+ void onSessionReceived(String sessionId); // 新增:接收sessionId
+ void loginLost();
+ }
+
+ public interface IndexView extends IView {
+ //发送消息给 AI,获取结果
+ void getTalkResultSuccess(ChatAiMsgEntity bean);
+ //获取首页数据成功
+ void getIndexTemplateSuccess(List list);
+ // 新增流式回调方法
+ void onStreamMessageReceived(String messageChunk);
+ //完成
+ void onStreamComplete(String complateMsg);
+ //接收sessionId
+ void onSessionReceived(String sessionId);
+ //查询历史记录成功 g
+ void getHistoryDataSuccess(CharHistoryEntity entity);
+ //删除会话成功
+ void deleteHistorySuccess(BaseBean bean,int delPos);
+ //根据 id 查询会话详情
+ void getChatDetailSuccess(List list);
+ //登录失效
+ void loginLost();
+ //获取首页聊天模板
+ void getChatZixunSuccess(List list);
+ //提交举报反馈成功
+ void commitReportSuccess(BaseBean bean);
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/chat/mvp/IndexImpl.java b/app/src/main/java/com/ckkj/water/chat/mvp/IndexImpl.java
new file mode 100644
index 0000000..ca16302
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/chat/mvp/IndexImpl.java
@@ -0,0 +1,575 @@
+package com.ckkj.water.chat.mvp;
+
+import static android.content.ContentValues.TAG;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.ckkj.water.AppConfig;
+import com.ckkj.water.chat.entity.CharHistoryEntity;
+import com.ckkj.water.chat.entity.ChatAiMsgEntity;
+import com.ckkj.water.chat.entity.ChatQuestionEntity;
+import com.ckkj.water.chat.entity.IndexTemplateEntity;
+import com.ckkj.water.network.BaseBean;
+import com.ckkj.water.network.NetWorkManager;
+
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URLEncoder;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.net.SocketTimeoutException;
+
+import io.reactivex.Observer;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.annotations.NonNull;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.schedulers.Schedulers;
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/7
+ * @description TODO: 描述该类的作用
+ */
+
+public class IndexImpl implements IndexContract.DataModel{
+
+ @Override
+ public void sendMsgToAi(Map map, IndexContract.CallBack callBack) {
+ try {
+ NetWorkManager.getRequest().indexTalkToAi(NetWorkManager.getToken(),map)
+ .subscribeOn(Schedulers.io())
+ .unsubscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new Observer>() {
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {
+
+ }
+
+ @Override
+ public void onNext(@NonNull BaseBean bean) {
+ if(bean.getCode() == 200){
+ callBack.success(bean.getData());
+ }else{
+ callBack.fail(bean.getMessage());
+ }
+ }
+
+ @Override
+ public void onError(@NonNull Throwable e) {
+ callBack.fail(e.getMessage());
+ }
+
+ @Override
+ public void onComplete() {
+ }
+ });
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 发送消息给 AI - 流式版本
+ * @param map
+ * @param callBack
+ */
+ private final OkHttpClient client = new OkHttpClient.Builder()
+ .connectTimeout(60, TimeUnit.SECONDS) // 连接超时60秒
+ .readTimeout(60, TimeUnit.SECONDS) // 读取超时60秒
+ .writeTimeout(60, TimeUnit.SECONDS) // 写入超时60秒
+ .callTimeout(60, TimeUnit.SECONDS) // 整个调用超时60秒
+ .build();
+
+ private Call currentCall = null; // 新增:保存当前请求的Call对象
+ private volatile boolean isRequestStopped = false; // 新增:标记是否已停止请求
+
+ @Override
+ public void sendMsgToAi(Map map, IndexContract.AiStreamCallback callBack) {
+ try {
+ String finalUrl = AppConfig.BASE_URL + "mobile/context?" + buildQueryParams(map);
+ Log.d(TAG, "Request URL: " + finalUrl);
+
+ Request request = new Request.Builder()
+ .addHeader("Authorization", "Bearer " + AppConfig.USER_TOKEN)
+ .url(finalUrl)
+ .get()
+ .build();
+ currentCall = client.newCall(request);
+ isRequestStopped = false;
+
+ currentCall.enqueue(new Callback() {
+ @Override
+ public void onFailure(Call call, IOException e) {
+ if (isRequestStopped) return; // 如果已停止,不处理失败回调
+
+ // 检查是否是超时异常
+ String errorMessage = e.getMessage();
+ if (e instanceof SocketTimeoutException ||
+ (errorMessage != null && errorMessage.contains("timeout"))) {
+ errorMessage = "请求超时,请稍后重试";
+ }
+
+ String finalErrorMessage = errorMessage;
+ new Handler(Looper.getMainLooper()).post(() -> {
+ if (callBack != null) callBack.onError(finalErrorMessage);
+ });
+ }
+
+ @Override
+ public void onResponse(Call call, Response response) {
+ if (isRequestStopped) return;
+
+ if (!response.isSuccessful()) {
+ new Handler(Looper.getMainLooper()).post(() -> {
+ if (callBack != null) callBack.onError("请求失败: " + response.code());
+ });
+ return;
+ }
+
+ InputStream inputStream = response.body().byteStream();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
+
+ try {
+ reader.mark(1024); // 标记流的当前位置
+ String peekLine = reader.readLine();
+ reader.reset(); // 回到标记位置
+
+ // 判断是否是标准 JSON(非流式错误返回)
+ if (peekLine != null && peekLine.trim().startsWith("{")) {
+ StringBuilder jsonBuilder = new StringBuilder();
+ String line;
+ while ((line = reader.readLine()) != null) {
+ jsonBuilder.append(line);
+ }
+ String responseJson = jsonBuilder.toString();
+
+ JSONObject json = new JSONObject(responseJson);
+ int code = json.optInt("code", 0);
+ String msg = json.optString("msg", "");
+ if (code == 401) {
+ new Handler(Looper.getMainLooper()).post(() -> {
+ if (callBack != null) callBack.loginLost();
+ });
+ } else {
+ new Handler(Looper.getMainLooper()).post(() -> {
+ if (callBack != null) callBack.onError("未知错误: " + msg);
+ });
+ }
+ return;
+ }
+
+ // 否则处理为 SSE 流式数据
+ handleSSEStream(reader, callBack);
+
+ } catch (Exception e) {
+ new Handler(Looper.getMainLooper()).post(() -> {
+// if (callBack != null) callBack.onError("响应异常: " + e.getMessage());
+ });
+ } finally {
+ try {
+ reader.close();
+ response.close();
+ } catch (IOException ignored) {}
+ }
+ }
+ });
+
+ } catch (Exception e) {
+ new Handler(Looper.getMainLooper()).post(() -> {
+ if (callBack != null) callBack.onError("请求异常: " + e.getMessage());
+ });
+ }
+ }
+
+ private void handleSSEStream(BufferedReader reader, IndexContract.AiStreamCallback callBack) throws IOException {
+ String line;
+ StringBuilder accumulatedContent = new StringBuilder();
+ StringBuilder lastLineBuffer = new StringBuilder();
+ String currentEvent = null;
+ String currentData = null;
+ String sessionId = "";
+
+ while ((line = reader.readLine()) != null) {
+ if (isRequestStopped) break;
+
+ if (line.startsWith("event:")) {
+ currentEvent = line.substring(6).trim();
+ } else if (line.startsWith("data:")) {
+ currentData = line.substring(5).trim();
+
+ if ("session".equals(currentEvent)) {
+ try {
+ JsonObject sessionData = JsonParser.parseString(currentData).getAsJsonObject();
+ sessionId = sessionData.get("sessionId").getAsString();
+ if (callBack != null && !isRequestStopped) {
+ callBack.onSessionReceived(sessionId);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "解析session数据失败", e);
+ }
+ } else if ("chunk".equals(currentEvent)) {
+ try {
+ JsonObject chunkData = JsonParser.parseString(currentData).getAsJsonObject();
+ String content = chunkData.get("content").getAsString();
+ boolean hasNewline = chunkData.get("hasNewline").getAsBoolean();
+
+ if (isTableLineFragment(lastLineBuffer.toString(), content)) {
+ lastLineBuffer.append(content);
+ if (hasNewline) {
+ accumulatedContent.append(lastLineBuffer).append("\n");
+ lastLineBuffer.setLength(0);
+ }
+ } else {
+ if (lastLineBuffer.length() > 0) {
+ accumulatedContent.append(lastLineBuffer);
+ lastLineBuffer.setLength(0);
+ }
+ lastLineBuffer.append(content);
+ if (hasNewline) {
+ accumulatedContent.append(lastLineBuffer).append("\n");
+ lastLineBuffer.setLength(0);
+ }
+ }
+
+ final String currentContent = accumulatedContent.toString();
+ new Handler(Looper.getMainLooper()).post(() -> {
+ if (callBack != null && !isRequestStopped) {
+ callBack.onMessage(currentContent);
+ }
+ });
+
+ } catch (Exception e) {
+ accumulatedContent.append(currentData);
+ final String currentContent = accumulatedContent.toString();
+ new Handler(Looper.getMainLooper()).post(() -> {
+ if (callBack != null && !isRequestStopped) {
+ callBack.onMessage(currentContent);
+ }
+ });
+ }
+ } else if ("complete".equals(currentEvent)) {
+ // 先处理剩余的 lastLineBuffer 内容,但不通过 onMessage 回调
+ if (lastLineBuffer.length() > 0) {
+ accumulatedContent.append(lastLineBuffer);
+ // 注意:这里不调用 callBack.onMessage(),避免触发 onStreamMessageReceived
+ }
+
+ String complateContent = "";
+ try {
+ JsonObject completeData = JsonParser.parseString(currentData).getAsJsonObject();
+ complateContent = completeData.get("fullResponse").getAsString();
+ } catch (Exception e) {
+ Log.e(TAG, "解析complete数据失败", e);
+ }
+
+ final String finalComplateContent = complateContent;
+ new Handler(Looper.getMainLooper()).post(() -> {
+ if (callBack != null && !isRequestStopped) {
+ callBack.onComplete(finalComplateContent); // 只调用 onComplete
+ }
+ });
+ }
+ }
+ }
+
+ // 处理结束前残留内容
+// if (lastLineBuffer.length() > 0 && !isRequestStopped) {
+// accumulatedContent.append(lastLineBuffer);
+// final String finalContent = accumulatedContent.toString();
+// new Handler(Looper.getMainLooper()).post(() -> {
+// if (callBack != null && !isRequestStopped) {
+// callBack.onMessage(finalContent);
+// }
+// });
+// }
+ }
+
+ /**
+ * 停止当前请求
+ */
+ public void stopCurrentRequest() {
+ isRequestStopped = true;
+ if (currentCall != null && !currentCall.isCanceled()) {
+ currentCall.cancel();
+ currentCall = null;
+ }
+ }
+
+ /**
+ * 判断是否是表格分隔线片段
+ */
+ private boolean isTableLineFragment(String last, String curr) {
+ return (last.contains("|") && last.contains("-")) || (curr.contains("|") && curr.contains("-"));
+ }
+
+ // 构造 GET 参数
+ private String buildQueryParams(Map map) {
+ StringBuilder sb = new StringBuilder();
+ for (Map.Entry entry : map.entrySet()) {
+ try {
+ if (sb.length() > 0) sb.append("&");
+ sb.append(URLEncoder.encode(entry.getKey(), "UTF-8"));
+ sb.append("=");
+ sb.append(URLEncoder.encode(String.valueOf(entry.getValue()), "UTF-8"));
+ } catch (Exception e) {
+ Log.e(TAG, "Encoding error", e);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * 获取首页数据
+ */
+ @Override
+ public void getIndexTemplateDatas(IndexContract.CallBack callBack) {
+ try {
+ NetWorkManager.getRequest().getIndexTemplateDatas()
+ .subscribeOn(Schedulers.io())
+ .unsubscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new Observer>>() {
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {
+ }
+
+ @Override
+ public void onNext(@NonNull BaseBean> bean) {
+ if(bean.getCode() == 200){
+ callBack.success(bean.getData());
+ }else{
+ callBack.fail(bean.getMessage());
+ }
+ }
+
+ @Override
+ public void onError(@NonNull Throwable e) {
+ callBack.fail(e.getMessage());
+ }
+
+ @Override
+ public void onComplete() {
+ }
+ });
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 获取会话历史记录
+ * @param map
+ * @param callBack
+ */
+ @Override
+ public void getHistoryChatDatas(Map map, IndexContract.CallBack callBack) {
+ try {
+ NetWorkManager.getRequest().getHistoryList(NetWorkManager.getToken(),map)
+ .subscribeOn(Schedulers.io())
+ .unsubscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new Observer>() {
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {
+
+ }
+
+ @Override
+ public void onNext(@NonNull BaseBean bean) {
+ if(bean.getCode() == 200){
+ callBack.success(bean.getData());
+ }else{
+ callBack.fail(bean.getMessage());
+ }
+ }
+
+ @Override
+ public void onError(@NonNull Throwable e) {
+ callBack.fail(e.getMessage());
+ }
+
+ @Override
+ public void onComplete() {
+ }
+ });
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 删除历史会话
+ * @param map
+ * @param callBack
+ */
+ @Override
+ public void deleteHistoryBySessionID(Map map, IndexContract.CallBack callBack) {
+ try {
+ NetWorkManager.getRequest().deleteHistoryBySessionID(NetWorkManager.getToken(),map)
+ .subscribeOn(Schedulers.io())
+ .unsubscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new Observer() {
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {
+
+ }
+
+ @Override
+ public void onNext(@NonNull BaseBean bean) {
+ if(bean.getCode() == 200){
+ callBack.success(bean);
+ }else{
+ callBack.fail(bean.getMessage());
+ }
+ }
+
+ @Override
+ public void onError(@NonNull Throwable e) {
+ callBack.fail(e.getMessage());
+ }
+
+ @Override
+ public void onComplete() {
+ }
+ });
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ /**
+ * 根据会话 id 查询详情
+ * @param map
+ * @param callBack
+ */
+ @Override
+ public void getChatDetailById(Map map, IndexContract.CallBack callBack) {
+ try {
+ NetWorkManager.getRequest().getChatDetailById(NetWorkManager.getToken(),map)
+ .subscribeOn(Schedulers.io())
+ .unsubscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new Observer>>() {
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {
+
+ }
+
+ @Override
+ public void onNext(@NonNull BaseBean> bean) {
+ if(bean.getCode() == 200){
+ callBack.success(bean.getData());
+ }else{
+ callBack.fail(bean.getMessage());
+ }
+ }
+
+ @Override
+ public void onError(@NonNull Throwable e) {
+ callBack.fail(e.getMessage());
+ }
+
+ @Override
+ public void onComplete() {
+ }
+ });
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 对话页,咨询模板获取
+ * @param callBack
+ */
+ @Override
+ public void getChatemplateDatas(IndexContract.CallBack callBack) {
+ try {
+ NetWorkManager.getRequest().aitebcontent()
+ .subscribeOn(Schedulers.io())
+ .unsubscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new Observer>>() {
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {
+ }
+
+ @Override
+ public void onNext(@NonNull BaseBean> bean) {
+ if(bean.getCode() == 200){
+ callBack.success(bean.getData());
+ }else{
+ callBack.fail(bean.getMessage());
+ }
+ }
+
+ @Override
+ public void onError(@NonNull Throwable e) {
+ callBack.fail(e.getMessage());
+ }
+
+ @Override
+ public void onComplete() {
+ }
+ });
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 举报反馈
+ * @param map
+ * @param callBack
+ */
+ @Override
+ public void commitReport(Map map, IndexContract.CallBack callBack) {
+ try {
+ NetWorkManager.getRequest().commitReporting(NetWorkManager.getToken(),map)
+ .subscribeOn(Schedulers.io())
+ .unsubscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new Observer() {
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {
+
+ }
+
+ @Override
+ public void onNext(@NonNull BaseBean bean) {
+ if(bean.getCode() == 200){
+ callBack.success(bean.getData());
+ }else{
+ callBack.fail(bean.getMessage());
+ }
+ }
+
+ @Override
+ public void onError(@NonNull Throwable e) {
+ callBack.fail(e.getMessage());
+ }
+
+ @Override
+ public void onComplete() {
+ }
+ });
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/chat/mvp/IndexPresenter.java b/app/src/main/java/com/ckkj/water/chat/mvp/IndexPresenter.java
new file mode 100644
index 0000000..47fbeb4
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/chat/mvp/IndexPresenter.java
@@ -0,0 +1,249 @@
+package com.ckkj.water.chat.mvp;
+
+import com.ckkj.water.EventMessage;
+import com.ckkj.water.base.BasePresenter;
+import com.ckkj.water.chat.entity.CharHistoryEntity;
+import com.ckkj.water.chat.entity.ChatAiMsgEntity;
+import com.ckkj.water.chat.entity.ChatQuestionEntity;
+import com.ckkj.water.chat.entity.IndexTemplateEntity;
+import com.ckkj.water.network.BaseBean;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import de.greenrobot.event.EventBus;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/7
+ * @description TODO: 描述该类的作用
+ */
+
+public class IndexPresenter extends BasePresenter {
+ private IndexImpl impl;
+
+ public IndexPresenter(IndexImpl impl) {
+ this.impl = impl;
+ }
+
+ /**
+ * 发送消息给 AI
+ *
+ * @param content
+ */
+// public void talkToAi(String content,String type) {
+// Map map = new HashMap<>();
+// map.put("content",content);
+// map.put("type",type);
+// map.put("sessionId","");
+// impl.sendMsgToAi(map, new IndexContract.CallBack() {
+// @Override
+// public void success(ChatAiMsgEntity bean) {
+// if( IndexPresenter.this.getView() != null){
+// IndexPresenter.this.getView().getTalkResultSuccess(bean);
+// }
+// }
+//
+// @Override
+// public void fail(String msg) {
+// if( IndexPresenter.this.getView() != null){
+// IndexPresenter.this.getView().showError(msg);
+// }
+// }
+// });
+// }
+ public void talkToAi(String content, String type, String sessionID) {
+ Map map = new HashMap<>();
+ map.put("content", content);
+ map.put("type", type);
+ map.put("sessionId", sessionID);
+
+ impl.sendMsgToAi(map, new IndexContract.AiStreamCallback() {
+ @Override
+ public void onMessage(String messageChunk) {
+ // 流式数据到达时,通知View更新UI
+ if (IndexPresenter.this.getView() != null) {
+ IndexPresenter.this.getView().onStreamMessageReceived(messageChunk);
+ }
+ }
+
+ @Override
+ public void onError(String msg) {
+ if (IndexPresenter.this.getView() != null) {
+ IndexPresenter.this.getView().showError(msg);
+ }
+ }
+
+ @Override
+ public void onComplete(String complateMsg) {
+ // 流式传输完成
+ if (IndexPresenter.this.getView() != null) {
+ IndexPresenter.this.getView().onStreamComplete(complateMsg);
+ }
+ }
+
+ @Override
+ public void onSessionReceived(String sessionId) {
+ // 接收到sessionId时,通知View
+ if (IndexPresenter.this.getView() != null) {
+ IndexPresenter.this.getView().onSessionReceived(sessionId);
+ }
+ }
+
+ @Override
+ public void loginLost() {
+ if (IndexPresenter.this.getView() != null) {
+ IndexPresenter.this.getView().loginLost();
+ }
+ }
+ });
+ }
+
+ /**
+ * 停止当前流式请求
+ */
+ public void stopCurrentRequest() {
+ impl.stopCurrentRequest();
+ }
+
+ /**
+ * 首页模板消息数据获取
+ */
+ public void getIndexTemplateDatas() {
+ impl.getIndexTemplateDatas(new IndexContract.CallBack>() {
+ @Override
+ public void success(List list) {
+ if (IndexPresenter.this.getView() != null) {
+ IndexPresenter.this.getView().getIndexTemplateSuccess(list);
+ }
+ }
+
+ @Override
+ public void fail(String msg) {
+ if (IndexPresenter.this.getView() != null) {
+ IndexPresenter.this.getView().showError(msg);
+ }
+ }
+ });
+ }
+
+ /**
+ * 获取历史记录
+ * @param mCurrentPage
+ */
+ public void getHistoryDatas(int mCurrentPage) {
+ Map map = new HashMap<>();
+ map.put("pageNum", mCurrentPage);
+ map.put("pageSize", 10);
+ impl.getHistoryChatDatas(map, new IndexContract.CallBack() {
+ @Override
+ public void success(CharHistoryEntity entity) {
+ if (IndexPresenter.this.getView() != null) {
+ IndexPresenter.this.getView().getHistoryDataSuccess(entity);
+ }
+ }
+
+ @Override
+ public void fail(String msg) {
+ if (IndexPresenter.this.getView() != null) {
+ IndexPresenter.this.getView().showError(msg);
+ }
+ }
+ });
+ }
+
+
+ /**
+ * 删除历史记录
+ * @param sessionID
+ */
+ public void deleteHistoryBySessionID(String sessionID,int delPos) {
+ Map map = new HashMap<>();
+ map.put("sessionId", sessionID);
+ impl.deleteHistoryBySessionID(map, new IndexContract.CallBack() {
+ @Override
+ public void success(BaseBean entity) {
+ EventBus.getDefault().post(new EventMessage(EventMessage.HISTORY_SESSIONID_DELETE_SUCCESS,sessionID));
+ if (IndexPresenter.this.getView() != null) {
+ IndexPresenter.this.getView().deleteHistorySuccess(entity,delPos);
+ }
+ }
+
+ @Override
+ public void fail(String msg) {
+ if (IndexPresenter.this.getView() != null) {
+ IndexPresenter.this.getView().showError(msg);
+ }
+ }
+ });
+ }
+ /**
+ * 根据sessionID 查询消息记录
+ * @param sessionId
+ */
+ public void getChatDetailByID(String sessionId) {
+ Map map = new HashMap<>();
+ map.put("sessionId", sessionId);
+ impl.getChatDetailById(map, new IndexContract.CallBack>() {
+ @Override
+ public void success(List list) {
+ if (IndexPresenter.this.getView() != null) {
+ IndexPresenter.this.getView().getChatDetailSuccess(list);
+ }
+ }
+
+ @Override
+ public void fail(String msg) {
+ if (IndexPresenter.this.getView() != null) {
+ IndexPresenter.this.getView().showError(msg);
+ }
+ }
+ });
+ }
+
+ /**
+ * 获取首页咨询会话模板
+ */
+ public void getChatTemplateDatas() {
+ impl.getChatemplateDatas(new IndexContract.CallBack>() {
+ @Override
+ public void success(List list) {
+ if (IndexPresenter.this.getView() != null) {
+ IndexPresenter.this.getView().getChatZixunSuccess(list);
+ }
+ }
+
+ @Override
+ public void fail(String msg) {
+ if (IndexPresenter.this.getView() != null) {
+ IndexPresenter.this.getView().showError(msg);
+ }
+ }
+ });
+ }
+
+ /**
+ * 举报
+ * @param reason
+ */
+ public void commitReport(String reason) {
+ Map map = new HashMap<>();
+ map.put("content", reason);
+ impl.commitReport(map, new IndexContract.CallBack() {
+ @Override
+ public void success(BaseBean bean) {
+ if (IndexPresenter.this.getView() != null) {
+ IndexPresenter.this.getView().commitReportSuccess(bean);
+ }
+ }
+
+ @Override
+ public void fail(String msg) {
+ if (IndexPresenter.this.getView() != null) {
+ IndexPresenter.this.getView().showError(msg);
+ }
+ }
+ });
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/custom/CustomFileType.java b/app/src/main/java/com/ckkj/water/custom/CustomFileType.java
new file mode 100644
index 0000000..f257eac
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/custom/CustomFileType.java
@@ -0,0 +1,38 @@
+package com.ckkj.water.custom;
+
+import me.rosuh.filepicker.filetype.FileType;
+import com.ckkj.water.R;
+
+import org.jetbrains.annotations.NotNull;
+
+public class CustomFileType implements FileType {
+
+@Override
+public int getFileIconResId() {
+ return R.drawable.ic_unknown_file_picker;
+}
+
+@NotNull
+@Override
+public String getFileType() {
+ return "Excel";
+}
+
+@Override
+public boolean verify(@NotNull String fileName) {
+ boolean isHasSuffix = fileName.contains(".");
+ if (!isHasSuffix) {
+ // 如果没有 . 符号,即是没有文件后缀
+ return false;
+ }
+ String suffix = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
+
+ return "pdf".equals(suffix)
+ || "ppt".equals(suffix)
+ || "pptx".equals(suffix)
+ || "doc".equals(suffix)
+ || "docx".equals(suffix)
+ || "xls".equals(suffix)
+ || "xlsx".equals(suffix);
+}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ckkj/water/custom/CustomHorizontalBubbleAttachPopup.java b/app/src/main/java/com/ckkj/water/custom/CustomHorizontalBubbleAttachPopup.java
new file mode 100644
index 0000000..a46d203
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/custom/CustomHorizontalBubbleAttachPopup.java
@@ -0,0 +1,53 @@
+//package com.ckkj.water.custom;
+//
+//import android.content.Context;
+//import android.graphics.Color;
+//import android.view.View;
+//import android.widget.Toast;
+//
+//import androidx.annotation.NonNull;
+//
+//import com.lxj.xpopup.core.BubbleHorizontalAttachPopupView;
+//import com.lxj.xpopup.util.XPopupUtils;
+//import com.ckkj.water.R;
+//
+///**
+// * Description: 自定义Attach弹窗,水平方向的带气泡的弹窗
+// * Create by lxj, at 2019/3/13
+// */
+//public class CustomHorizontalBubbleAttachPopup extends BubbleHorizontalAttachPopupView {
+// private Context mContext;
+// public CustomHorizontalBubbleAttachPopup(@NonNull Context context) {
+// super(context);
+// this.mContext = context;
+// }
+//
+// @Override
+// protected int getImplLayoutId() {
+// return R.layout.custom_attach_popup;
+// }
+//
+// @Override
+// protected void onCreate() {
+// super.onCreate();
+// setBubbleBgColor(Color.parseColor("#4D5063"));
+// setBubbleShadowSize(XPopupUtils.dp2px(getContext(), 3));
+// setBubbleShadowColor(Color.BLACK);
+// getPopupImplView().setBackgroundResource(0);
+// findViewById(R.id.tv_zan).setOnClickListener(new OnClickListener() {
+// @Override
+// public void onClick(View v) {
+// Toast.makeText(mContext, "赞", Toast.LENGTH_LONG).show();
+// dismiss();
+// }
+// });
+// findViewById(R.id.tv_comment).setOnClickListener(new OnClickListener() {
+// @Override
+// public void onClick(View v) {
+// Toast.makeText(mContext, "评论", Toast.LENGTH_LONG).show();
+// dismiss();
+// }
+// });
+// }
+//
+//}
diff --git a/app/src/main/java/com/ckkj/water/custom/CustomTimeFilterPopupView.java b/app/src/main/java/com/ckkj/water/custom/CustomTimeFilterPopupView.java
new file mode 100644
index 0000000..71f2bbb
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/custom/CustomTimeFilterPopupView.java
@@ -0,0 +1,144 @@
+package com.ckkj.water.custom;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+
+import com.bigkoo.pickerview.builder.TimePickerBuilder;
+import com.bigkoo.pickerview.listener.OnTimeSelectListener;
+import com.bigkoo.pickerview.view.TimePickerView;
+import com.ckkj.water.R;
+import com.ckkj.water.utils.show.ToastUtil;
+import com.ckkj.water.utils.time.ZYTimeUtils;
+import com.lxj.xpopup.impl.PartShadowPopupView;
+
+import java.util.Date;
+
+/**
+ * Description: 自定义局部阴影弹窗
+ * Create by dance, at
+ */
+public class CustomTimeFilterPopupView extends PartShadowPopupView implements View.OnClickListener {
+ private Context mContext;
+ private String mStartTime,mEndTime;
+ private OnClickListener listener;
+
+ public CustomTimeFilterPopupView(@NonNull Context context,OnClickListener listener) {
+ super(context);
+ this.mContext = context;
+ this.listener = listener;
+ }
+
+ public interface OnClickListener{
+ void callBack(String startTime,String endTime);
+ }
+
+ public void setOnclickListener(OnClickListener listener){
+ this.listener = listener;
+ }
+ @Override
+ protected int getImplLayoutId() {
+ return R.layout.history_filter_time_dialog;
+ }
+
+ TextView tvStartTime,tvEndTime,tvReset,tvFinish;
+ @Override
+ protected void onCreate() {
+ super.onCreate();
+ tvStartTime = findViewById(R.id.tv_start_time);
+ tvEndTime = findViewById(R.id.tv_end_time);
+ tvReset = findViewById(R.id.tv_reset);
+ tvFinish = findViewById(R.id.tv_finish);
+ tvStartTime.setOnClickListener(this);
+ tvEndTime.setOnClickListener(this);
+ tvReset.setOnClickListener(this);
+ tvFinish.setOnClickListener(this);
+ }
+
+
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()){
+ case R.id.tv_start_time:
+ boolean times[] = {true, true, true, false, false, false};
+ //时间选择器
+ TimePickerView pvTime = new TimePickerBuilder(mContext, new OnTimeSelectListener() {
+ @Override
+ public void onTimeSelect(Date date, View v) {
+ tvStartTime.setText(ZYTimeUtils.getStrTimeData_YYYY_MM_dd(date));
+ mStartTime = ZYTimeUtils.getStrTimeData_YYYY_MM_dd(date);
+ }
+ }).setType(times)
+ .setCancelColor(mContext.getColor(R.color.color_999999))
+ .setSubmitColor(mContext.getColor(R.color.color_zhu))
+ .setTextColorCenter(mContext.getColor(R.color.color_zhu))
+ .build();
+ pvTime.show();
+
+ break;
+
+ case R.id.tv_end_time:
+ boolean endTimes[] = {true, true, true, false, false, false};
+ //时间选择器
+ TimePickerView pvEndTime = new TimePickerBuilder(mContext, new OnTimeSelectListener() {
+ @Override
+ public void onTimeSelect(Date date, View v) {
+ tvEndTime.setText(ZYTimeUtils.getStrTimeData_YYYY_MM_dd(date));
+ mEndTime = ZYTimeUtils.getStrTimeData_YYYY_MM_dd(date);
+ }
+ }).setType(endTimes)
+ .setCancelColor(mContext.getColor(R.color.color_999999))
+ .setSubmitColor(mContext.getColor(R.color.color_zhu))
+ .setTextColorCenter(mContext.getColor(R.color.color_zhu))
+ .build();
+ pvEndTime.show();
+ break;
+
+ case R.id.tv_reset:
+ tvStartTime.setText("");
+ tvEndTime.setText("");
+ mStartTime = "";
+ mEndTime = "";
+ break;
+
+ case R.id.tv_finish:
+ if(TextUtils.isEmpty(mStartTime)){
+ ToastUtil.showShort(mContext,mContext.getString(R.string.history_filter_start_time_toast));
+ return;
+ }
+ if(TextUtils.isEmpty(mEndTime)){
+ ToastUtil.showShort(mContext,mContext.getString(R.string.history_filter_end_time_toast));
+ return;
+ }
+ // 比较结束时间和开始时间,结束时间不能早于开始时间
+ if (!ZYTimeUtils.isEndTimeAfterStartTime(mStartTime, mEndTime)) {
+ ToastUtil.showShort(mContext, mContext.getString(R.string.history_filter_end_time_error));
+ return;
+ }
+
+ if(listener != null){
+ listener.callBack(mStartTime,mEndTime);
+ this.dismiss();
+ }
+ break;
+ }
+ }
+
+ @Override
+ protected void onShow() {
+ super.onShow();
+ Log.e("tag","CustomPartShadowPopupView onShow");
+ }
+
+ @Override
+ protected void onDismiss() {
+ super.onDismiss();
+ Log.e("tag","CustomPartShadowPopupView onDismiss");
+ }
+
+
+}
diff --git a/app/src/main/java/com/ckkj/water/custom/CustomTypeFilterPopupView.java b/app/src/main/java/com/ckkj/water/custom/CustomTypeFilterPopupView.java
new file mode 100644
index 0000000..19e6731
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/custom/CustomTypeFilterPopupView.java
@@ -0,0 +1,128 @@
+package com.ckkj.water.custom;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+
+import com.bigkoo.pickerview.builder.TimePickerBuilder;
+import com.bigkoo.pickerview.listener.OnTimeSelectListener;
+import com.bigkoo.pickerview.view.TimePickerView;
+import com.ckkj.water.R;
+import com.ckkj.water.utils.show.ToastUtil;
+import com.ckkj.water.utils.time.ZYTimeUtils;
+import com.lxj.xpopup.impl.PartShadowPopupView;
+
+import java.util.Date;
+
+/**
+ * Description: 自定义局部阴影弹窗
+ * Create by dance, at
+ */
+public class CustomTypeFilterPopupView extends PartShadowPopupView implements View.OnClickListener {
+ private Context mContext;
+ private OnClickListener listener;
+ private TextView tvManual,tvAuto,tvReset,tvFinish;
+ //是否选择了手动
+ private boolean mSelManual = false;
+ //是否选择了自动排程
+ private boolean mSelAuto = false;
+ public CustomTypeFilterPopupView(@NonNull Context context, OnClickListener listener) {
+ super(context);
+ this.mContext = context;
+ this.listener = listener;
+ }
+
+ public interface OnClickListener{
+ void callBack(boolean mSelManual,boolean mSelAuto);
+ }
+
+ public void setOnclickListener(OnClickListener listener){
+ this.listener = listener;
+ }
+ @Override
+ protected int getImplLayoutId() {
+ return R.layout.history_filter_type_dialog;
+ }
+
+ @Override
+ protected void onCreate() {
+ super.onCreate();
+ tvManual = findViewById(R.id.tv_manual);
+ tvAuto = findViewById(R.id.tv_auto);
+ tvReset = findViewById(R.id.tv_reset);
+ tvFinish = findViewById(R.id.tv_finish);
+ tvManual.setOnClickListener(this);
+ tvAuto.setOnClickListener(this);
+ tvReset.setOnClickListener(this);
+ tvFinish.setOnClickListener(this);
+ tvManual.setSelected(false);
+ }
+
+
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()){
+ case R.id.tv_manual:
+ if(mSelManual){
+ mSelManual = false;
+ tvManual.setTextColor(mContext.getColor(R.color.color_zhu));
+ tvManual.setSelected(mSelManual);
+ }else{
+ mSelManual = true;
+ tvManual.setTextColor(mContext.getColor(R.color.white));
+ tvManual.setSelected(mSelManual);
+ }
+ break;
+
+ case R.id.tv_auto:
+ if(mSelAuto){
+ mSelAuto = false;
+ tvAuto.setTextColor(mContext.getColor(R.color.color_zhu));
+ tvAuto.setSelected(mSelAuto);
+ }else{
+ mSelAuto = true;
+ tvAuto.setTextColor(mContext.getColor(R.color.white));
+ tvAuto.setSelected(mSelAuto);
+ }
+ break;
+
+ case R.id.tv_reset:
+ mSelManual = false;
+ mSelAuto = false;
+ tvManual.setTextColor(mContext.getColor(R.color.color_zhu));
+ tvAuto.setTextColor(mContext.getColor(R.color.color_zhu));
+ tvManual.setSelected(mSelManual);
+ tvAuto.setSelected(mSelAuto);
+ break;
+
+ case R.id.tv_finish:
+ if(!mSelManual && !mSelAuto){
+ ToastUtil.showShort(mContext,mContext.getString(R.string.history_filter_type_toast));
+ return;
+ }
+ if(listener != null){
+ listener.callBack(mSelManual,mSelAuto);
+ this.dismiss();
+ }
+ break;
+ }
+ }
+
+ @Override
+ protected void onShow() {
+ super.onShow();
+ Log.e("tag","CustomPartShadowPopupView onShow");
+ }
+
+ @Override
+ protected void onDismiss() {
+ super.onDismiss();
+ Log.e("tag","CustomPartShadowPopupView onDismiss");
+ }
+
+
+}
diff --git a/app/src/main/java/com/ckkj/water/custom/DividerItemDecoration.java b/app/src/main/java/com/ckkj/water/custom/DividerItemDecoration.java
new file mode 100644
index 0000000..7872cd8
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/custom/DividerItemDecoration.java
@@ -0,0 +1,31 @@
+package com.ckkj.water.custom;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.view.View;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+public class DividerItemDecoration extends RecyclerView.ItemDecoration {
+ private final Paint paint;
+ private final int height;
+
+ public DividerItemDecoration(int color, int height) {
+ paint = new Paint();
+ paint.setColor(color);
+ this.height = height;
+ }
+
+ @Override
+ public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
+ int left = parent.getPaddingLeft();
+ int right = parent.getWidth() - parent.getPaddingRight();
+
+ for (int i = 0; i < parent.getChildCount() - 1; i++) {
+ View child = parent.getChildAt(i);
+ int top = child.getBottom();
+ int bottom = top + height;
+ c.drawRect(left, top, right, bottom, paint);
+ }
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/custom/GridSpacingItemDecoration.java b/app/src/main/java/com/ckkj/water/custom/GridSpacingItemDecoration.java
new file mode 100644
index 0000000..91198cc
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/custom/GridSpacingItemDecoration.java
@@ -0,0 +1,41 @@
+package com.ckkj.water.custom;
+
+import android.graphics.Rect;
+import android.view.View;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
+ private final int spanCount; // 列数
+ private final int spacing; // 间距
+ private final boolean includeEdge; // 是否包含边缘
+
+ public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) {
+ this.spanCount = spanCount;
+ this.spacing = spacing;
+ this.includeEdge = includeEdge;
+ }
+
+ @Override
+ public void getItemOffsets(@NonNull Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
+ int position = parent.getChildAdapterPosition(view); // item 位置
+ int column = position % spanCount; // 列数
+
+ if (includeEdge) {
+ outRect.left = spacing - column * spacing / spanCount; // 左边距
+ outRect.right = (column + 1) * spacing / spanCount; // 右边距
+
+ if (position < spanCount) { // 第一行
+ outRect.top = spacing; // 上边距
+ }
+ outRect.bottom = spacing; // 下边距
+ } else {
+ outRect.left = column * spacing / spanCount; // 左边距
+ outRect.right = spacing - (column + 1) * spacing / spanCount; // 右边距
+
+ if (position >= spanCount) {
+ outRect.top = spacing; // 上边距
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/custom/helper/KeyEventCompat.java b/app/src/main/java/com/ckkj/water/custom/helper/KeyEventCompat.java
new file mode 100644
index 0000000..2d4f781
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/custom/helper/KeyEventCompat.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.ckkj.water.custom.helper;
+
+import android.view.KeyEvent;
+import android.view.View;
+
+/**
+ * Helper for accessing features in {@link KeyEvent} introduced after
+ * API level 4 in a backwards compatible fashion.
+ */
+public final class KeyEventCompat {
+ /**
+ * Interface for the full API.
+ */
+ interface KeyEventVersionImpl {
+ int normalizeMetaState(int metaState);
+ boolean metaStateHasModifiers(int metaState, int modifiers);
+ boolean metaStateHasNoModifiers(int metaState);
+ boolean isCtrlPressed(KeyEvent event);
+ }
+
+ /**
+ * Interface implementation that doesn't use anything about v4 APIs.
+ */
+ static class BaseKeyEventVersionImpl implements KeyEventVersionImpl {
+ private static final int META_MODIFIER_MASK =
+ KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_RIGHT_ON
+ | KeyEvent.META_ALT_ON | KeyEvent.META_ALT_LEFT_ON | KeyEvent.META_ALT_RIGHT_ON
+ | KeyEvent.META_SYM_ON;
+
+ // Mask of all lock key meta states.
+ private static final int META_ALL_MASK = META_MODIFIER_MASK;
+
+ private static int metaStateFilterDirectionalModifiers(int metaState,
+ int modifiers, int basic, int left, int right) {
+ final boolean wantBasic = (modifiers & basic) != 0;
+ final int directional = left | right;
+ final boolean wantLeftOrRight = (modifiers & directional) != 0;
+
+ if (wantBasic) {
+ if (wantLeftOrRight) {
+ throw new IllegalArgumentException("bad arguments");
+ }
+ return metaState & ~directional;
+ } else if (wantLeftOrRight) {
+ return metaState & ~basic;
+ } else {
+ return metaState;
+ }
+ }
+
+ @Override
+ public int normalizeMetaState(int metaState) {
+ if ((metaState & (KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_RIGHT_ON)) != 0) {
+ metaState |= KeyEvent.META_SHIFT_ON;
+ }
+ if ((metaState & (KeyEvent.META_ALT_LEFT_ON | KeyEvent.META_ALT_RIGHT_ON)) != 0) {
+ metaState |= KeyEvent.META_ALT_ON;
+ }
+ return metaState & META_ALL_MASK;
+ }
+
+ @Override
+ public boolean metaStateHasModifiers(int metaState, int modifiers) {
+ metaState = normalizeMetaState(metaState) & META_MODIFIER_MASK;
+ metaState = metaStateFilterDirectionalModifiers(metaState, modifiers,
+ KeyEvent.META_SHIFT_ON, KeyEvent.META_SHIFT_LEFT_ON, KeyEvent.META_SHIFT_RIGHT_ON);
+ metaState = metaStateFilterDirectionalModifiers(metaState, modifiers,
+ KeyEvent.META_ALT_ON, KeyEvent.META_ALT_LEFT_ON, KeyEvent.META_ALT_RIGHT_ON);
+ return metaState == modifiers;
+ }
+
+ @Override
+ public boolean metaStateHasNoModifiers(int metaState) {
+ return (normalizeMetaState(metaState) & META_MODIFIER_MASK) == 0;
+ }
+
+ @Override
+ public boolean isCtrlPressed(KeyEvent event) {
+ return false;
+ }
+ }
+
+ /**
+ * Interface implementation for devices with at least v11 APIs.
+ */
+ static class HoneycombKeyEventVersionImpl extends BaseKeyEventVersionImpl {
+ @Override
+ public int normalizeMetaState(int metaState) {
+ return KeyEventCompatHoneycomb.normalizeMetaState(metaState);
+ }
+
+ @Override
+ public boolean metaStateHasModifiers(int metaState, int modifiers) {
+ return KeyEventCompatHoneycomb.metaStateHasModifiers(metaState, modifiers);
+ }
+
+ @Override
+ public boolean metaStateHasNoModifiers(int metaState) {
+ return KeyEventCompatHoneycomb.metaStateHasNoModifiers(metaState);
+ }
+
+ @Override
+ public boolean isCtrlPressed(KeyEvent event) {
+ return KeyEventCompatHoneycomb.isCtrlPressed(event);
+ }
+ }
+
+ /**
+ * Select the correct implementation to use for the current platform.
+ */
+ static final KeyEventVersionImpl IMPL;
+ static {
+ if (android.os.Build.VERSION.SDK_INT >= 11) {
+ IMPL = new HoneycombKeyEventVersionImpl();
+ } else {
+ IMPL = new BaseKeyEventVersionImpl();
+ }
+ }
+
+ // -------------------------------------------------------------------
+
+ public static int normalizeMetaState(int metaState) {
+ return IMPL.normalizeMetaState(metaState);
+ }
+
+ public static boolean metaStateHasModifiers(int metaState, int modifiers) {
+ return IMPL.metaStateHasModifiers(metaState, modifiers);
+ }
+
+ public static boolean metaStateHasNoModifiers(int metaState) {
+ return IMPL.metaStateHasNoModifiers(metaState);
+ }
+
+ public static boolean hasModifiers(KeyEvent event, int modifiers) {
+ return IMPL.metaStateHasModifiers(event.getMetaState(), modifiers);
+ }
+
+ public static boolean hasNoModifiers(KeyEvent event) {
+ return IMPL.metaStateHasNoModifiers(event.getMetaState());
+ }
+
+ /**
+ * @deprecated Call {@link KeyEvent#startTracking()} directly. This method will be removed in a
+ * future release.
+ */
+ @Deprecated
+ public static void startTracking(KeyEvent event) {
+ event.startTracking();
+ }
+
+ /**
+ * @deprecated Call {@link KeyEvent#isTracking()} directly. This method will be removed in a
+ * future release.
+ */
+ @Deprecated
+ public static boolean isTracking(KeyEvent event) {
+ return event.isTracking();
+ }
+
+ /**
+ * @deprecated Call {@link View#getKeyDispatcherState()} directly. This method will be removed
+ * in a future release.
+ */
+ @Deprecated
+ public static Object getKeyDispatcherState(View view) {
+ return view.getKeyDispatcherState();
+ }
+
+ /**
+ * @deprecated Call
+ * {@link KeyEvent#dispatch(KeyEvent.Callback, KeyEvent.DispatcherState, Object)} directly.
+ * This method will be removed in a future release.
+ */
+ @Deprecated
+ public static boolean dispatch(KeyEvent event, KeyEvent.Callback receiver, Object state,
+ Object target) {
+ return event.dispatch(receiver, (KeyEvent.DispatcherState)state, target);
+ }
+
+ public static boolean isCtrlPressed(KeyEvent event) {
+ return IMPL.isCtrlPressed(event);
+ }
+
+ private KeyEventCompat() {}
+}
diff --git a/app/src/main/java/com/ckkj/water/custom/helper/KeyEventCompatHoneycomb.java b/app/src/main/java/com/ckkj/water/custom/helper/KeyEventCompatHoneycomb.java
new file mode 100644
index 0000000..264af56
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/custom/helper/KeyEventCompatHoneycomb.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ckkj.water.custom.helper;
+
+import android.annotation.TargetApi;
+import android.view.KeyEvent;
+
+import androidx.annotation.RequiresApi;
+
+/**
+ * Implementation of key event compatibility that can call Honeycomb APIs.
+ */
+
+@RequiresApi(11)
+@TargetApi(11)
+class KeyEventCompatHoneycomb {
+ public static int normalizeMetaState(int metaState) {
+ return KeyEvent.normalizeMetaState(metaState);
+ }
+
+ public static boolean metaStateHasModifiers(int metaState, int modifiers) {
+ return KeyEvent.metaStateHasModifiers(metaState, modifiers);
+ }
+
+ public static boolean metaStateHasNoModifiers(int metaState) {
+ return KeyEvent.metaStateHasNoModifiers(metaState);
+ }
+
+ public static boolean isCtrlPressed(KeyEvent event) {
+ return event.isCtrlPressed();
+ }
+}
+
diff --git a/app/src/main/java/com/ckkj/water/custom/view/CircleCheckView.java b/app/src/main/java/com/ckkj/water/custom/view/CircleCheckView.java
new file mode 100644
index 0000000..ca27c60
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/custom/view/CircleCheckView.java
@@ -0,0 +1,104 @@
+package com.ckkj.water.custom.view;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.util.AttributeSet;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+public class CircleCheckView extends View {
+ private Paint circlePaint;
+ private Paint checkPaint;
+ private int backgroundColor = 0xFF000000; // 默认黑色
+ private boolean showCheck = false; // 是否显示对号
+
+ public CircleCheckView(Context context) {
+ super(context);
+ init();
+ }
+
+ public CircleCheckView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public CircleCheckView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ private void init() {
+ circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ circlePaint.setColor(backgroundColor);
+ circlePaint.setStyle(Paint.Style.FILL);
+
+ checkPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ checkPaint.setColor(0xFFFFFFFF); // 默认白色对号
+ checkPaint.setStyle(Paint.Style.STROKE);
+ checkPaint.setStrokeWidth(8f); // 对号线宽
+ checkPaint.setStrokeCap(Paint.Cap.ROUND);
+ checkPaint.setStrokeJoin(Paint.Join.ROUND);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ int width = getWidth();
+ int height = getHeight();
+ int radius = Math.min(width, height) / 2;
+
+ // 画圆
+ canvas.drawCircle(width / 2f, height / 2f, radius, circlePaint);
+
+ // 画对号
+ if (showCheck) {
+ drawCheckMark(canvas, width, height);
+ }
+ }
+
+ private void drawCheckMark(Canvas canvas, int width, int height) {
+ Path path = new Path();
+ float size = Math.min(width, height) * 0.2f; // 对号大小大约是圆直径40%
+ float centerX = width / 2f;
+ float centerY = height / 2f;
+
+ // 起点 (左下)
+ path.moveTo(centerX - size / 2, centerY);
+ // 中间点 (中间)
+ path.lineTo(centerX, centerY + size / 2);
+ // 终点 (右上)
+ path.lineTo(centerX + size / 2, centerY - size / 2);
+
+ canvas.drawPath(path, checkPaint);
+ }
+
+ // 外部调用,设置背景色
+ @Override
+ public void setBackgroundColor(int color) {
+ backgroundColor = color;
+ circlePaint.setColor(backgroundColor);
+ invalidate();
+ }
+
+ // 外部调用,控制显示或隐藏对号
+ public void setShowCheck(boolean show) {
+ this.showCheck = show;
+ invalidate();
+ }
+
+ // 外部调用,可以改变对号颜色
+ public void setCheckColor(int color) {
+ checkPaint.setColor(color);
+ invalidate();
+ }
+
+ // 外部调用,可以改变对号粗细
+ public void setCheckStrokeWidth(float width) {
+ checkPaint.setStrokeWidth(width);
+ invalidate();
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/custom/view/CustomAttachPopup.java b/app/src/main/java/com/ckkj/water/custom/view/CustomAttachPopup.java
new file mode 100644
index 0000000..5f1cb5b
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/custom/view/CustomAttachPopup.java
@@ -0,0 +1,38 @@
+package com.ckkj.water.custom.view;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+
+import com.ckkj.water.R;
+import com.lxj.xpopup.core.AttachPopupView;
+
+/**
+ * Description: 自定义背景的Attach弹窗
+ * Create by lxj, at 2019/3/13
+ */
+public class CustomAttachPopup extends AttachPopupView {
+ public CustomAttachPopup(@NonNull Context context) {
+ super(context);
+ }
+
+ @Override
+ protected int getImplLayoutId() {
+ return R.layout.custom_attach_popup;
+ }
+
+ @Override
+ protected void onCreate() {
+ super.onCreate();
+ final TextView tv = findViewById(R.id.tv_content);
+ tv.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+// tv.setText(tv.getText() + "\n 啊哈哈哈啊哈");
+// tv.setText("\n 啊哈哈哈啊哈");
+ }
+ });
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/custom/view/CustomBubbleAttachPopup.java b/app/src/main/java/com/ckkj/water/custom/view/CustomBubbleAttachPopup.java
new file mode 100644
index 0000000..3eb618a
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/custom/view/CustomBubbleAttachPopup.java
@@ -0,0 +1,39 @@
+package com.ckkj.water.custom.view;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+
+import com.bumptech.glide.Glide;
+import com.ckkj.water.R;
+import com.lxj.xpopup.core.BubbleAttachPopupView;
+import com.lxj.xpopup.util.XPopupUtils;
+
+/**
+ * Description: 自定义气泡Attach弹窗
+ * Create by lxj, at 2019/3/13
+ */
+public class CustomBubbleAttachPopup extends BubbleAttachPopupView {
+ private String mContent;
+ public CustomBubbleAttachPopup(@NonNull Context context,String content) {
+ super(context);
+ this.mContent = content;
+ }
+
+ @Override
+ protected int getImplLayoutId() {
+ return R.layout.custom_bubble_attach_popup;
+ }
+
+ @Override
+ protected void onCreate() {
+ super.onCreate();
+ final TextView tv = findViewById(R.id.tv);
+ tv.setText(mContent);
+ }
+
+}
diff --git a/app/src/main/java/com/ckkj/water/custom/view/CustomNestedScrollView.java b/app/src/main/java/com/ckkj/water/custom/view/CustomNestedScrollView.java
new file mode 100644
index 0000000..eb74179
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/custom/view/CustomNestedScrollView.java
@@ -0,0 +1,37 @@
+package com.ckkj.water.custom.view;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+import androidx.core.widget.NestedScrollView;
+
+public class CustomNestedScrollView extends NestedScrollView {
+ private boolean isScrollable = true;
+
+ public CustomNestedScrollView(Context context) {
+ super(context);
+ }
+
+ public CustomNestedScrollView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CustomNestedScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public void setScrollable(boolean scrollable) {
+ this.isScrollable = scrollable;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ return isScrollable && super.onInterceptTouchEvent(ev);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ return isScrollable && super.onTouchEvent(ev);
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/custom/view/CustomNoTouchRecyclerView.java b/app/src/main/java/com/ckkj/water/custom/view/CustomNoTouchRecyclerView.java
new file mode 100644
index 0000000..f810a26
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/custom/view/CustomNoTouchRecyclerView.java
@@ -0,0 +1,27 @@
+package com.ckkj.water.custom.view;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+public class CustomNoTouchRecyclerView extends RecyclerView {
+ public CustomNoTouchRecyclerView(Context context) {
+ super(context);
+ }
+
+ public CustomNoTouchRecyclerView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CustomNoTouchRecyclerView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ // 返回 false 以便父布局可以处理触摸事件
+ return false;
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/custom/view/CustomProgressBar.java b/app/src/main/java/com/ckkj/water/custom/view/CustomProgressBar.java
new file mode 100644
index 0000000..448d852
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/custom/view/CustomProgressBar.java
@@ -0,0 +1,46 @@
+package com.ckkj.water.custom.view;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.ckkj.water.R;
+
+public class CustomProgressBar extends View {
+ private Bitmap bitmap;
+ private Paint paint;
+ private float rotation = 0;
+
+ public CustomProgressBar(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ private void init() {
+ paint = new Paint();
+ // 加载自定义图片
+ bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.img_loading_progressbar); // 替换为你的图片资源
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ // 保存当前画布状态
+ canvas.save();
+ // 旋转画布
+ canvas.rotate(rotation, getWidth() / 2, getHeight() / 2);
+ // 绘制图片
+ canvas.drawBitmap(bitmap, (getWidth() - bitmap.getWidth()) / 2, (getHeight() - bitmap.getHeight()) / 2, paint);
+ // 恢复画布状态
+ canvas.restore();
+ }
+
+ public void setRotation(float rotation) {
+ this.rotation = rotation;
+ invalidate(); // 重新绘制
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/custom/view/CustomRefreshHeader.java b/app/src/main/java/com/ckkj/water/custom/view/CustomRefreshHeader.java
new file mode 100644
index 0000000..627b58a
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/custom/view/CustomRefreshHeader.java
@@ -0,0 +1,130 @@
+package com.ckkj.water.custom.view;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.LinearInterpolator;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+
+import com.scwang.smart.refresh.layout.api.RefreshHeader;
+import com.scwang.smart.refresh.layout.api.RefreshKernel;
+import com.scwang.smart.refresh.layout.api.RefreshLayout;
+import com.scwang.smart.refresh.layout.constant.RefreshState;
+import com.scwang.smart.refresh.layout.constant.SpinnerStyle;
+import com.ckkj.water.R;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/11
+ * @description TODO: 描述该类的作用
+ */
+
+public class CustomRefreshHeader extends RelativeLayout implements RefreshHeader {
+
+ private ImageView ivRefresh;
+ private ObjectAnimator rotateAnimator;
+
+ public CustomRefreshHeader(Context context) {
+ super(context);
+ init(context);
+ }
+
+ public CustomRefreshHeader(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ private void init(Context context) {
+ LayoutInflater.from(context).inflate(R.layout.view_refresh_header, this);
+ ivRefresh = findViewById(R.id.ivRefresh);
+
+ // 初始化 ObjectAnimator
+ rotateAnimator = ObjectAnimator.ofFloat(ivRefresh, "rotation", 0f, 360f);
+ rotateAnimator.setDuration(1500);
+ rotateAnimator.setRepeatCount(ValueAnimator.INFINITE);
+ rotateAnimator.setInterpolator(new LinearInterpolator()); // 匀速旋转
+ }
+
+ @NonNull
+ @Override
+ public View getView() {
+ return this;
+ }
+
+ @SuppressLint("RestrictedApi")
+ @Override
+ public void onStartAnimator(@NonNull RefreshLayout layout, int height, int maxDragHeight) {
+ if (rotateAnimator != null && !rotateAnimator.isRunning()) {
+ rotateAnimator.start();
+ }
+ }
+
+ @SuppressLint("RestrictedApi")
+ @Override
+ public int onFinish(@NonNull RefreshLayout layout, boolean success) {
+ if (rotateAnimator != null && rotateAnimator.isRunning()) {
+ // 为了平滑停止,先设置减速插值器,再 cancel 动画
+ rotateAnimator.setInterpolator(new DecelerateInterpolator());
+ postDelayed(() -> {
+ if (rotateAnimator != null) {
+ rotateAnimator.cancel();
+ rotateAnimator.setInterpolator(new LinearInterpolator()); // 恢复为匀速
+ }
+ }, 300); // 给一点缓冲时间
+ }
+ return 500; // 动画停止后延迟 500ms 收起 Header
+ }
+
+ // --- 必须实现的方法 ---
+
+ @NonNull
+ @Override
+ public SpinnerStyle getSpinnerStyle() {
+ return SpinnerStyle.Translate; // Header 跟随内容滑动
+ }
+
+ @Override
+ public boolean isSupportHorizontalDrag() {
+ return false;
+ }
+
+ @SuppressLint("RestrictedApi")
+ @Override
+ public void setPrimaryColors(@ColorInt int... colors) {}
+
+ @SuppressLint("RestrictedApi")
+ @Override
+ public void onInitialized(@NonNull RefreshKernel kernel, int height, int maxDragHeight) {}
+
+ @SuppressLint("RestrictedApi")
+ @Override
+ public void onMoving(boolean isDragging, float percent, int offset, int height, int maxDragHeight) {}
+
+ @SuppressLint("RestrictedApi")
+ @Override
+ public void onReleased(@NonNull RefreshLayout layout, int height, int maxDragHeight) {}
+
+ @SuppressLint("RestrictedApi")
+ @Override
+ public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) {}
+
+ @Override
+ public boolean autoOpen(int duration, float dragRate, boolean animationOnly) {
+ return false;
+ }
+
+ @SuppressLint("RestrictedApi")
+ @Override
+ public void onStateChanged(@NonNull RefreshLayout refreshLayout, @NonNull RefreshState oldState, @NonNull RefreshState newState) {
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ckkj/water/custom/view/DashedLineView.java b/app/src/main/java/com/ckkj/water/custom/view/DashedLineView.java
new file mode 100644
index 0000000..5a4ede6
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/custom/view/DashedLineView.java
@@ -0,0 +1,42 @@
+package com.ckkj.water.custom.view;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+
+public class DashedLineView extends View {
+ private Paint paint;
+
+ public DashedLineView(Context context) {
+ super(context);
+ init();
+ }
+
+ public DashedLineView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public DashedLineView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ private void init() {
+ paint = new Paint();
+ paint.setColor(0xFF0000FF); // 蓝色
+ paint.setStrokeWidth(1f); // 高度为1px
+ paint.setStyle(Paint.Style.STROKE);
+ paint.setPathEffect(new android.graphics.DashPathEffect(new float[]{2, 2}, 0)); // 虚线效果
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ float width = getWidth();
+ float height = getHeight();
+ canvas.drawLine(0, height / 2, width, height / 2, paint); // 绘制虚线
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/custom/view/DottedRoundRectDrawable_blue.java b/app/src/main/java/com/ckkj/water/custom/view/DottedRoundRectDrawable_blue.java
new file mode 100644
index 0000000..8aa2504
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/custom/view/DottedRoundRectDrawable_blue.java
@@ -0,0 +1,51 @@
+package com.ckkj.water.custom.view;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.DashPathEffect;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+
+public class DottedRoundRectDrawable_blue extends Drawable {
+
+ private Paint mPaint;
+ private RectF mRectF;
+ private float mCornerRadius;
+
+ public DottedRoundRectDrawable_blue(float cornerRadius) {
+ mPaint = new Paint();
+ mPaint.setColor(Color.parseColor("#1A72FF")); // 设置虚线颜色
+ mPaint.setStyle(Paint.Style.STROKE); // 设置为描边
+ mPaint.setStrokeWidth(3); // 设置虚线宽度
+ // 设置虚线效果
+ mPaint.setPathEffect(new DashPathEffect(new float[]{2, 2}, 0)); // 虚线的长度和间隔
+
+ mCornerRadius = cornerRadius; // 圆角半径
+ mRectF = new RectF();
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ // 获取当前 Drawable 的边界
+ mRectF.set(getBounds());
+ // 绘制圆角矩形虚线
+ canvas.drawRoundRect(mRectF, mCornerRadius, mCornerRadius, mPaint);
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ mPaint.setAlpha(alpha);
+ }
+
+ @Override
+ public void setColorFilter(android.graphics.ColorFilter colorFilter) {
+ mPaint.setColorFilter(colorFilter);
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/custom/view/DottedRoundRectDrawable_gray.java b/app/src/main/java/com/ckkj/water/custom/view/DottedRoundRectDrawable_gray.java
new file mode 100644
index 0000000..a6a9d3e
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/custom/view/DottedRoundRectDrawable_gray.java
@@ -0,0 +1,51 @@
+package com.ckkj.water.custom.view;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.DashPathEffect;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+
+public class DottedRoundRectDrawable_gray extends Drawable {
+
+ private Paint mPaint;
+ private RectF mRectF;
+ private float mCornerRadius;
+
+ public DottedRoundRectDrawable_gray(float cornerRadius) {
+ mPaint = new Paint();
+ mPaint.setColor(Color.parseColor("#BDBDBD")); // 设置虚线颜色
+ mPaint.setStyle(Paint.Style.STROKE); // 设置为描边
+ mPaint.setStrokeWidth(3); // 设置虚线宽度
+ // 设置虚线效果
+ mPaint.setPathEffect(new DashPathEffect(new float[]{2, 2}, 0)); // 虚线的长度和间隔
+
+ mCornerRadius = cornerRadius; // 圆角半径
+ mRectF = new RectF();
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ // 获取当前 Drawable 的边界
+ mRectF.set(getBounds());
+ // 绘制圆角矩形虚线
+ canvas.drawRoundRect(mRectF, mCornerRadius, mCornerRadius, mPaint);
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ mPaint.setAlpha(alpha);
+ }
+
+ @Override
+ public void setColorFilter(android.graphics.ColorFilter colorFilter) {
+ mPaint.setColorFilter(colorFilter);
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/custom/view/HackyViewPager.java b/app/src/main/java/com/ckkj/water/custom/view/HackyViewPager.java
new file mode 100644
index 0000000..c3929fa
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/custom/view/HackyViewPager.java
@@ -0,0 +1,40 @@
+package com.ckkj.water.custom.view;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+import androidx.viewpager.widget.ViewPager;
+
+/**
+ * Hacky fix for Issue #4 and
+ * http://code.google.com/p/android/issues/detail?id=18990
+ *
+ * ScaleGestureDetector seems to mess up the touch events, which means that
+ * ViewGroups which make use of onInterceptTouchEvent throw a lot of
+ * IllegalArgumentException: pointerIndex out of range.
+ *
+ * There's not much I can do in my code for now, but we can mask the result by
+ * just catching the problem and ignoring it.
+ *
+ * @author Chris Banes
+ */
+public class HackyViewPager extends ViewPager {
+
+ public HackyViewPager(Context context) {
+ super(context);
+ }
+
+ public HackyViewPager(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ try {
+ return super.onInterceptTouchEvent(ev);
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ckkj/water/custom/view/LazyViewPager.java b/app/src/main/java/com/ckkj/water/custom/view/LazyViewPager.java
new file mode 100644
index 0000000..1df258c
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/custom/view/LazyViewPager.java
@@ -0,0 +1,1716 @@
+package com.ckkj.water.custom.view;
+
+/* * Copyright (C) 2011 The Android Open Source Project
+ * * Licensed under the Apache License, Version 2.0 (the "License");
+ * * you may not use this file except in compliance with the License.
+ * * You may obtain a copy of the License at
+ * * http://www.apache.org/licenses/LICENSE-2.0
+ * * Unless required by applicable law or agreed to in writing, software
+ * * distributed under the License is distributed on an "AS IS" BASIS,
+ * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * * See the License for the specific language governing permissions and *
+ * limitations under the License. */
+
+import android.content.Context;
+import android.database.DataSetObserver;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.FocusFinder;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.SoundEffectConstants;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.animation.Interpolator;
+import android.widget.Scroller;
+
+
+import androidx.core.os.ParcelableCompat;
+import androidx.core.os.ParcelableCompatCreatorCallbacks;
+import androidx.core.view.MotionEventCompat;
+import androidx.core.view.VelocityTrackerCompat;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.ViewConfigurationCompat;
+import androidx.core.widget.EdgeEffectCompat;
+import androidx.viewpager.widget.PagerAdapter;
+
+import com.ckkj.water.custom.helper.KeyEventCompat;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+/**
+ * Layout manager that allows the user to flip left and right through pages of * data. You supply an implementation of a * {@link PagerAdapter} to generate the pages that the * view shows. * * * Note this class is currently under early design and development. The API will * likely change in later updates of the compatibility library, requiring * changes to the source code of apps when they are compiled against the newer * version. *
+ */
+public class LazyViewPager extends ViewGroup {
+ private static final String TAG = "LazyViewPager";
+ private static final boolean DEBUG = false;
+
+ private static final boolean USE_CACHE = false;
+
+ private static final int DEFAULT_OFFSCREEN_PAGES = 0;// 默认的加载页面,为0表示不预加载下一个界面
+ private static final int MAX_SETTLE_DURATION = 600; // ms
+
+ static class ItemInfo {
+ Object object;
+ int position;
+ boolean scrolling;
+ }
+
+ private static final Comparator COMPARATOR = new Comparator() {
+ @Override
+ public int compare(ItemInfo lhs, ItemInfo rhs) {
+ return lhs.position - rhs.position;
+ }
+ };
+
+ private static final Interpolator sInterpolator = new Interpolator() {
+ public float getInterpolation(float t) {
+ // _o(t) = t * t * ((tension + 1) * t + tension)
+ // o(t) = _o(t - 1) + 1
+ t -= 1.0f;
+ return t * t * t + 1.0f;
+ }
+ };
+
+ private final ArrayList mItems = new ArrayList();
+
+ private PagerAdapter mAdapter;
+ private int mCurItem; // Index of currently displayed page.
+ private int mRestoredCurItem = -1;
+ private Parcelable mRestoredAdapterState = null;
+ private ClassLoader mRestoredClassLoader = null;
+ private Scroller mScroller;
+ private PagerObserver mObserver;
+
+ private int mPageMargin;
+ private Drawable mMarginDrawable;
+
+ private int mChildWidthMeasureSpec;
+ private int mChildHeightMeasureSpec;
+ private boolean mInLayout;
+
+ private boolean mScrollingCacheEnabled;
+
+ private boolean mPopulatePending;
+ private boolean mScrolling;
+ private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;
+
+ private boolean mIsBeingDragged;
+ private boolean mIsUnableToDrag;
+ private int mTouchSlop;
+ private float mInitialMotionX;
+ /**
+ * Position of the last motion event.
+ */
+ private float mLastMotionX;
+ private float mLastMotionY;
+ /**
+ * ID of the active pointer. This is used to retain consistency during * drags/flings if multiple pointers are used.
+ */
+ private int mActivePointerId = INVALID_POINTER;
+ /**
+ * Sentinel value for no current active pointer. Used by * {@link #mActivePointerId}.
+ */
+ private static final int INVALID_POINTER = -1;
+
+ /**
+ * Determines speed during touch scrolling
+ */
+ private VelocityTracker mVelocityTracker;
+ private int mMinimumVelocity;
+ private int mMaximumVelocity;
+ private float mBaseLineFlingVelocity;
+ private float mFlingVelocityInfluence;
+
+ private boolean mFakeDragging;
+ private long mFakeDragBeginTime;
+
+ private EdgeEffectCompat mLeftEdge;
+ private EdgeEffectCompat mRightEdge;
+
+ private boolean mFirstLayout = true;
+
+ private OnPageChangeListener mOnPageChangeListener;
+
+ /**
+ * Indicates that the pager is in an idle, settled state. The current page * is fully in view and no animation is in progress.
+ */
+ public static final int SCROLL_STATE_IDLE = 0;
+
+ /**
+ * Indicates that the pager is currently being dragged by the user.
+ */
+ public static final int SCROLL_STATE_DRAGGING = 1;
+
+ /**
+ * Indicates that the pager is in the process of settling to a final * position.
+ */
+ public static final int SCROLL_STATE_SETTLING = 2;
+
+ private int mScrollState = SCROLL_STATE_IDLE;
+ // add by zzh is forbid horizontal switch tab enable
+ private boolean mNoScroll;
+
+ /**
+ * Callback interface for responding to changing state of the selected page.
+ */
+ public interface OnPageChangeListener {
+
+ /**
+ * This method will be invoked when the current page is scrolled, either * as part of a programmatically initiated smooth scroll or a user * initiated touch scroll. * * @param position * Position index of the first page currently being * displayed. Page position+1 will be visible if * positionOffset is nonzero. * @param positionOffset * Value from [0, 1) indicating the offset from the page at * position. * @param positionOffsetPixels * Value in pixels indicating the offset from position.
+ */
+ public void onPageScrolled(int position, float positionOffset,
+ int positionOffsetPixels);
+
+ /**
+ * This method will be invoked when a new page becomes selected. * Animation is not necessarily complete. * * @param position * Position index of the new selected page.
+ */
+ public void onPageSelected(int position);
+
+ /**
+ * Called when the scroll state changes. Useful for discovering when the * user begins dragging, when the pager is automatically settling to the * current page, or when it is fully stopped/idle. * * @param state * The new scroll state. * @see android.support.v4.view.ViewPager#SCROLL_STATE_IDLE * @see android.support.v4.view.ViewPager#SCROLL_STATE_DRAGGING * @see android.support.v4.view.ViewPager#SCROLL_STATE_SETTLING
+ */
+ public void onPageScrollStateChanged(int state);
+ }
+
+ /**
+ * Simple implementation of the * {@link com.bxg.news.view.LazyViewPager.OnPageChangeListener} * interface with stub implementations of each method. Extend this if you do * not intend to override every method of * {@link com.bxg.news.view.LazyViewPager.OnPageChangeListener}.
+ */
+ public static class SimpleOnPageChangeListener implements OnPageChangeListener {
+ @Override
+ public void onPageScrolled(int position, float positionOffset,
+ int positionOffsetPixels) {
+ // This space for rent
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ // This space for rent
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ // This space for rent
+ }
+ }
+
+ public LazyViewPager(Context context) {
+ super(context);
+ initViewPager();
+ }
+
+ public LazyViewPager(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initViewPager();
+ }
+
+ void initViewPager() {
+ setWillNotDraw(false);
+ setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
+ setFocusable(true);
+ final Context context = getContext();
+ mScroller = new Scroller(context, sInterpolator);
+ final ViewConfiguration configuration = ViewConfiguration.get(context);
+ mTouchSlop = ViewConfigurationCompat
+ .getScaledPagingTouchSlop(configuration);
+ mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
+ mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+ mLeftEdge = new EdgeEffectCompat(context);
+ mRightEdge = new EdgeEffectCompat(context);
+
+ float density = context.getResources().getDisplayMetrics().density;
+ mBaseLineFlingVelocity = 2500.0f * density;
+ mFlingVelocityInfluence = 0.4f;
+ }
+
+ private void setScrollState(int newState) {
+ if (mScrollState == newState) {
+ return;
+ }
+
+ mScrollState = newState;
+ if (mOnPageChangeListener != null) {
+ mOnPageChangeListener.onPageScrollStateChanged(newState);
+ }
+ }
+
+ public void setAdapter(PagerAdapter adapter) {
+ if (mAdapter != null) {
+ mAdapter.unregisterDataSetObserver(mObserver);
+ mAdapter.startUpdate(this);
+ for (int i = 0; i < mItems.size(); i++) {
+ final ItemInfo ii = mItems.get(i);
+ mAdapter.destroyItem(this, ii.position, ii.object);
+ }
+ mAdapter.finishUpdate(this);
+ mItems.clear();
+ removeAllViews();
+ mCurItem = 0;
+ scrollTo(0, 0);
+ }
+
+ mAdapter = adapter;
+
+ if (mAdapter != null) {
+ if (mObserver == null) {
+ mObserver = new PagerObserver();
+ }
+ mAdapter.registerDataSetObserver(mObserver);
+ mPopulatePending = false;
+ if (mRestoredCurItem >= 0) {
+ mAdapter.restoreState(mRestoredAdapterState,
+ mRestoredClassLoader);
+ setCurrentItemInternal(mRestoredCurItem, false, true);
+ mRestoredCurItem = -1;
+ mRestoredAdapterState = null;
+ mRestoredClassLoader = null;
+ } else {
+ populate();
+ }
+ }
+ }
+
+ public PagerAdapter getAdapter() {
+ return mAdapter;
+ }
+
+ /**
+ * Set the currently selected page. If the ViewPager has already been * through its first layout there will be a smooth animated transition * between the current item and the specified item. * * @param item * Item index to select
+ */
+ public void setCurrentItem(int item) {
+ mPopulatePending = false;
+ setCurrentItemInternal(item, !mFirstLayout, false);
+ }
+
+ /**
+ * Set the currently selected page. * * @param item * Item index to select * @param smoothScroll * True to smoothly scroll to the new item, false to transition * immediately
+ */
+ public void setCurrentItem(int item, boolean smoothScroll) {
+ mPopulatePending = false;
+ setCurrentItemInternal(item, smoothScroll, false);
+ }
+
+ public int getCurrentItem() {
+ return mCurItem;
+ }
+
+ void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
+ setCurrentItemInternal(item, smoothScroll, always, 0);
+ }
+
+ void setCurrentItemInternal(int item, boolean smoothScroll, boolean always,
+ int velocity) {
+ if (mAdapter == null || mAdapter.getCount() <= 0) {
+ setScrollingCacheEnabled(false);
+ return;
+ }
+ if (!always && mCurItem == item && mItems.size() != 0) {
+ setScrollingCacheEnabled(false);
+ return;
+ }
+ if (item < 0) {
+ item = 0;
+ } else if (item >= mAdapter.getCount()) {
+ item = mAdapter.getCount() - 1;
+ }
+ final int pageLimit = mOffscreenPageLimit;
+ if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
+ // We are doing a jump by more than one page. To avoid
+ // glitches, we want to keep all current pages in the view
+ // until the scroll ends.
+ for (int i = 0; i < mItems.size(); i++) {
+ mItems.get(i).scrolling = true;
+ }
+ }
+
+ final boolean dispatchSelected = mCurItem != item;
+ mCurItem = item;
+ populate();
+ final int destX = (getWidth() + mPageMargin) * item;
+ if (smoothScroll) {
+ smoothScrollTo(destX, 0, velocity);
+ if (dispatchSelected && mOnPageChangeListener != null) {
+ mOnPageChangeListener.onPageSelected(item);
+ }
+ } else {
+ if (dispatchSelected && mOnPageChangeListener != null) {
+ mOnPageChangeListener.onPageSelected(item);
+ }
+ completeScroll();
+ scrollTo(destX, 0);
+ }
+ }
+
+ public void setOnPageChangeListener(OnPageChangeListener listener) {
+ mOnPageChangeListener = listener;
+ }
+
+ /**
+ * Returns the number of pages that will be retained to either side of the * current page in the view hierarchy in an idle state. Defaults to 1. * * @return How many pages will be kept offscreen on either side * @see #setOffscreenPageLimit(int)
+ */
+ public int getOffscreenPageLimit() {
+ return mOffscreenPageLimit;
+ }
+
+ /**
+ * Set the number of pages that should be retained to either side of the * current page in the view hierarchy in an idle state. Pages beyond this * limit will be recreated from the adapter when needed. * * * This is offered as an optimization. If you know in advance the number of * pages you will need to support or have lazy-loading mechanisms in place * on your pages, tweaking this setting can have benefits in perceived * smoothness of paging animations and interaction. If you have a small * number of pages (3-4) that you can keep active all at once, less time * will be spent in layout for newly created view subtrees as the user pages * back and forth. *
* * * You should keep this limit low, especially if your pages have complex * layouts. This setting defaults to 1. *
* * @param limit * How many pages will be kept offscreen in an idle state.
+ */
+ public void setOffscreenPageLimit(int limit) {
+ if (limit < DEFAULT_OFFSCREEN_PAGES) {
+ Log.w(TAG, "Requested offscreen page limit " + limit
+ + " too small; defaulting to " + DEFAULT_OFFSCREEN_PAGES);
+ limit = DEFAULT_OFFSCREEN_PAGES;
+ }
+ if (limit != mOffscreenPageLimit) {
+ mOffscreenPageLimit = limit;
+ populate();
+ }
+ }
+
+ /**
+ * Set the margin between pages. * * @param marginPixels * Distance between adjacent pages in pixels * @see #getPageMargin() * @see #setPageMarginDrawable(android.graphics.drawable.Drawable) * @see #setPageMarginDrawable(int)
+ */
+ public void setPageMargin(int marginPixels) {
+ final int oldMargin = mPageMargin;
+ mPageMargin = marginPixels;
+
+ final int width = getWidth();
+ recomputeScrollPosition(width, width, marginPixels, oldMargin);
+
+ requestLayout();
+ }
+
+ /**
+ * Return the margin between pages. * * @return The size of the margin in pixels
+ */
+ public int getPageMargin() {
+ return mPageMargin;
+ }
+
+ /**
+ * Set a drawable that will be used to fill the margin between pages. * * @param d * Drawable to display between pages
+ */
+ public void setPageMarginDrawable(Drawable d) {
+ mMarginDrawable = d;
+ if (d != null)
+ refreshDrawableState();
+ setWillNotDraw(d == null);
+ invalidate();
+ }
+
+ /**
+ * Set a drawable that will be used to fill the margin between pages. * * @param resId * Resource ID of a drawable to display between pages
+ */
+ public void setPageMarginDrawable(int resId) {
+ setPageMarginDrawable(getContext().getResources().getDrawable(resId));
+ }
+
+ @Override
+ protected boolean verifyDrawable(Drawable who) {
+ return super.verifyDrawable(who) || who == mMarginDrawable;
+ }
+
+ @Override
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
+ final Drawable d = mMarginDrawable;
+ if (d != null && d.isStateful()) {
+ d.setState(getDrawableState());
+ }
+ }
+
+ // We want the duration of the page snap animation to be influenced by the
+ // distance that
+ // the screen has to travel, however, we don't want this duration to be
+ // effected in a
+ // purely linear fashion. Instead, we use this method to moderate the effect
+ // that the distance
+ // of travel has on the overall snap duration.
+ float distanceInfluenceForSnapDuration(float f) {
+ f -= 0.5f; // center the values about 0.
+ f *= 0.3f * Math.PI / 2.0f;
+ return (float) Math.sin(f);
+ }
+
+ /**
+ * Like {@link View#scrollBy}, but scroll smoothly instead of * immediately. * * @param x * the number of pixels to scroll by on the X axis * @param y * the number of pixels to scroll by on the Y axis
+ */
+ void smoothScrollTo(int x, int y) {
+ smoothScrollTo(x, y, 0);
+ }
+
+ /**
+ * Like {@link View#scrollBy}, but scroll smoothly instead of * immediately. * * @param x * the number of pixels to scroll by on the X axis * @param y * the number of pixels to scroll by on the Y axis * @param velocity * the velocity associated with a fling, if applicable. (0 * otherwise)
+ */
+ void smoothScrollTo(int x, int y, int velocity) {
+ if (getChildCount() == 0) {
+ // Nothing to do.
+ setScrollingCacheEnabled(false);
+ return;
+ }
+ int sx = getScrollX();
+ int sy = getScrollY();
+ int dx = x - sx;
+ int dy = y - sy;
+ if (dx == 0 && dy == 0) {
+ completeScroll();
+ setScrollState(SCROLL_STATE_IDLE);
+ return;
+ }
+
+ setScrollingCacheEnabled(true);
+ mScrolling = true;
+ setScrollState(SCROLL_STATE_SETTLING);
+
+ final float pageDelta = (float) Math.abs(dx)
+ / (getWidth() + mPageMargin);
+ int duration = (int) (pageDelta * 100);
+
+ velocity = Math.abs(velocity);
+ if (velocity > 0) {
+ duration += (duration / (velocity / mBaseLineFlingVelocity))
+ * mFlingVelocityInfluence;
+ } else {
+ duration += 100;
+ }
+ duration = Math.min(duration, MAX_SETTLE_DURATION);
+
+ mScroller.startScroll(sx, sy, dx, dy, duration);
+ invalidate();
+ }
+
+ void addNewItem(int position, int index) {
+ ItemInfo ii = new ItemInfo();
+ ii.position = position;
+ ii.object = mAdapter.instantiateItem(this, position);
+ if (index < 0) {
+ mItems.add(ii);
+ } else {
+ mItems.add(index, ii);
+ }
+ }
+
+ void dataSetChanged() {
+ // This method only gets called if our observer is attached, so mAdapter
+ // is non-null.
+
+ boolean needPopulate = mItems.size() < 3
+ && mItems.size() < mAdapter.getCount();
+ int newCurrItem = -1;
+
+ for (int i = 0; i < mItems.size(); i++) {
+ final ItemInfo ii = mItems.get(i);
+ final int newPos = mAdapter.getItemPosition(ii.object);
+
+ if (newPos == PagerAdapter.POSITION_UNCHANGED) {
+ continue;
+ }
+
+ if (newPos == PagerAdapter.POSITION_NONE) {
+ mItems.remove(i);
+ i--;
+ mAdapter.destroyItem(this, ii.position, ii.object);
+ needPopulate = true;
+
+ if (mCurItem == ii.position) {
+ // Keep the current item in the valid range
+ newCurrItem = Math.max(0,
+ Math.min(mCurItem, mAdapter.getCount() - 1));
+ }
+ continue;
+ }
+
+ if (ii.position != newPos) {
+ if (ii.position == mCurItem) {
+ // Our current item changed position. Follow it.
+ newCurrItem = newPos;
+ }
+
+ ii.position = newPos;
+ needPopulate = true;
+ }
+ }
+
+ Collections.sort(mItems, COMPARATOR);
+
+ if (newCurrItem >= 0) {
+ // TODO This currently causes a jump.
+ setCurrentItemInternal(newCurrItem, false, true);
+ needPopulate = true;
+ }
+ if (needPopulate) {
+ populate();
+ requestLayout();
+ }
+ }
+
+ void populate() {
+ if (mAdapter == null) {
+ return;
+ }
+
+ // Bail now if we are waiting to populate. This is to hold off
+ // on creating views from the time the user releases their finger to
+ // fling to a new position until we have finished the scroll to
+ // that position, avoiding glitches from happening at that point.
+ if (mPopulatePending) {
+ if (DEBUG)
+ Log.i(TAG, "populate is pending, skipping for now...");
+ return;
+ }
+
+ // Also, don't populate until we are attached to a window. This is to
+ // avoid trying to populate before we have restored our view hierarchy
+ // state and conflicting with what is restored.
+ if (getWindowToken() == null) {
+ return;
+ }
+
+ mAdapter.startUpdate(this);
+
+ final int pageLimit = mOffscreenPageLimit;
+ final int startPos = Math.max(0, mCurItem - pageLimit);
+ final int N = mAdapter.getCount();
+ final int endPos = Math.min(N - 1, mCurItem + pageLimit);
+
+ if (DEBUG)
+ Log.v(TAG, "populating: startPos=" + startPos + " endPos=" + endPos);
+
+ // Add and remove pages in the existing list.
+ int lastPos = -1;
+ for (int i = 0; i < mItems.size(); i++) {
+ ItemInfo ii = mItems.get(i);
+ if ((ii.position < startPos || ii.position > endPos)
+ && !ii.scrolling) {
+ if (DEBUG)
+ Log.i(TAG, "removing: " + ii.position + " @ " + i);
+ mItems.remove(i);
+ i--;
+ mAdapter.destroyItem(this, ii.position, ii.object);
+ } else if (lastPos < endPos && ii.position > startPos) {
+ // The next item is outside of our range, but we have a gap
+ // between it and the last item where we want to have a page
+ // shown. Fill in the gap.
+ lastPos++;
+ if (lastPos < startPos) {
+ lastPos = startPos;
+ }
+ while (lastPos <= endPos && lastPos < ii.position) {
+ if (DEBUG)
+ Log.i(TAG, "inserting: " + lastPos + " @ " + i);
+ addNewItem(lastPos, i);
+ lastPos++;
+ i++;
+ }
+ }
+ lastPos = ii.position;
+ }
+
+ // Add any new pages we need at the end.
+ lastPos = mItems.size() > 0 ? mItems.get(mItems.size() - 1).position
+ : -1;
+ if (lastPos < endPos) {
+ lastPos++;
+ lastPos = lastPos > startPos ? lastPos : startPos;
+ while (lastPos <= endPos) {
+ if (DEBUG)
+ Log.i(TAG, "appending: " + lastPos);
+ addNewItem(lastPos, -1);
+ lastPos++;
+ }
+ }
+
+ if (DEBUG) {
+ Log.i(TAG, "Current page list:");
+ for (int i = 0; i < mItems.size(); i++) {
+ Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);
+ }
+ }
+
+ ItemInfo curItem = null;
+ for (int i = 0; i < mItems.size(); i++) {
+ if (mItems.get(i).position == mCurItem) {
+ curItem = mItems.get(i);
+ break;
+ }
+ }
+ mAdapter.setPrimaryItem(this, mCurItem,
+ curItem != null ? curItem.object : null);
+
+ mAdapter.finishUpdate(this);
+
+ if (hasFocus()) {
+ View currentFocused = findFocus();
+ ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused)
+ : null;
+ if (ii == null || ii.position != mCurItem) {
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ ii = infoForChild(child);
+ if (ii != null && ii.position == mCurItem) {
+ if (child.requestFocus(FOCUS_FORWARD)) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public static class SavedState extends BaseSavedState {
+ int position;
+ Parcelable adapterState;
+ ClassLoader loader;
+
+ public SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeInt(position);
+ out.writeParcelable(adapterState, flags);
+ }
+
+ @Override
+ public String toString() {
+ return "FragmentPager.SavedState{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " position=" + position + "}";
+ }
+
+ public static final Creator CREATOR = ParcelableCompat
+ .newCreator(new ParcelableCompatCreatorCallbacks() {
+ @Override
+ public SavedState createFromParcel(Parcel in,
+ ClassLoader loader) {
+ return new SavedState(in, loader);
+ }
+
+ @Override
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ });
+
+ SavedState(Parcel in, ClassLoader loader) {
+ super(in);
+ if (loader == null) {
+ loader = getClass().getClassLoader();
+ }
+ position = in.readInt();
+ adapterState = in.readParcelable(loader);
+ this.loader = loader;
+ }
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+ SavedState ss = new SavedState(superState);
+ ss.position = mCurItem;
+ if (mAdapter != null) {
+ ss.adapterState = mAdapter.saveState();
+ }
+ return ss;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ if (!(state instanceof SavedState)) {
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
+ SavedState ss = (SavedState) state;
+ super.onRestoreInstanceState(ss.getSuperState());
+
+ if (mAdapter != null) {
+ mAdapter.restoreState(ss.adapterState, ss.loader);
+ setCurrentItemInternal(ss.position, false, true);
+ } else {
+ mRestoredCurItem = ss.position;
+ mRestoredAdapterState = ss.adapterState;
+ mRestoredClassLoader = ss.loader;
+ }
+ }
+
+ @Override
+ public void addView(View child, int index, LayoutParams params) {
+ if (mInLayout) {
+ addViewInLayout(child, index, params);
+ child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec);
+ } else {
+ super.addView(child, index, params);
+ }
+
+ if (USE_CACHE) {
+ if (child.getVisibility() != GONE) {
+ child.setDrawingCacheEnabled(mScrollingCacheEnabled);
+ } else {
+ child.setDrawingCacheEnabled(false);
+ }
+ }
+ }
+
+ ItemInfo infoForChild(View child) {
+ for (int i = 0; i < mItems.size(); i++) {
+ ItemInfo ii = mItems.get(i);
+ if (mAdapter.isViewFromObject(child, ii.object)) {
+ return ii;
+ }
+ }
+ return null;
+ }
+
+ ItemInfo infoForAnyChild(View child) {
+ ViewParent parent;
+ while ((parent = child.getParent()) != this) {
+ if (parent == null || !(parent instanceof View)) {
+ return null;
+ }
+ child = (View) parent;
+ }
+ return infoForChild(child);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mFirstLayout = true;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // For simple implementation, or internal size is always 0.
+ // We depend on the container to specify the layout size of
+ // our view. We can't really know what it is since we will be
+ // adding and removing different arbitrary views and do not
+ // want the layout to change as this happens.
+ setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
+ getDefaultSize(0, heightMeasureSpec));
+
+ // Children are just made to fill our space.
+ mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth()
+ - getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY);
+ mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
+ getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
+ MeasureSpec.EXACTLY);
+
+ // Make sure we have created all fragments that we need to have shown.
+ mInLayout = true;
+ populate();
+ mInLayout = false;
+
+ // Make sure all children have been properly measured.
+ final int size = getChildCount();
+ for (int i = 0; i < size; ++i) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ if (DEBUG)
+ Log.v(TAG, "Measuring #" + i + " " + child + ": "
+ + mChildWidthMeasureSpec);
+ child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec);
+ }
+ }
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+
+ // Make sure scroll position is set correctly.
+ if (w != oldw) {
+ recomputeScrollPosition(w, oldw, mPageMargin, mPageMargin);
+ }
+ }
+
+ private void recomputeScrollPosition(int width, int oldWidth, int margin,
+ int oldMargin) {
+ final int widthWithMargin = width + margin;
+ if (oldWidth > 0) {
+ final int oldScrollPos = getScrollX();
+ final int oldwwm = oldWidth + oldMargin;
+ final int oldScrollItem = oldScrollPos / oldwwm;
+ final float scrollOffset = (float) (oldScrollPos % oldwwm) / oldwwm;
+ final int scrollPos = (int) ((oldScrollItem + scrollOffset) * widthWithMargin);
+ scrollTo(scrollPos, getScrollY());
+ if (!mScroller.isFinished()) {
+ // We now return to your regularly scheduled scroll, already in
+ // progress.
+ final int newDuration = mScroller.getDuration()
+ - mScroller.timePassed();
+ mScroller.startScroll(scrollPos, 0, mCurItem * widthWithMargin,
+ 0, newDuration);
+ }
+ } else {
+ int scrollPos = mCurItem * widthWithMargin;
+ if (scrollPos != getScrollX()) {
+ completeScroll();
+ scrollTo(scrollPos, getScrollY());
+ }
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ mInLayout = true;
+ populate();
+ mInLayout = false;
+
+ final int count = getChildCount();
+ final int width = r - l;
+
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ ItemInfo ii;
+ if (child.getVisibility() != GONE
+ && (ii = infoForChild(child)) != null) {
+ int loff = (width + mPageMargin) * ii.position;
+ int childLeft = getPaddingLeft() + loff;
+ int childTop = getPaddingTop();
+ if (DEBUG)
+ Log.v(TAG,
+ "Positioning #" + i + " " + child + " f="
+ + ii.object + ":" + childLeft + ","
+ + childTop + " " + child.getMeasuredWidth()
+ + "x" + child.getMeasuredHeight());
+ child.layout(childLeft, childTop,
+ childLeft + child.getMeasuredWidth(),
+ childTop + child.getMeasuredHeight());
+ }
+ }
+ mFirstLayout = false;
+ }
+
+ @Override
+ public void computeScroll() {
+ if (DEBUG)
+ Log.i(TAG, "computeScroll: finished=" + mScroller.isFinished());
+ if (!mScroller.isFinished()) {
+ if (mScroller.computeScrollOffset()) {
+ if (DEBUG)
+ Log.i(TAG, "computeScroll: still scrolling");
+ int oldX = getScrollX();
+ int oldY = getScrollY();
+ int x = mScroller.getCurrX();
+ int y = mScroller.getCurrY();
+
+ if (oldX != x || oldY != y) {
+ scrollTo(x, y);
+ }
+
+ if (mOnPageChangeListener != null) {
+ final int widthWithMargin = getWidth() + mPageMargin;
+ final int position = x / widthWithMargin;
+ final int offsetPixels = x % widthWithMargin;
+ final float offset = (float) offsetPixels / widthWithMargin;
+ mOnPageChangeListener.onPageScrolled(position, offset,
+ offsetPixels);
+ }
+
+ // Keep on drawing until the animation has finished.
+ invalidate();
+ return;
+ }
+ }
+
+ // Done with scroll, clean up state.
+ completeScroll();
+ }
+
+ private void completeScroll() {
+ boolean needPopulate = mScrolling;
+ if (needPopulate) {
+ // Done with scroll, no longer want to cache view drawing.
+ setScrollingCacheEnabled(false);
+ mScroller.abortAnimation();
+ int oldX = getScrollX();
+ int oldY = getScrollY();
+ int x = mScroller.getCurrX();
+ int y = mScroller.getCurrY();
+ if (oldX != x || oldY != y) {
+ scrollTo(x, y);
+ }
+ setScrollState(SCROLL_STATE_IDLE);
+ }
+ mPopulatePending = false;
+ mScrolling = false;
+ for (int i = 0; i < mItems.size(); i++) {
+ ItemInfo ii = mItems.get(i);
+ if (ii.scrolling) {
+ needPopulate = true;
+ ii.scrolling = false;
+ }
+ }
+ if (needPopulate) {
+ populate();
+ }
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ // add by zzh @{
+ if (mNoScroll) {
+ return false;
+ }
+ // @}
+ /* * This method JUST determines whether we want to intercept the motion. * If we return true, onMotionEvent will be called and we do the actual * scrolling there. */
+
+ final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
+
+ // Always take care of the touch gesture being complete.
+ if (action == MotionEvent.ACTION_CANCEL
+ || action == MotionEvent.ACTION_UP) {
+ // Release the drag.
+ if (DEBUG)
+ Log.v(TAG, "Intercept done!");
+ mIsBeingDragged = false;
+ mIsUnableToDrag = false;
+ mActivePointerId = INVALID_POINTER;
+ return false;
+ }
+
+ // Nothing more to do here if we have decided whether or not we
+ // are dragging.
+ if (action != MotionEvent.ACTION_DOWN) {
+ if (mIsBeingDragged) {
+ if (DEBUG)
+ Log.v(TAG, "Intercept returning true!");
+ return true;
+ }
+ if (mIsUnableToDrag) {
+ if (DEBUG)
+ Log.v(TAG, "Intercept returning false!");
+ return false;
+ }
+ }
+
+ switch (action) {
+ case MotionEvent.ACTION_MOVE: {
+ /* * mIsBeingDragged == false, otherwise the shortcut would have * caught it. Check whether the user has moved far enough from his * original down touch. */
+
+ /* * Locally do absolute value. mLastMotionY is set to the y value of * the down event. */
+ final int activePointerId = mActivePointerId;
+ if (activePointerId == INVALID_POINTER) {
+ // If we don't have a valid id, the touch down wasn't on
+ // content.
+ break;
+ }
+
+ final int pointerIndex = MotionEventCompat.findPointerIndex(ev,
+ activePointerId);
+ final float x = MotionEventCompat.getX(ev, pointerIndex);
+ final float dx = x - mLastMotionX;
+ final float xDiff = Math.abs(dx);
+ final float y = MotionEventCompat.getY(ev, pointerIndex);
+ final float yDiff = Math.abs(y - mLastMotionY);
+ final int scrollX = getScrollX();
+ final boolean atEdge = (dx > 0 && scrollX == 0)
+ || (dx < 0 && mAdapter != null && scrollX >= (mAdapter
+ .getCount() - 1) * getWidth() - 1);
+ if (DEBUG)
+ Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + ","
+ + yDiff);
+
+ if (canScroll(this, false, (int) dx, (int) x, (int) y)) {
+ // Nested view has scrollable area under this point. Let it be
+ // handled there.
+ mInitialMotionX = mLastMotionX = x;
+ mLastMotionY = y;
+ return false;
+ }
+ if (xDiff > mTouchSlop && xDiff > yDiff) {
+ if (DEBUG)
+ Log.v(TAG, "Starting drag!");
+ mIsBeingDragged = true;
+ setScrollState(SCROLL_STATE_DRAGGING);
+ mLastMotionX = x;
+ setScrollingCacheEnabled(true);
+ } else {
+ if (yDiff > mTouchSlop) {
+ // The finger has moved enough in the vertical
+ // direction to be counted as a drag... abort
+ // any attempt to drag horizontally, to work correctly
+ // with children that have scrolling containers.
+ if (DEBUG)
+ Log.v(TAG, "Starting unable to drag!");
+ mIsUnableToDrag = true;
+ }
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_DOWN: {
+ /* * Remember location of down touch. ACTION_DOWN always refers to * pointer index 0. */
+ mLastMotionX = mInitialMotionX = ev.getX();
+ mLastMotionY = ev.getY();
+ mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
+
+ if (mScrollState == SCROLL_STATE_SETTLING) {
+ // Let the user 'catch' the pager as it animates.
+ mIsBeingDragged = true;
+ mIsUnableToDrag = false;
+ setScrollState(SCROLL_STATE_DRAGGING);
+ } else {
+ completeScroll();
+ mIsBeingDragged = false;
+ mIsUnableToDrag = false;
+ }
+
+ if (DEBUG)
+ Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
+ + " mIsBeingDragged=" + mIsBeingDragged
+ + "mIsUnableToDrag=" + mIsUnableToDrag);
+ break;
+ }
+
+ case MotionEventCompat.ACTION_POINTER_UP:
+ onSecondaryPointerUp(ev);
+ break;
+ }
+
+ /* * The only time we want to intercept motion events is if we are in the * drag mode. */
+ return mIsBeingDragged;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (mFakeDragging) {
+ // A fake drag is in progress already, ignore this real one
+ // but still eat the touch events.
+ // (It is likely that the user is multi-touching the screen.)
+ return true;
+ }
+
+ if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
+ // Don't handle edge touches immediately -- they may actually belong
+ // to one of our
+ // descendants.
+ return false;
+ }
+
+ if (mAdapter == null || mAdapter.getCount() == 0) {
+ // Nothing to present or scroll; nothing to touch.
+ return false;
+ }
+
+ // add by zzh @{
+ if (mNoScroll) {
+ return false;
+ }
+ // @}
+
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(ev);
+
+ final int action = ev.getAction();
+ boolean needsInvalidate = false;
+
+ switch (action & MotionEventCompat.ACTION_MASK) {
+ case MotionEvent.ACTION_DOWN: {
+ /* * If being flinged and user touches, stop the fling. isFinished * will be false if being flinged. */
+ completeScroll();
+
+ // Remember where the motion event started
+ mLastMotionX = mInitialMotionX = ev.getX();
+ mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
+ break;
+ }
+ case MotionEvent.ACTION_MOVE:
+ if (!mIsBeingDragged) {
+ final int pointerIndex = MotionEventCompat.findPointerIndex(ev,
+ mActivePointerId);
+ final float x = MotionEventCompat.getX(ev, pointerIndex);
+ final float xDiff = Math.abs(x - mLastMotionX);
+ final float y = MotionEventCompat.getY(ev, pointerIndex);
+ final float yDiff = Math.abs(y - mLastMotionY);
+ if (DEBUG)
+ Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff
+ + "," + yDiff);
+ if (xDiff > mTouchSlop && xDiff > yDiff) {
+ if (DEBUG)
+ Log.v(TAG, "Starting drag!");
+ mIsBeingDragged = true;
+ mLastMotionX = x;
+ setScrollState(SCROLL_STATE_DRAGGING);
+ setScrollingCacheEnabled(true);
+ }
+ }
+ if (mIsBeingDragged) {
+ // Scroll to follow the motion event
+ final int activePointerIndex = MotionEventCompat
+ .findPointerIndex(ev, mActivePointerId);
+ final float x = MotionEventCompat.getX(ev, activePointerIndex);
+ final float deltaX = mLastMotionX - x;
+ mLastMotionX = x;
+ float oldScrollX = getScrollX();
+ float scrollX = oldScrollX + deltaX;
+ final int width = getWidth();
+ final int widthWithMargin = width + mPageMargin;
+
+ final int lastItemIndex = mAdapter.getCount() - 1;
+ final float leftBound = Math.max(0, (mCurItem - 1)
+ * widthWithMargin);
+ final float rightBound = Math.min(mCurItem + 1, lastItemIndex)
+ * widthWithMargin;
+ if (scrollX < leftBound) {
+ if (leftBound == 0) {
+ float over = -scrollX;
+ needsInvalidate = mLeftEdge.onPull(over / width);
+ }
+ scrollX = leftBound;
+ } else if (scrollX > rightBound) {
+ if (rightBound == lastItemIndex * widthWithMargin) {
+ float over = scrollX - rightBound;
+ needsInvalidate = mRightEdge.onPull(over / width);
+ }
+ scrollX = rightBound;
+ }
+ // Don't lose the rounded component
+ mLastMotionX += scrollX - (int) scrollX;
+ scrollTo((int) scrollX, getScrollY());
+ if (mOnPageChangeListener != null) {
+ final int position = (int) scrollX / widthWithMargin;
+ final int positionOffsetPixels = (int) scrollX
+ % widthWithMargin;
+ final float positionOffset = (float) positionOffsetPixels
+ / widthWithMargin;
+ mOnPageChangeListener.onPageScrolled(position,
+ positionOffset, positionOffsetPixels);
+ }
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ if (mIsBeingDragged) {
+ final VelocityTracker velocityTracker = mVelocityTracker;
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+ int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
+ velocityTracker, mActivePointerId);
+ mPopulatePending = true;
+ final int widthWithMargin = getWidth() + mPageMargin;
+ final int scrollX = getScrollX();
+ final int currentPage = scrollX / widthWithMargin;
+ int nextPage = initialVelocity > 0 ? currentPage
+ : currentPage + 1;
+ setCurrentItemInternal(nextPage, true, true, initialVelocity);
+
+ mActivePointerId = INVALID_POINTER;
+ endDrag();
+ needsInvalidate = mLeftEdge.onRelease()
+ | mRightEdge.onRelease();
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ if (mIsBeingDragged) {
+ setCurrentItemInternal(mCurItem, true, true);
+ mActivePointerId = INVALID_POINTER;
+ endDrag();
+ needsInvalidate = mLeftEdge.onRelease()
+ | mRightEdge.onRelease();
+ }
+ break;
+ case MotionEventCompat.ACTION_POINTER_DOWN: {
+ final int index = MotionEventCompat.getActionIndex(ev);
+ final float x = MotionEventCompat.getX(ev, index);
+ mLastMotionX = x;
+ mActivePointerId = MotionEventCompat.getPointerId(ev, index);
+ break;
+ }
+ case MotionEventCompat.ACTION_POINTER_UP:
+ onSecondaryPointerUp(ev);
+ mLastMotionX = MotionEventCompat.getX(ev,
+ MotionEventCompat.findPointerIndex(ev, mActivePointerId));
+ break;
+ }
+ if (needsInvalidate) {
+ invalidate();
+ }
+ return true;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+ boolean needsInvalidate = false;
+
+ final int overScrollMode = ViewCompat.getOverScrollMode(this);
+ if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS
+ || (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS
+ && mAdapter != null && mAdapter.getCount() > 1)) {
+ if (!mLeftEdge.isFinished()) {
+ final int restoreCount = canvas.save();
+ final int height = getHeight() - getPaddingTop()
+ - getPaddingBottom();
+
+ canvas.rotate(270);
+ canvas.translate(-height + getPaddingTop(), 0);
+ mLeftEdge.setSize(height, getWidth());
+ needsInvalidate |= mLeftEdge.draw(canvas);
+ canvas.restoreToCount(restoreCount);
+ }
+ if (!mRightEdge.isFinished()) {
+ final int restoreCount = canvas.save();
+ final int width = getWidth();
+ final int height = getHeight() - getPaddingTop()
+ - getPaddingBottom();
+ final int itemCount = mAdapter != null ? mAdapter.getCount()
+ : 1;
+
+ canvas.rotate(90);
+ canvas.translate(-getPaddingTop(), -itemCount
+ * (width + mPageMargin) + mPageMargin);
+ mRightEdge.setSize(height, width);
+ needsInvalidate |= mRightEdge.draw(canvas);
+ canvas.restoreToCount(restoreCount);
+ }
+ } else {
+ mLeftEdge.finish();
+ mRightEdge.finish();
+ }
+
+ if (needsInvalidate) {
+ // Keep animating
+ invalidate();
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ // Draw the margin drawable if needed.
+ if (mPageMargin > 0 && mMarginDrawable != null) {
+ final int scrollX = getScrollX();
+ final int width = getWidth();
+ final int offset = scrollX % (width + mPageMargin);
+ if (offset != 0) {
+ // Pages fit completely when settled; we only need to draw when
+ // in between
+ final int left = scrollX - offset + width;
+ mMarginDrawable.setBounds(left, 0, left + mPageMargin,
+ getHeight());
+ mMarginDrawable.draw(canvas);
+ }
+ }
+ }
+
+ /**
+ * Start a fake drag of the pager. * * * A fake drag can be useful if you want to synchronize the motion of the * ViewPager with the touch scrolling of another view, while still letting * the ViewPager control the snapping motion and fling behavior. (e.g. * parallax-scrolling tabs.) Call {@link #fakeDragBy(float)} to simulate the * actual drag motion. Call {@link #endFakeDrag()} to complete the fake drag * and fling as necessary. * *
* During a fake drag the ViewPager will ignore all touch events. If a real * drag is already in progress, this method will return false. * * @return true if the fake drag began successfully, false if it could not * be started. * * @see #fakeDragBy(float) * @see #endFakeDrag()
+ */
+ public boolean beginFakeDrag() {
+ if (mIsBeingDragged) {
+ return false;
+ }
+ mFakeDragging = true;
+ setScrollState(SCROLL_STATE_DRAGGING);
+ mInitialMotionX = mLastMotionX = 0;
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ } else {
+ mVelocityTracker.clear();
+ }
+ final long time = SystemClock.uptimeMillis();
+ final MotionEvent ev = MotionEvent.obtain(time, time,
+ MotionEvent.ACTION_DOWN, 0, 0, 0);
+ mVelocityTracker.addMovement(ev);
+ ev.recycle();
+ mFakeDragBeginTime = time;
+ return true;
+ }
+
+ /**
+ * End a fake drag of the pager. * * @see #beginFakeDrag() * @see #fakeDragBy(float)
+ */
+ public void endFakeDrag() {
+ if (!mFakeDragging) {
+ throw new IllegalStateException(
+ "No fake drag in progress. Call beginFakeDrag first.");
+ }
+
+ final VelocityTracker velocityTracker = mVelocityTracker;
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+ int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(
+ velocityTracker, mActivePointerId);
+ mPopulatePending = true;
+ if ((Math.abs(initialVelocity) > mMinimumVelocity)
+ || Math.abs(mInitialMotionX - mLastMotionX) >= (getWidth() / 3)) {
+ if (mLastMotionX > mInitialMotionX) {
+ setCurrentItemInternal(mCurItem - 1, true, true);
+ } else {
+ setCurrentItemInternal(mCurItem + 1, true, true);
+ }
+ } else {
+ setCurrentItemInternal(mCurItem, true, true);
+ }
+ endDrag();
+
+ mFakeDragging = false;
+ }
+
+ /**
+ * Fake drag by an offset in pixels. You must have called * {@link #beginFakeDrag()} first. * * @param xOffset * Offset in pixels to drag by. * @see #beginFakeDrag() * @see #endFakeDrag()
+ */
+ public void fakeDragBy(float xOffset) {
+ if (!mFakeDragging) {
+ throw new IllegalStateException(
+ "No fake drag in progress. Call beginFakeDrag first.");
+ }
+
+ mLastMotionX += xOffset;
+ float scrollX = getScrollX() - xOffset;
+ final int width = getWidth();
+ final int widthWithMargin = width + mPageMargin;
+
+ final float leftBound = Math.max(0, (mCurItem - 1) * widthWithMargin);
+ final float rightBound = Math
+ .min(mCurItem + 1, mAdapter.getCount() - 1) * widthWithMargin;
+ if (scrollX < leftBound) {
+ scrollX = leftBound;
+ } else if (scrollX > rightBound) {
+ scrollX = rightBound;
+ }
+ // Don't lose the rounded component
+ mLastMotionX += scrollX - (int) scrollX;
+ scrollTo((int) scrollX, getScrollY());
+ if (mOnPageChangeListener != null) {
+ final int position = (int) scrollX / widthWithMargin;
+ final int positionOffsetPixels = (int) scrollX % widthWithMargin;
+ final float positionOffset = (float) positionOffsetPixels
+ / widthWithMargin;
+ mOnPageChangeListener.onPageScrolled(position, positionOffset,
+ positionOffsetPixels);
+ }
+
+ // Synthesize an event for the VelocityTracker.
+ final long time = SystemClock.uptimeMillis();
+ final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time,
+ MotionEvent.ACTION_MOVE, mLastMotionX, 0, 0);
+ mVelocityTracker.addMovement(ev);
+ ev.recycle();
+ }
+
+ /**
+ * Returns true if a fake drag is in progress. * * @return true if currently in a fake drag, false otherwise. * * @see #beginFakeDrag() * @see #fakeDragBy(float) * @see #endFakeDrag()
+ */
+ public boolean isFakeDragging() {
+ return mFakeDragging;
+ }
+
+ private void onSecondaryPointerUp(MotionEvent ev) {
+ final int pointerIndex = MotionEventCompat.getActionIndex(ev);
+ final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
+ if (pointerId == mActivePointerId) {
+ // This was our active pointer going up. Choose a new
+ // active pointer and adjust accordingly.
+ final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
+ mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex);
+ mActivePointerId = MotionEventCompat.getPointerId(ev,
+ newPointerIndex);
+ if (mVelocityTracker != null) {
+ mVelocityTracker.clear();
+ }
+ }
+ }
+
+ private void endDrag() {
+ mIsBeingDragged = false;
+ mIsUnableToDrag = false;
+
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ private void setScrollingCacheEnabled(boolean enabled) {
+ if (mScrollingCacheEnabled != enabled) {
+ mScrollingCacheEnabled = enabled;
+ if (USE_CACHE) {
+ final int size = getChildCount();
+ for (int i = 0; i < size; ++i) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ child.setDrawingCacheEnabled(enabled);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Tests scrollability within child views of v given a delta of dx. * * @param v * View to test for horizontal scrollability * @param checkV * Whether the view v passed should itself be checked for * scrollability (true), or just its children (false). * @param dx * Delta scrolled in pixels * @param x * X coordinate of the active touch point * @param y * Y coordinate of the active touch point * @return true if child views of v can be scrolled by delta of dx.
+ */
+ protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
+ if (v instanceof ViewGroup) {
+ final ViewGroup group = (ViewGroup) v;
+ final int scrollX = v.getScrollX();
+ final int scrollY = v.getScrollY();
+ final int count = group.getChildCount();
+ // Count backwards - let topmost views consume scroll distance
+ // first.
+ for (int i = count - 1; i >= 0; i--) {
+ // TODO: Add versioned support here for transformed views.
+ // This will not work for transformed views in Honeycomb+
+ final View child = group.getChildAt(i);
+ if (x + scrollX >= child.getLeft()
+ && x + scrollX < child.getRight()
+ && y + scrollY >= child.getTop()
+ && y + scrollY < child.getBottom()
+ && canScroll(child, true, dx,
+ x + scrollX - child.getLeft(), y + scrollY
+ - child.getTop())) {
+ return true;
+ }
+ }
+ }
+
+ return checkV && ViewCompat.canScrollHorizontally(v, -dx);
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ // Let the focused view and/or our descendants get the key first
+ return super.dispatchKeyEvent(event) || executeKeyEvent(event);
+ }
+
+ /**
+ * You can call this function yourself to have the scroll view perform * scrolling from a key event, just as if the event had been dispatched to * it by the view hierarchy. * * @param event * The key event to execute. * @return Return true if the event was handled, else false.
+ */
+ public boolean executeKeyEvent(KeyEvent event) {
+ boolean handled = false;
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ switch (event.getKeyCode()) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ handled = arrowScroll(FOCUS_LEFT);
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ handled = arrowScroll(FOCUS_RIGHT);
+ break;
+ case KeyEvent.KEYCODE_TAB:
+ if (KeyEventCompat.hasNoModifiers(event)) {
+ handled = arrowScroll(FOCUS_FORWARD);
+ } else if (KeyEventCompat.hasModifiers(event,
+ KeyEvent.META_SHIFT_ON)) {
+ handled = arrowScroll(FOCUS_BACKWARD);
+ }
+ break;
+ }
+ }
+ return handled;
+ }
+
+ public boolean arrowScroll(int direction) {
+ View currentFocused = findFocus();
+ if (currentFocused == this)
+ currentFocused = null;
+
+ boolean handled = false;
+
+ View nextFocused = FocusFinder.getInstance().findNextFocus(this,
+ currentFocused, direction);
+ if (nextFocused != null && nextFocused != currentFocused) {
+ if (direction == View.FOCUS_LEFT) {
+ // If there is nothing to the left, or this is causing us to
+ // jump to the right, then what we really want to do is page
+ // left.
+ if (currentFocused != null
+ && nextFocused.getLeft() >= currentFocused.getLeft()) {
+ handled = pageLeft();
+ } else {
+ handled = nextFocused.requestFocus();
+ }
+ } else if (direction == View.FOCUS_RIGHT) {
+ // If there is nothing to the right, or this is causing us to
+ // jump to the left, then what we really want to do is page
+ // right.
+ if (currentFocused != null
+ && nextFocused.getLeft() <= currentFocused.getLeft()) {
+ handled = pageRight();
+ } else {
+ handled = nextFocused.requestFocus();
+ }
+ }
+ } else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) {
+ // Trying to move left and nothing there; try to page.
+ handled = pageLeft();
+ } else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) {
+ // Trying to move right and nothing there; try to page.
+ handled = pageRight();
+ }
+ if (handled) {
+ playSoundEffect(SoundEffectConstants
+ .getContantForFocusDirection(direction));
+ }
+ return handled;
+ }
+
+ boolean pageLeft() {
+ if (mCurItem > 0) {
+ setCurrentItem(mCurItem - 1, true);
+ return true;
+ }
+ return false;
+ }
+
+ boolean pageRight() {
+ if (mAdapter != null && mCurItem < (mAdapter.getCount() - 1)) {
+ setCurrentItem(mCurItem + 1, true);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * We only want the current page that is being shown to be focusable.
+ */
+ @Override
+ public void addFocusables(ArrayList views, int direction,
+ int focusableMode) {
+ final int focusableCount = views.size();
+
+ final int descendantFocusability = getDescendantFocusability();
+
+ if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
+ for (int i = 0; i < getChildCount(); i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() == VISIBLE) {
+ ItemInfo ii = infoForChild(child);
+ if (ii != null && ii.position == mCurItem) {
+ child.addFocusables(views, direction, focusableMode);
+ }
+ }
+ }
+ }
+
+ // we add ourselves (if focusable) in all cases except for when we are
+ // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable.
+ // this is
+ // to avoid the focus search finding layouts when a more precise search
+ // among the focusable children would be more interesting.
+ if (descendantFocusability != FOCUS_AFTER_DESCENDANTS ||
+ // No focusable descendants
+ (focusableCount == views.size())) {
+ // Note that we can't call the superclass here, because it will
+ // add all views in. So we need to do the same thing View does.
+ if (!isFocusable()) {
+ return;
+ }
+ if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE
+ && isInTouchMode() && !isFocusableInTouchMode()) {
+ return;
+ }
+ if (views != null) {
+ views.add(this);
+ }
+ }
+ }
+
+ /**
+ * We only want the current page that is being shown to be touchable.
+ */
+ @Override
+ public void addTouchables(ArrayList views) {
+ // Note that we don't call super.addTouchables(), which means that
+ // we don't call View.addTouchables(). This is okay because a ViewPager
+ // is itself not touchable.
+ for (int i = 0; i < getChildCount(); i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() == VISIBLE) {
+ ItemInfo ii = infoForChild(child);
+ if (ii != null && ii.position == mCurItem) {
+ child.addTouchables(views);
+ }
+ }
+ }
+ }
+
+ /**
+ * We only want the current page that is being shown to be focusable.
+ */
+ @Override
+ protected boolean onRequestFocusInDescendants(int direction,
+ Rect previouslyFocusedRect) {
+ int index;
+ int increment;
+ int end;
+ int count = getChildCount();
+ if ((direction & FOCUS_FORWARD) != 0) {
+ index = 0;
+ increment = 1;
+ end = count;
+ } else {
+ index = count - 1;
+ increment = -1;
+ end = -1;
+ }
+ for (int i = index; i != end; i += increment) {
+ View child = getChildAt(i);
+ if (child.getVisibility() == VISIBLE) {
+ ItemInfo ii = infoForChild(child);
+ if (ii != null && ii.position == mCurItem) {
+ if (child.requestFocus(direction, previouslyFocusedRect)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ // ViewPagers should only report accessibility info for the current
+ // page,
+ // otherwise things get very confusing.
+
+ // TODO: Should this note something about the paging container?
+
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() == VISIBLE) {
+ final ItemInfo ii = infoForChild(child);
+ if (ii != null && ii.position == mCurItem
+ && child.dispatchPopulateAccessibilityEvent(event)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private class PagerObserver extends DataSetObserver {
+
+ @Override
+ public void onChanged() {
+ dataSetChanged();
+ }
+
+ @Override
+ public void onInvalidated() {
+ dataSetChanged();
+ }
+ }
+
+ // add by zzh for forbid horizontal scroll
+ public void setNoScroll(boolean noScroll) {
+ this.mNoScroll = noScroll;
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/custom/view/MyScollWebView.java b/app/src/main/java/com/ckkj/water/custom/view/MyScollWebView.java
new file mode 100644
index 0000000..8b394df
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/custom/view/MyScollWebView.java
@@ -0,0 +1,28 @@
+package com.ckkj.water.custom.view;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.webkit.WebView;
+
+public class MyScollWebView extends WebView {
+ public MyScollWebView(Context context) {
+ super(context);
+ }
+
+ public MyScollWebView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MyScollWebView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (getParent() != null) {
+ getParent().requestDisallowInterceptTouchEvent(true); // 禁止父视图拦截触摸事件
+ }
+ return super.onTouchEvent(event);
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/custom/view/NoVerticalScrollRecyclerView.java b/app/src/main/java/com/ckkj/water/custom/view/NoVerticalScrollRecyclerView.java
new file mode 100644
index 0000000..366b1c1
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/custom/view/NoVerticalScrollRecyclerView.java
@@ -0,0 +1,40 @@
+package com.ckkj.water.custom.view;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+public class NoVerticalScrollRecyclerView extends RecyclerView {
+
+ public NoVerticalScrollRecyclerView(@NonNull Context context) {
+ super(context);
+ }
+
+ public NoVerticalScrollRecyclerView(@NonNull Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public NoVerticalScrollRecyclerView(@NonNull Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent e) {
+ // 禁止上下滑动
+ if (e.getAction() == MotionEvent.ACTION_MOVE) {
+ return true; // 返回 true 表示消费事件,不向下传递
+ }
+ return super.onTouchEvent(e); // 处理其他事件
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent e) {
+ // 禁止上下滑动
+ if (e.getAction() == MotionEvent.ACTION_MOVE) {
+ return true; // 返回 true 表示拦截事件
+ }
+ return super.onInterceptTouchEvent(e);
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/custom/view/PhotoViewActivity.java b/app/src/main/java/com/ckkj/water/custom/view/PhotoViewActivity.java
new file mode 100644
index 0000000..e1bdfc0
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/custom/view/PhotoViewActivity.java
@@ -0,0 +1,163 @@
+package com.ckkj.water.custom.view;
+
+import android.annotation.SuppressLint;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.fragment.app.FragmentActivity;
+import androidx.viewpager.widget.PagerAdapter;
+import androidx.viewpager.widget.ViewPager;
+
+import com.bumptech.glide.Glide;
+import com.github.chrisbanes.photoview.OnPhotoTapListener;
+import com.github.chrisbanes.photoview.PhotoView;
+import com.gyf.immersionbar.ImmersionBar;
+import com.lxj.xpopup.XPopup;
+import com.lxj.xpopup.interfaces.OnSelectListener;
+import com.ckkj.water.R;
+import com.ckkj.water.utils.DownFileUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * 图片查看器
+ *
+ * @author Mc
+ */
+@SuppressWarnings({ "unchecked" }) public class PhotoViewActivity extends FragmentActivity {
+ private HackyViewPager viewPager;
+ private ArrayList mList;
+ private TextView textView;
+ private List img_urls;
+ private int i;
+ private boolean crop;
+
+ @SuppressLint("MissingInflatedId")
+ @Override protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_photoview);
+ ImmersionBar.with(this)
+ .statusBarColor(R.color.black)
+ .statusBarDarkFont(false)
+ .titleBar(findViewById(R.id.rl_title)) // 替换为你的标题栏View
+ .keyboardEnable(true)
+ .navigationBarDarkIcon(false)
+ .init();
+ initShowPhoto();
+ initView();
+ }
+
+ private void initShowPhoto() {
+
+ Bundle bundle = getIntent().getExtras();
+ if (bundle == null) {
+ finish();
+ } else {
+ i = bundle.getInt("img_item", 0);
+ crop = bundle.getBoolean("case", false);
+ img_urls = (List) bundle.getSerializable("img_urls");
+ String imgUrl = bundle.getString("img_url");
+ if (img_urls == null) {
+ img_urls = new ArrayList<>();
+ if (imgUrl != null) {
+ img_urls.add(imgUrl);
+ }
+ }
+ }
+ }
+
+ private void initView() {
+ viewPager = (HackyViewPager) findViewById(R.id.ViewPager);
+ textView = (TextView) findViewById(R.id.text);
+ mList = new ArrayList<>();
+
+ PagerAdapter pagerAdapter = new PagerAdapter() {
+
+ // 获取要滑动的控件的数量,在这里我们以滑动的广告栏为例,那么这里就应该是展示的广告图片的ImageView数量
+ @Override public int getCount() {
+ return img_urls.size();
+
+ //return Integer.MAX_VALUE; 返回一个比较大的值,目的是为了实现无限轮播
+ }
+
+ // 来判断显示的是否是同一张图片,这里我们将两个参数相比较返回即可
+ @Override public boolean isViewFromObject(View view, Object object) {
+ return view == object;
+ }
+
+ // 当要显示的图片可以进行缓存的时候,会调用这个方法进行显示图片的初始化,
+ // 我们将要显示的ImageView加入到ViewGroup中,然后作为返回值返回即可
+ @Override public Object instantiateItem(ViewGroup container, int position) {
+ PhotoView imageView = new PhotoView(PhotoViewActivity.this);
+ Glide.with(PhotoViewActivity.this).load(img_urls.get(position)).into(imageView);
+ imageView.setOnPhotoTapListener(new OnPhotoTapListener() {
+ @Override
+ public void onPhotoTap(ImageView view, float x, float y) {
+
+ }
+
+ });
+ imageView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ finish();
+ }
+ });
+ imageView.setOnLongClickListener(new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ new XPopup.Builder(PhotoViewActivity.this).asBottomList("请选择", new String[]{"下载"}, new OnSelectListener() {
+ @Override
+ public void onSelect(int position, String text) {
+ if(position == 0){
+ DownFileUtil.downloadImage(PhotoViewActivity.this,img_urls.get(position));
+ }
+ }
+ }).show();
+ return false;
+ }
+ });
+ container.addView(imageView, ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT);
+ mList.add(imageView);
+ return imageView;
+ }
+
+
+ //PagerAdapter只缓存三张要显示的图片,如果滑动的图片超出了缓存的范围,就会调用这个方法,将图片销毁
+ @Override public void destroyItem(ViewGroup container, int position, Object object) {
+ //container.removeView(mList.get(position));
+ }
+ };
+ textView.setText(((i + 1) + "/" + img_urls.size()));
+ viewPager.setAdapter(pagerAdapter);
+ setSelect(i);//通过传值获取点击图片位置,从而显示当前图片
+ //设置滑动监听事件
+ viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+
+ }
+
+ //滑动到第几张图片的调用的方法,position当前显示图片位置
+ @Override public void onPageSelected(int position) {
+ setSelect(position);
+ textView.setText(((position + 1) + "/" + img_urls.size()));
+ }
+
+ @Override public void onPageScrollStateChanged(int state) {
+
+ }
+ });
+ }
+
+ //启动查看图片时,显示你点击图片
+ public void setSelect(int i) {
+ viewPager.setCurrentItem(i);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ckkj/water/custom/view/SideBarLayout.java b/app/src/main/java/com/ckkj/water/custom/view/SideBarLayout.java
new file mode 100644
index 0000000..86f2cf9
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/custom/view/SideBarLayout.java
@@ -0,0 +1,144 @@
+package com.ckkj.water.custom.view;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import com.ckkj.water.R;
+import com.lzj.sidebar.SideBarSortView;
+
+public class SideBarLayout extends RelativeLayout implements SideBarSortView.OnIndexChangedListener {
+ private View mLayout;
+ private Context mContext;
+ private TextView mTvTips;
+ private SideBarSortView mSortView;
+ private int selectTextColor;
+ private int unselectTextColor;
+ private float selectTextSize;
+ private float unselectTextSize;
+
+ private int wordTextColor;
+ private float wordTextSize;
+ private Drawable wordBackground;
+
+ public SideBarLayout(Context context) {
+ super(context);
+ }
+
+ public SideBarLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(context, attrs);
+ initView();
+
+ }
+
+ public SideBarLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context, attrs);
+ initView();
+ }
+
+ private void init(Context context, AttributeSet attrs) {
+ mContext = context;
+ //获取自定义属性
+ if (attrs != null) {
+ TypedArray ta = mContext.obtainStyledAttributes(attrs, R.styleable.SideBarView);
+ unselectTextColor = ta.getColor(R.styleable.SideBarView_sidebarUnSelectTextColor, Color.parseColor("#999999"));
+ selectTextColor = ta.getColor(R.styleable.SideBarView_sidebarSelectTextColor, Color.parseColor("#2E56D7"));
+ selectTextSize = ta.getDimension(R.styleable.SideBarView_sidebarSelectTextSize, dip2px(mContext, 12));
+ unselectTextSize = ta.getDimension(R.styleable.SideBarView_sidebarUnSelectTextSize, dip2px(mContext, 10));
+
+ wordTextSize = ta.getDimension(R.styleable.SideBarView_sidebarWordTextSize, px2sp(mContext, 45));
+ wordTextColor = ta.getColor(R.styleable.SideBarView_sidebarWordTextColor, Color.parseColor("#FFFFFF"));
+ wordBackground = ta.getDrawable(R.styleable.SideBarView_sidebarWordBackground);
+ if (wordBackground == null) {
+ wordBackground = context.getResources().getDrawable(R.drawable.sort_text_view_hint_bg);
+ }
+ ta.recycle();
+ }
+ }
+
+ private void initView() {
+ //引入布局
+ mLayout = LayoutInflater.from(mContext).inflate(R.layout.view_sidebar_layout, null, true);
+ mTvTips = (TextView) mLayout.findViewById(R.id.tvTips);
+ mSortView = (SideBarSortView) mLayout.findViewById(R.id.sortView);
+ mSortView.setIndexChangedListener(this);
+
+
+ mSortView.setmTextColor(unselectTextColor);
+ mSortView.setmTextSize(unselectTextSize);
+
+ mSortView.setmTextColorChoose(selectTextColor);
+ mSortView.setmTextSizeChoose(selectTextSize);
+ mSortView.invalidate();
+
+ mTvTips.setTextColor(wordTextColor);
+ mTvTips.setTextSize(px2sp(mContext, wordTextSize));
+ mTvTips.setBackground(wordBackground);
+ this.addView(mLayout); //将子布局添加到父容器,才显示控件
+ }
+
+
+ /**
+ * 监听回调:由侧边栏滑动更新Item
+ */
+
+ private OnSideBarLayoutListener mListener;
+
+ public static interface OnSideBarLayoutListener {
+ void onSideBarScrollUpdateItem(String word);
+ }
+
+ public void setSideBarLayout(OnSideBarLayoutListener listener) {
+ this.mListener = listener;
+ }
+
+ /**
+ * 侧边栏滑动 更新Item
+ * @param word 字母
+ */
+ @Override
+ public void onSideBarScrollUpdateItem(String word) {
+ mTvTips.setVisibility(View.VISIBLE);
+ mTvTips.setText(word);
+ if (mListener != null) {
+ mListener.onSideBarScrollUpdateItem(word);
+ }
+ }
+
+ /**
+ * 侧边栏滑动结束 隐藏提示
+ */
+ @Override
+ public void onSideBarScrollEndHideText() {
+ mTvTips.setVisibility(View.GONE);
+ }
+
+ /**
+ * Item滚动更新 侧边栏
+ * @param word
+ */
+ public void onItemScrollUpdateSideBarText(String word) {
+ if (mListener != null) {
+ mSortView.onUpdateSideBarText(word);
+ }
+ }
+
+ public static int dip2px(Context context, float dpValue) {
+ final float scale = context.getResources().getDisplayMetrics().density;
+ return (int) (dpValue * scale + 0.5f);
+ }
+
+ public static int px2sp(Context context, float pxValue) {
+ final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
+ return (int) (pxValue / fontScale + 0.5f);
+ }
+
+}
diff --git a/app/src/main/java/com/ckkj/water/custom/view/SideBarSortView.java b/app/src/main/java/com/ckkj/water/custom/view/SideBarSortView.java
new file mode 100644
index 0000000..671701b
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/custom/view/SideBarSortView.java
@@ -0,0 +1,171 @@
+package com.ckkj.water.custom.view;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewParent;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Created by lzj on 2019/12/31
+ * Describe :字母排序索引
+ */
+public class SideBarSortView extends View {
+ private Canvas mCanvas;
+ private int mSelectIndex = 0;
+ private float mTextSize;
+ private int mTextColor;
+ private float mTextSizeChoose;
+ private int mTextColorChoose;
+ //标记 避免重复调用
+ private boolean isDown = false;
+
+ private float radius = 100f; // 圆的半径
+
+
+ public void setmTextSize(float mTextSize) {
+ this.mTextSize = mTextSize;
+ }
+
+ public void setmTextColor(int mTextColor) {
+ this.mTextColor = mTextColor;
+ }
+
+ public void setmTextSizeChoose(float mTextSizeChoose) {
+ this.mTextSizeChoose = mTextSizeChoose;
+ }
+
+ public void setmTextColorChoose(int mTextColorChoose) {
+ this.mTextColorChoose = mTextColorChoose;
+ }
+
+ public static String[] mList = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K",
+ "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "#"};
+
+ public Paint paint = new Paint();
+ public Paint backgroundPaint;
+
+ public SideBarSortView(Context context) {
+ super(context);
+ }
+
+ public SideBarSortView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ this.mCanvas = canvas;
+ paintText();
+ }
+
+ private void paintText() {
+ //计算每一个字母的高度,总告诉除以字母集合的高度就可以
+ int height = (getHeight()) / mList.length;
+ for (int i = 0; i < mList.length; i++) {
+ if (i == mSelectIndex) {
+ //背景色
+ backgroundPaint = new Paint();
+ backgroundPaint.setColor(Color.parseColor("#F53F3F"));
+ paint.setColor(mTextColorChoose);
+ paint.setTextSize(mTextSizeChoose);
+ /******背景色*******/
+ // 计算圆心位置
+ float centerX = getWidth() / 2;
+ float centerY = getHeight() / 2;
+ mCanvas.drawCircle(centerX, centerY, radius, backgroundPaint);
+ } else {
+ paint.setColor(mTextColor);
+ paint.setTextSize(mTextSize);
+ }
+ paint.setAntiAlias(true);//设置抗锯齿
+ paint.setTypeface(Typeface.DEFAULT_BOLD);
+ //计算每一个字母x轴
+ float paintX = getWidth() / 2F - paint.measureText(mList[i]) / 2;
+ //计算每一个字母Y轴
+ int paintY = height * i + height;
+ //绘画出来这个TextView
+ mCanvas.drawText(mList[i], paintX, paintY, paint);
+ //画完一个以后重置画笔
+ paint.reset();
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ ViewParent parent;
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_MOVE:
+ int index = (int) (event.getY() / getHeight() * mList.length);
+ if (index >= 0 && index < mList.length && mSelectIndex != index) {
+ if (mClickListener != null) {
+ mClickListener.onSideBarScrollUpdateItem(mList[index]);
+ }
+ mSelectIndex = index;
+ invalidate();
+ //改变标记状态
+ isDown = true;
+ }
+ parent = getParent();
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(true);// 请求父级拦截,解决水平滑动冲突
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ if (mClickListener != null) {
+ mClickListener.onSideBarScrollEndHideText();
+ }
+ //改变标记状态
+ isDown = false;
+
+ parent = getParent();
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(false);// 请求父级放行,解决水平滑动冲突
+ }
+ break;
+ }
+ return true;
+ }
+
+ private OnIndexChangedListener mClickListener;
+
+ public static interface OnIndexChangedListener {
+ //滚动位置
+ void onSideBarScrollUpdateItem(String word);
+
+ //隐藏提示文本
+ void onSideBarScrollEndHideText();
+ }
+
+ public void setIndexChangedListener(OnIndexChangedListener listener) {
+ this.mClickListener = listener;
+ }
+
+ /**
+ * Item滚动 更新侧边栏字母
+ *
+ * @param word 字母
+ */
+ public void onUpdateSideBarText(String word) {
+ //手指没触摸才调用
+ if (!isDown) {
+ for (int i = 0; i < mList.length; i++) {
+ if (mList[i].equals(word) && mSelectIndex != i) {
+ mSelectIndex = i;
+ invalidate();
+ }
+ }
+ }
+ }
+
+}
+
diff --git a/app/src/main/java/com/ckkj/water/custom/view/SideBarView.java b/app/src/main/java/com/ckkj/water/custom/view/SideBarView.java
new file mode 100644
index 0000000..ca4ddfc
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/custom/view/SideBarView.java
@@ -0,0 +1,87 @@
+package com.ckkj.water.custom.view;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * @Description TODO
+ * @systemUser 86133
+ * @Author FengJingQi
+ * @Date 04-22-2025 周二 10:00
+ */
+
+public class SideBarView extends View {
+
+ private static final String[] LETTERS = {
+ "A", "B", "C", "D", "E", "F", "G", "H", "I",
+ "J", "K", "L", "M", "N", "O", "P", "Q", "R",
+ "S", "T", "U", "V", "W", "X", "Y", "Z"
+ };
+
+ private Paint paint = new Paint();
+ private int choose = -1;
+ private OnLetterTouchListener listener;
+
+ public SideBarView(Context context) {
+ super(context);
+ }
+
+ public SideBarView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void setOnLetterTouchListener(OnLetterTouchListener listener) {
+ this.listener = listener;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ int height = getHeight();
+ int width = getWidth();
+ int singleHeight = height / LETTERS.length;
+
+ for (int i = 0; i < LETTERS.length; i++) {
+ paint.setColor(Color.BLACK);
+ paint.setTextSize(30);
+ paint.setAntiAlias(true);
+ float xPos = width / 2 - paint.measureText(LETTERS[i]) / 2;
+ float yPos = singleHeight * i + singleHeight;
+ canvas.drawText(LETTERS[i], xPos, yPos, paint);
+ }
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ final int action = event.getAction();
+ final float y = event.getY();
+ final int oldChoose = choose;
+ final int c = (int) (y / getHeight() * LETTERS.length);
+
+ switch (action) {
+ case MotionEvent.ACTION_UP:
+ choose = -1;
+ invalidate();
+ break;
+ default:
+ if (oldChoose != c && c >= 0 && c < LETTERS.length) {
+ if (listener != null) {
+ listener.onLetterTouched(LETTERS[c]);
+ }
+ choose = c;
+ invalidate();
+ }
+ break;
+ }
+ return true;
+ }
+
+ public interface OnLetterTouchListener {
+ void onLetterTouched(String letter);
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/custom/view/SimpleSpinner.java b/app/src/main/java/com/ckkj/water/custom/view/SimpleSpinner.java
new file mode 100644
index 0000000..a29aba2
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/custom/view/SimpleSpinner.java
@@ -0,0 +1,155 @@
+package com.ckkj.water.custom.view;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+
+
+import com.ckkj.water.R;
+
+import java.util.ArrayList;
+
+/**
+ * @author: gaoql
+ * @date: 2022/3/1 11:04
+ * @function:封装下拉选框
+ */
+public class SimpleSpinner extends androidx.appcompat.widget.AppCompatSpinner {
+
+ private ArrayAdapter adapter;
+ private ArrayList contents;
+ private String defaultContent;
+ private Context context;
+ private OnSpinnerEventsListener mListener;
+ public SimpleSpinner(Context context) {
+ super(context);
+ onCreate(context);
+ }
+
+ public SimpleSpinner(Context context, int mode) {
+ super(context, mode);
+ onCreate(context);
+ }
+
+ public SimpleSpinner(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ onCreate(context);
+ }
+
+ public SimpleSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ onCreate(context);
+ }
+ /**
+ * @author gaoql
+ * @time 2022/3/1 11:05
+ * @description 初始化
+ */
+ private void onCreate(Context context){
+ this.context = context;
+ }
+ /**
+ * @author gaoql
+ * @time 2022/3/1 11:44
+ * @description 设置spinner数据
+ */
+ public void setContent(ArrayList contents){
+ this.contents = contents;
+ if(adapter!=null){
+ adapter = null;
+ }
+ adapter = new ArrayAdapter(getContext(), R.layout.sp_item_select,contents.toArray(new String[]{}));
+ adapter.setDropDownViewResource(R.layout.sp_item_dropdown);
+ this.setAdapter(adapter);
+ }
+ /**
+ * @author gaoql
+ * @time 2022/3/3 9:12
+ * @description 设置默认显示内容
+ */
+ public void setDefaultContent(String defaultContent){
+ this.defaultContent = defaultContent;
+ }
+ /**
+ * @author gaoql
+ * @time 2022/3/1 13:26
+ * @description 选择事件
+ */
+ public void setOnItemSelectedListener(OnItemSelectedListener onItemSelectedListener){
+ setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView> parent, View view, int position, long id) {
+ //首次刚加载时 判断是否设置默认显示内容,如果有则显示默认内容,逻辑是把第一条的view内容修改掉
+ if(!TextUtils.isEmpty(defaultContent)){
+ if(view instanceof TextView){
+ TextView textView = (TextView) view;
+ textView.setText(defaultContent);
+ textView.setTextColor(Color.parseColor("#999999"));
+ defaultContent = "";
+ }
+ }else{
+ //把刚刚修改的内容和颜色都修改回来
+ if(position == 0){
+ if(view instanceof TextView){
+ TextView textView = (TextView) view;
+ textView.setText(contents.get(0));
+ textView.setTextColor(Color.parseColor("#333333"));
+ }
+ }
+ onItemSelectedListener.onItemSelected(parent,view,position,id,contents.get(position));
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView> parent) {
+
+ }
+ });
+ }
+ public interface OnItemSelectedListener{
+ void onItemSelected(AdapterView> adapterView, View view, int position, long id,String content);
+ }
+ /**
+ * @author gaoql
+ * @time 2022/3/3 10:22
+ * @description 解决 选择相同选项时 点击不触发问题,逻辑是 如果点击的是相同选项则 强制调用一次条码选择事件
+ */
+ @Override
+ public void setSelection(int position) {
+ super.setSelection(position);
+ boolean sameSelected = position == getSelectedItemPosition();
+ if (sameSelected) {
+ getOnItemSelectedListener().onItemSelected(this, getSelectedView(),
+ position, getSelectedItemId());
+ }
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ super.onWindowFocusChanged(hasWindowFocus);
+ if (mListener != null) {
+ if (hasWindowFocus) {
+ // 收起事件
+ mListener.onSpinnerClosed();
+ } else {
+ // 展开事件
+ mListener.onSpinnerOpened();
+ }
+ }
+ }
+
+ public void setOnSpinnerEventsListener(OnSpinnerEventsListener listener) {
+ mListener = listener;
+ }
+
+ // 定义接口
+ public interface OnSpinnerEventsListener {
+ void onSpinnerOpened();
+ void onSpinnerClosed();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ckkj/water/device/AddDeviceActivity.java b/app/src/main/java/com/ckkj/water/device/AddDeviceActivity.java
new file mode 100644
index 0000000..f7acb61
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/device/AddDeviceActivity.java
@@ -0,0 +1,188 @@
+package com.ckkj.water.device;
+
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
+import android.Manifest;
+import android.content.Intent;
+import android.graphics.drawable.AnimationDrawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.View;
+import android.widget.ImageView;
+
+import androidx.annotation.NonNull;
+
+import com.ckkj.water.ConstantUtil;
+import com.ckkj.water.R;
+import com.ckkj.water.base.BaseActivity;
+import com.ckkj.water.chat.mvp.IndexPresenter;
+import com.ckkj.water.databinding.AddDeviceLayoutBinding;
+import com.ckkj.water.device.mvp.DeviceContract;
+import com.ckkj.water.device.mvp.DeviceImpl;
+import com.ckkj.water.device.mvp.DevicePresenter;
+import com.ckkj.water.network.BaseBean;
+import com.ckkj.water.utils.show.ToastUtil;
+import com.gyf.immersionbar.ImmersionBar;
+import com.maning.mlkitscanner.scan.MNScanManager;
+import com.maning.mlkitscanner.scan.callback.act.MNScanCallback;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import pub.devrel.easypermissions.EasyPermissions;
+
+/**
+ * 添加设备页面
+ */
+public class AddDeviceActivity extends BaseActivity
+ implements DeviceContract.DeviceView, EasyPermissions.PermissionCallbacks {
+ @Override
+ public DevicePresenter createPresenter() {
+ return new DevicePresenter(new DeviceImpl());
+ }
+
+
+ @Override
+ public void initData(Bundle savedInstanceState) {
+ super.initData(savedInstanceState);
+ ImmersionBar.with(this)
+ .statusBarColor(R.color.color_F9F9F9) // 设置状态栏颜色
+ .statusBarDarkFont(true) // 设置状态栏字体为黑色
+ .titleBar(viewBinding.rlTitle)
+ .init();
+ viewBinding.includedTitle.tvTitle.setText(R.string.add_device_title);
+ // 启动帧动画
+ ImageView ivScanAnimation = viewBinding.ivScanAnimation;
+ if (ivScanAnimation.getDrawable() instanceof AnimationDrawable) {
+ AnimationDrawable animationDrawable = (AnimationDrawable) ivScanAnimation.getDrawable();
+ animationDrawable.start();
+ }
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ // 停止帧动画
+ ImageView ivScanAnimation = viewBinding.ivScanAnimation;
+ if (ivScanAnimation.getDrawable() instanceof AnimationDrawable) {
+ AnimationDrawable animationDrawable = (AnimationDrawable) ivScanAnimation.getDrawable();
+ if (animationDrawable.isRunning()) {
+ animationDrawable.stop();
+ viewBinding.ivScanAnimation.setVisibility(GONE);
+ viewBinding.imgDeviceItem.setVisibility(VISIBLE);
+ }
+ }
+ }
+ },3000);
+ initListener();
+ }
+
+
+ private void initListener() {
+ setClick(viewBinding.includedTitle.imgBack);
+ setClick(viewBinding.tvScanAdd);
+ setClick(viewBinding.imgDeviceItem);
+ }
+
+ private void setClick(View view) {
+ view.setOnClickListener(this::onClick);
+ }
+
+ public void onClick(View v){
+ switch (v.getId()){
+ case R.id.img_back:
+ finish();
+ break;
+
+ case R.id.img_device_item:
+ Bundle bundle = new Bundle();
+ bundle.putInt(ConstantUtil.ACTIVITY_NAVIGATE_FLAG,100);
+ goTo(mContext, SelectWifiActivity.class,bundle);
+ break;
+
+ case R.id.tv_scan_add:
+ //检测有无相机权限
+ String readPermission = Manifest.permission.CAMERA;
+ if(!hasRecordPermission(readPermission)){
+ getPermissionRequst(readPermission,"权限申请",
+ "智能浇水需要您的授权访问相机,并使用扫一扫",REQUEST_EVENT_CODE_10001);
+ }else{
+ scanQrcode();
+ }
+ break;
+ }
+ }
+
+ private void scanQrcode() {
+ //默认扫描
+ MNScanManager.startScan(mContext, new MNScanCallback() {
+ @Override
+ public void onActivityResult(int resultCode, Intent data) {
+ switch (resultCode) {
+ case MNScanManager.RESULT_SUCCESS:
+ ArrayList results = data.getStringArrayListExtra(MNScanManager.INTENT_KEY_RESULT_SUCCESS);
+ if(results != null){
+ ToastUtil.showShort(mContext,results.get(0).toString());
+ }
+ break;
+ case MNScanManager.RESULT_FAIL:
+ String resultError = data.getStringExtra(MNScanManager.INTENT_KEY_RESULT_ERROR);
+ break;
+ case MNScanManager.RESULT_CANCLE:
+ ToastUtil.showShort(mContext,"取消扫码");
+ break;
+ }
+ }
+ });
+ }
+
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ // 停止帧动画
+ ImageView ivScanAnimation = viewBinding.ivScanAnimation;
+ if (ivScanAnimation.getDrawable() instanceof AnimationDrawable) {
+ AnimationDrawable animationDrawable = (AnimationDrawable) ivScanAnimation.getDrawable();
+ if (animationDrawable.isRunning()) {
+ animationDrawable.stop();
+ }
+ }
+ }
+
+ @Override
+ public int getLayoutResId(Bundle savedInstanceState) {
+ return 0;
+ }
+
+ @Override
+ public void showError(String message) {
+
+ }
+
+ @Override
+ public void logOut(BaseBean bean) {
+
+
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull @NotNull String[] permissions, @NonNull @NotNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
+ }
+
+ @Override
+ public void onPermissionsGranted(int requestCode, @NonNull List perms) {
+ hidePermissionDialog();
+ if (requestCode == REQUEST_EVENT_CODE_10001) {
+ scanQrcode();
+ }
+ }
+
+ @Override
+ public void onPermissionsDenied(int requestCode, @NonNull List perms) {
+
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/device/DeviceConnectActivity.java b/app/src/main/java/com/ckkj/water/device/DeviceConnectActivity.java
new file mode 100644
index 0000000..0ba19ae
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/device/DeviceConnectActivity.java
@@ -0,0 +1,97 @@
+package com.ckkj.water.device;
+
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
+import android.graphics.drawable.AnimationDrawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+
+import com.ckkj.water.R;
+import com.ckkj.water.base.BaseActivity;
+import com.ckkj.water.databinding.DeviceConnectLayoutBinding;
+import com.ckkj.water.device.mvp.DeviceContract;
+import com.ckkj.water.device.mvp.DeviceImpl;
+import com.ckkj.water.device.mvp.DevicePresenter;
+import com.ckkj.water.network.BaseBean;
+import com.ckkj.water.utils.show.ToastUtil;
+import com.gyf.immersionbar.ImmersionBar;
+
+/**
+ * 连接设备页面
+ */
+public class DeviceConnectActivity extends BaseActivity
+ implements DeviceContract.DeviceView {
+
+ private AnimationDrawable animationDrawable;
+ private Handler handler = new Handler(Looper.getMainLooper());
+ private static final long ANIMATION_DURATION = 5000L; // 5秒
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ public void initData(Bundle savedInstanceState) {
+ super.initData(savedInstanceState);
+ ImmersionBar.with(this)
+ .statusBarColor(R.color.color_F9F9F9)
+ .statusBarDarkFont(true)
+ .titleBar(viewBinding.rlTitle)
+ .init();
+ viewBinding.includedTitle.tvTitle.setText(R.string.select_wifi_tip);
+ startConnectAnimation();
+ }
+
+ private void startConnectAnimation() {
+ viewBinding.ivConnectAnimation.setImageResource(R.drawable.anim_connect_device);
+ animationDrawable = (AnimationDrawable) viewBinding.ivConnectAnimation.getDrawable();
+ if (animationDrawable != null) {
+ animationDrawable.start();
+ handler.postDelayed(() -> {
+ if (animationDrawable != null && animationDrawable.isRunning()) {
+ animationDrawable.stop();
+ }
+ viewBinding.linearConnectIng.setVisibility(GONE);
+ viewBinding.linearSuccess.setVisibility(VISIBLE);
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ goTo(mContext, DeviceReNameActivity.class,null);
+ }
+ },1000);
+ }, ANIMATION_DURATION);
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ handler.removeCallbacksAndMessages(null);
+ if (animationDrawable != null && animationDrawable.isRunning()) {
+ animationDrawable.stop();
+ }
+ }
+
+ @Override
+ public DevicePresenter createPresenter() {
+ return new DevicePresenter(new DeviceImpl());
+ }
+
+ @Override
+ public int getLayoutResId(Bundle savedInstanceState) {
+ return 0;
+ }
+
+ @Override
+ public void showError(String message) {
+
+ }
+
+ @Override
+ public void logOut(BaseBean bean) {
+
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/device/DeviceControlActivity.java b/app/src/main/java/com/ckkj/water/device/DeviceControlActivity.java
new file mode 100644
index 0000000..3ac6857
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/device/DeviceControlActivity.java
@@ -0,0 +1,134 @@
+package com.ckkj.water.device;
+
+import android.os.Bundle;
+import android.view.View;
+
+import com.chad.library.adapter.base.BaseQuickAdapter;
+import com.ckkj.water.R;
+import com.ckkj.water.base.BaseActivity;
+import com.ckkj.water.custom.GridSpacingItemDecoration;
+import com.ckkj.water.custom.view.CustomAttachPopup;
+import com.ckkj.water.custom.view.CustomBubbleAttachPopup;
+import com.ckkj.water.databinding.DeviceControlLayoutBinding;
+import com.ckkj.water.device.adapter.DeviceTimeSelectAdapter;
+import com.ckkj.water.device.mvp.DeviceContract;
+import com.ckkj.water.device.mvp.DeviceImpl;
+import com.ckkj.water.device.mvp.DevicePresenter;
+import com.ckkj.water.network.BaseBean;
+import com.gyf.immersionbar.ImmersionBar;
+import com.lxj.xpopup.XPopup;
+import com.lxj.xpopup.core.BasePopupView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 设备控制页面
+ */
+public class DeviceControlActivity extends BaseActivity
+ implements DeviceContract.DeviceView {
+
+ private List mTimeList = new ArrayList<>();
+ private DeviceTimeSelectAdapter mAdapter;
+ @Override
+ public DevicePresenter createPresenter() {
+ return new DevicePresenter(new DeviceImpl());
+ }
+
+ @Override
+ public void initData(Bundle savedInstanceState) {
+ super.initData(savedInstanceState);
+ mTimeList.add("1min");
+ mTimeList.add("2min");
+ mTimeList.add("3min");
+ mTimeList.add("4min");
+ mTimeList.add("5min");
+ mTimeList.add("10min");
+ mTimeList.add("15min");
+ mTimeList.add("30min");
+ mTimeList.add("1h");
+ mTimeList.add("2h");
+ mTimeList.add("3h");
+ mTimeList.add("4h");
+ ImmersionBar.with(this)
+ .statusBarColor(R.color.color_F9F9F9) // 设置状态栏颜色
+ .statusBarDarkFont(true) // 设置状态栏字体为黑色
+ .titleBar(viewBinding.rlTitle)
+ .init();
+ viewBinding.includedTitle.tvTitle.setText("设备名称");
+ viewBinding.includedTitle.imgSetting.setVisibility(View.VISIBLE);
+ mAdapter = new DeviceTimeSelectAdapter(mContext,R.layout.device_work_time_item,mTimeList);
+ viewBinding.rvTimeList.setAdapter(mAdapter);
+ viewBinding.rvTimeList.addItemDecoration(new GridSpacingItemDecoration(4, 10, true));
+ mAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
+ @Override
+ public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
+ mAdapter.setOnSelPosition(position);
+ }
+ });
+
+ initListener();
+ }
+
+ private void initListener() {
+ setClick(viewBinding.includedTitle.imgBack);
+ setClick(viewBinding.includedTitle.imgSetting);
+ setClick(viewBinding.linearBt);
+ setClick(viewBinding.linearWifi);
+ setClick(viewBinding.linearBat);
+ setClick(viewBinding.tvConfirm);
+ }
+
+ private void setClick(View view) {
+ view.setOnClickListener(this::onClick);
+ }
+
+ public void onClick(View v){
+ switch (v.getId()){
+ case R.id.img_back:
+ finish();
+ break;
+
+ case R.id.img_setting:
+ goTo(mContext, DeviceSettingsActivity.class,null);
+ break;
+
+ case R.id.linear_bt:
+
+ break;
+
+ case R.id.linear_wifi:
+
+ break;
+
+ case R.id.linear_bat:
+ new XPopup.Builder(mContext)
+ .isTouchThrough(true)
+ .isDestroyOnDismiss(true) //对于只使用一次的弹窗,推荐设置这个
+ .atView(viewBinding.linearBat)
+ .hasShadowBg(false) // 去掉半透明背景
+ .asCustom(new CustomBubbleAttachPopup(mContext,"设备电量:40% 更新时间: 05- 11 18:00"))
+ .show();
+ break;
+
+ case R.id.tv_confirm:
+ showCenterTipDialog(R.mipmap.icon_dialog_success,"开始浇水");
+ break;
+ }
+ }
+
+ @Override
+ public int getLayoutResId(Bundle savedInstanceState) {
+ return 0;
+ }
+
+ @Override
+ public void showError(String message) {
+
+ }
+
+ @Override
+ public void logOut(BaseBean bean) {
+
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/device/DeviceInfoActivity.java b/app/src/main/java/com/ckkj/water/device/DeviceInfoActivity.java
new file mode 100644
index 0000000..a5cf4a2
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/device/DeviceInfoActivity.java
@@ -0,0 +1,68 @@
+package com.ckkj.water.device;
+
+import android.os.Bundle;
+import android.view.View;
+
+import com.ckkj.water.R;
+import com.ckkj.water.base.BaseActivity;
+import com.ckkj.water.databinding.DeviceInfoLayoutBinding;
+import com.ckkj.water.device.mvp.DeviceContract;
+import com.ckkj.water.device.mvp.DeviceImpl;
+import com.ckkj.water.device.mvp.DevicePresenter;
+import com.ckkj.water.network.BaseBean;
+import com.gyf.immersionbar.ImmersionBar;
+
+/**
+ * 设备信息
+ */
+public class DeviceInfoActivity extends BaseActivity
+ implements DeviceContract.DeviceView {
+ @Override
+ public DevicePresenter createPresenter() {
+ return new DevicePresenter(new DeviceImpl());
+ }
+
+ @Override
+ public void initData(Bundle savedInstanceState) {
+ super.initData(savedInstanceState);
+
+ ImmersionBar.with(this)
+ .statusBarColor(R.color.white) // 设置状态栏颜色
+ .statusBarDarkFont(true) // 设置状态栏字体为黑色
+ .titleBar(viewBinding.rlTitle)
+ .init();
+ viewBinding.includedTitle.tvTitle.setText(R.string.device_setting_info);
+ initListener();
+ }
+
+ private void initListener() {
+ setClick(viewBinding.includedTitle.imgBack);
+ }
+
+ private void setClick(View view) {
+ view.setOnClickListener(this::onClick);
+ }
+
+ public void onClick(View v){
+ switch (v.getId()){
+ case R.id.img_back:
+ finish();
+ break;
+ }
+ }
+
+ @Override
+ public int getLayoutResId(Bundle savedInstanceState) {
+ return 0;
+ }
+
+ @Override
+ public void showError(String message) {
+
+ }
+
+ @Override
+ public void logOut(BaseBean bean) {
+
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/device/DeviceReNameActivity.java b/app/src/main/java/com/ckkj/water/device/DeviceReNameActivity.java
new file mode 100644
index 0000000..4aaeb12
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/device/DeviceReNameActivity.java
@@ -0,0 +1,79 @@
+package com.ckkj.water.device;
+
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.View;
+
+import com.ckkj.water.R;
+import com.ckkj.water.base.BaseActivity;
+import com.ckkj.water.databinding.SetDeviceNameLayoutBinding;
+import com.ckkj.water.device.mvp.DeviceContract;
+import com.ckkj.water.device.mvp.DeviceImpl;
+import com.ckkj.water.device.mvp.DevicePresenter;
+import com.ckkj.water.network.BaseBean;
+import com.ckkj.water.utils.show.ToastUtil;
+import com.gyf.immersionbar.ImmersionBar;
+
+/**
+ * 重置设备名称
+ */
+public class DeviceReNameActivity extends BaseActivity
+ implements DeviceContract.DeviceView {
+ @Override
+ public DevicePresenter createPresenter() {
+ return new DevicePresenter(new DeviceImpl());
+ }
+
+ @Override
+ public void initData(Bundle savedInstanceState) {
+ super.initData(savedInstanceState);
+ ImmersionBar.with(this)
+ .statusBarColor(R.color.color_F9F9F9) // 设置状态栏颜色
+ .statusBarDarkFont(true) // 设置状态栏字体为黑色
+ .titleBar(viewBinding.rlTitle)
+ .init();
+ viewBinding.includedTitle.tvTitle.setText(R.string.set_name);
+ initListener();
+ }
+
+ private void initListener() {
+ setClick(viewBinding.includedTitle.imgBack);
+ setClick(viewBinding.tvCommit);
+ }
+
+ private void setClick(View view) {
+ view.setOnClickListener(this::onClick);
+ }
+
+ public void onClick(View v){
+ switch (v.getId()){
+ case R.id.img_back:
+ finish();
+ break;
+
+ case R.id.tv_commit:
+ if(TextUtils.isEmpty(viewBinding.etDeviceName.getText().toString().trim())){
+ ToastUtil.showShort(mContext,getString(R.string.input_device_name_hint));
+ return;
+ }
+ ToastUtil.showShort(mContext,"名称保存成功");
+ goTo(mContext,DeviceControlActivity.class,null);
+ break;
+ }
+ }
+
+ @Override
+ public int getLayoutResId(Bundle savedInstanceState) {
+ return 0;
+ }
+
+ @Override
+ public void showError(String message) {
+
+ }
+
+ @Override
+ public void logOut(BaseBean bean) {
+
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/device/DeviceSettingsActivity.java b/app/src/main/java/com/ckkj/water/device/DeviceSettingsActivity.java
new file mode 100644
index 0000000..c6fd230
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/device/DeviceSettingsActivity.java
@@ -0,0 +1,143 @@
+package com.ckkj.water.device;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import com.ckkj.water.ConstantUtil;
+import com.ckkj.water.R;
+import com.ckkj.water.base.BaseActivity;
+import com.ckkj.water.base.BaseFragment;
+import com.ckkj.water.databinding.DeviceSettingsLayoutBinding;
+import com.ckkj.water.device.mvp.DeviceContract;
+import com.ckkj.water.device.mvp.DeviceImpl;
+import com.ckkj.water.device.mvp.DevicePresenter;
+import com.ckkj.water.network.BaseBean;
+import com.ckkj.water.utils.show.ToastUtil;
+import com.gyf.immersionbar.ImmersionBar;
+
+/**
+ * 设备控制页面
+ */
+public class DeviceSettingsActivity extends BaseActivity
+ implements DeviceContract.DeviceView {
+ @Override
+ public DevicePresenter createPresenter() {
+ return new DevicePresenter(new DeviceImpl());
+ }
+
+ @Override
+ public void initData(Bundle savedInstanceState) {
+ super.initData(savedInstanceState);
+
+ ImmersionBar.with(this)
+ .statusBarColor(R.color.white) // 设置状态栏颜色
+ .statusBarDarkFont(true) // 设置状态栏字体为黑色
+ .titleBar(viewBinding.rlTitle)
+ .init();
+ viewBinding.includedTitle.tvTitle.setText(R.string.settings_title);
+ initListener();
+ }
+
+ private void initListener() {
+ setClick(viewBinding.includedTitle.imgBack);
+ setClick(viewBinding.rlDevName);
+ setClick(viewBinding.rlDevInfo);
+ setClick(viewBinding.rlDevWifiManager);
+ setClick(viewBinding.rlDevUnbind);
+ }
+
+ private void setClick(View view) {
+ view.setOnClickListener(this::onClick);
+ }
+
+ public void onClick(View v){
+ switch (v.getId()){
+ case R.id.img_back:
+ finish();
+ break;
+
+ case R.id.rl_dev_name:
+ showEditDialog(getString(R.string.device_setting_name),
+ getString(R.string.input_device_name_hint),
+ true,
+ getString(R.string.cancel_text),
+ getString(R.string.confirm_text), new OnDialogClick() {
+ @Override
+ public void onCancelClick() {
+ }
+
+ @Override
+ public void onConfirmClisk() {
+ }
+
+ @Override
+ public void onConfirmClisk(String content) {
+ viewBinding.tvDeviceName.setText(content);
+ }
+
+ @Override
+ public void onView1Click() {
+ }
+ });
+ break;
+
+ case R.id.rl_dev_info:
+ goTo(mContext, DeviceInfoActivity.class,null);
+ break;
+
+ case R.id.rl_dev_wifi_manager:
+ Bundle bundle = new Bundle();
+ bundle.putInt(ConstantUtil.ACTIVITY_NAVIGATE_FLAG,200);
+ goToForResult(mContext, SelectWifiActivity.class,REQUEST_EVENT_CODE_10001,bundle);
+ break;
+
+ case R.id.rl_dev_unbind:
+ showDialogtext(null, false, false, true,
+ getString(R.string.tip_text), getString(R.string.tip_unbind_device),
+ getString(R.string.cancel_text), getString(R.string.confirm_text), new BaseFragment.OnDialogClick() {
+ @Override
+ public void onCancelClick() {
+ }
+
+ @Override
+ public void onConfirmClick() {
+ ToastUtil.showShort(mContext,"确定");
+ }
+ });
+ break;
+
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (data != null) {
+ if (resultCode == RESULT_OK) {
+ switch (requestCode) {
+ case REQUEST_EVENT_CODE_10001:
+ ToastUtil.showShort(mContext,data.getStringExtra("wifi_name"));
+ break;
+ }
+ }
+ }
+ }
+
+ @Override
+ public int getLayoutResId(Bundle savedInstanceState) {
+ return 0;
+ }
+
+ @Override
+ public void showError(String message) {
+
+ }
+
+ @Override
+ public void logOut(BaseBean bean) {
+
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/device/SelectWifiActivity.java b/app/src/main/java/com/ckkj/water/device/SelectWifiActivity.java
new file mode 100644
index 0000000..b9e09fe
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/device/SelectWifiActivity.java
@@ -0,0 +1,222 @@
+package com.ckkj.water.device;
+
+import android.Manifest;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.View;
+
+import androidx.core.app.ActivityCompat;
+import androidx.recyclerview.widget.DividerItemDecoration;
+
+import com.ckkj.water.ConstantUtil;
+import com.ckkj.water.R;
+import com.ckkj.water.base.BaseActivity;
+import com.ckkj.water.databinding.DeviceResetWifiBinding;
+import com.ckkj.water.device.mvp.DeviceContract;
+import com.ckkj.water.device.mvp.DeviceImpl;
+import com.ckkj.water.device.mvp.DevicePresenter;
+import com.ckkj.water.network.BaseBean;
+import com.ckkj.water.utils.show.ToastUtil;
+import com.ckkj.water.utils.text.TextUtil;
+import com.gyf.immersionbar.ImmersionBar;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class SelectWifiActivity extends BaseActivity
+ implements DeviceContract.DeviceView {
+
+ private WifiManager wifiManager;
+ private WifiListAdapter wifiListAdapter;
+ private WifiScanReceiver wifiScanReceiver;
+ //100 来自设备添加, 200 来自设备信息
+ private int WITCH_FROM_CODE = 100;
+
+ private String mWifiName = "";
+
+ @Override
+ public DevicePresenter createPresenter() {
+ return new DevicePresenter(new DeviceImpl());
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
+ wifiScanReceiver = new WifiScanReceiver();
+
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ registerReceiver(wifiScanReceiver, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ unregisterReceiver(wifiScanReceiver);
+ }
+
+ @Override
+ public void initData(Bundle savedInstanceState) {
+ super.initData(savedInstanceState);
+ Bundle bundle = getIntent().getExtras();
+ if(bundle != null){
+ WITCH_FROM_CODE = bundle.getInt(ConstantUtil.ACTIVITY_NAVIGATE_FLAG);
+ }
+
+ ImmersionBar.with(this)
+ .statusBarColor(R.color.color_F9F9F9)
+ .statusBarDarkFont(true)
+ .titleBar(viewBinding.rlTitle)
+ .init();
+ if(WITCH_FROM_CODE == 200){
+ viewBinding.tvNext.setText(getString(R.string.confirm_text));
+ }
+ viewBinding.includedTitle.tvTitle.setText(R.string.select_wifi_tip);
+
+ initListener();
+ initWifiList();
+ }
+
+ private void initWifiList() {
+ wifiListAdapter = new WifiListAdapter();
+ viewBinding.rvWifiList.setAdapter(wifiListAdapter);
+ wifiListAdapter.setOnWifiItemClickListener(wifiName -> {
+ mWifiName = wifiName;
+ viewBinding.etWifiName.setText(wifiName);
+ viewBinding.rvWifiList.setVisibility(View.GONE);
+ viewBinding.linearWifiInfo.setVisibility(View.VISIBLE);
+ viewBinding.tvNext.setVisibility(View.VISIBLE);
+ });
+ }
+
+ private void initListener() {
+ setClick(viewBinding.includedTitle.imgBack);
+ setClick(viewBinding.tvSelectWifi);
+ setClick(viewBinding.tvNext);
+ }
+
+ private void setClick(View view) {
+ view.setOnClickListener(this::onClick);
+ }
+
+ public void onClick(View view){
+ switch (view.getId()){
+ case R.id.img_back:
+ finish();
+ break;
+
+ case R.id.tv_select_wifi:
+ scanWifi();
+ break;
+
+ case R.id.tv_next:
+ if(TextUtils.isEmpty(mWifiName)){
+ ToastUtil.showShort(mContext,getString(R.string.toast_select_wifi_tip));
+ return;
+ }
+ if(TextUtils.isEmpty(viewBinding.etInputPsd.getText().toString())){
+ ToastUtil.showShort(mContext,getString(R.string.toast_select_wifi_psd_tip));
+ return;
+ }
+ if(WITCH_FROM_CODE == 100){
+ goTo(mContext,DeviceConnectActivity.class,null);
+ }else{
+ Intent intent = new Intent();
+ intent.putExtra("wifi_name",mWifiName);
+ intent.putExtra("wifi_psd",viewBinding.etInputPsd.getText().toString());
+ setResult(RESULT_OK,intent);
+ finish();
+ }
+ break;
+ }
+ }
+
+ private void scanWifi() {
+ showLoadingDialog();
+ viewBinding.tvNext.setVisibility(View.GONE);
+ if (wifiManager != null && wifiManager.isWifiEnabled()) {
+ wifiManager.startScan();
+ } else {
+ ToastUtil.showShort(mContext, getString(R.string.wifi_not_enabled));
+ hideDialogLoading();
+ }
+ }
+
+ private void displayWifiList(List scanResults) {
+ Set wifiSet = new HashSet<>();
+ List wifiNameList = new ArrayList<>();
+
+ for (ScanResult result : scanResults) {
+ String wifiName = result.SSID;
+ if (wifiName != null && !wifiName.isEmpty() && !wifiSet.contains(wifiName)) {
+ wifiSet.add(wifiName);
+ wifiNameList.add(wifiName);
+ }
+ }
+ hideDialogLoading();
+
+ if (wifiNameList.isEmpty()) {
+ ToastUtil.showShort(mContext, getString(R.string.no_wifi_found));
+ viewBinding.linearWifiInfo.setVisibility(View.VISIBLE);
+ viewBinding.rvWifiList.setVisibility(View.GONE);
+ } else {
+ wifiListAdapter.setData(wifiNameList);
+ viewBinding.linearWifiInfo.setVisibility(View.GONE);
+ viewBinding.rvWifiList.setVisibility(View.VISIBLE);
+ }
+ }
+
+ private class WifiScanReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(intent.getAction())) {
+ boolean success = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false);
+ if (success) {
+ if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+ // TODO: Consider calling
+ // ActivityCompat#requestPermissions
+ // here to request the missing permissions, and then overriding
+ // public void onRequestPermissionsResult(int requestCode, String[] permissions,
+ // int[] grantResults)
+ // to handle the case where the user grants the permission. See the documentation
+ // for ActivityCompat#requestPermissions for more details.
+ return;
+ }
+ List results = wifiManager.getScanResults();
+ displayWifiList(results);
+ } else {
+ ToastUtil.showShort(mContext, getString(R.string.wifi_scan_failed));
+ }
+ }
+ }
+ }
+
+ @Override
+ public int getLayoutResId(Bundle savedInstanceState) {
+ return 0;
+ }
+
+ @Override
+ public void showError(String message) {
+ ToastUtil.showShort(mContext,message);
+ }
+
+ @Override
+ public void logOut(BaseBean bean) {
+
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/device/WifiListAdapter.java b/app/src/main/java/com/ckkj/water/device/WifiListAdapter.java
new file mode 100644
index 0000000..5220c46
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/device/WifiListAdapter.java
@@ -0,0 +1,69 @@
+package com.ckkj.water.device;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.ckkj.water.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class WifiListAdapter extends RecyclerView.Adapter {
+
+ private List wifiList = new ArrayList<>();
+ private OnWifiItemClickListener listener;
+
+ public interface OnWifiItemClickListener {
+ void onWifiItemClick(String wifiName);
+ }
+
+ public void setOnWifiItemClickListener(OnWifiItemClickListener listener) {
+ this.listener = listener;
+ }
+
+ public void setData(List list) {
+ this.wifiList = list;
+ notifyDataSetChanged();
+ }
+
+ @NonNull
+ @Override
+ public WifiViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.wifi_list_item, parent, false);
+ return new WifiViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull WifiViewHolder holder, int position) {
+ String wifiName = wifiList.get(position);
+ holder.tvWifiName.setText(wifiName);
+ holder.itemView.setOnClickListener(v -> {
+ if (listener != null) {
+ listener.onWifiItemClick(wifiName);
+ }
+ });
+ }
+
+ @Override
+ public int getItemCount() {
+ return wifiList.size();
+ }
+
+ static class WifiViewHolder extends RecyclerView.ViewHolder {
+ ImageView ivWifiIcon;
+ TextView tvWifiName;
+
+ public WifiViewHolder(@NonNull View itemView) {
+ super(itemView);
+ ivWifiIcon = itemView.findViewById(R.id.iv_wifi_icon);
+ tvWifiName = itemView.findViewById(R.id.tv_wifi_name);
+ }
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/device/adapter/DeviceTimeSelectAdapter.java b/app/src/main/java/com/ckkj/water/device/adapter/DeviceTimeSelectAdapter.java
new file mode 100644
index 0000000..ee05d61
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/device/adapter/DeviceTimeSelectAdapter.java
@@ -0,0 +1,43 @@
+package com.ckkj.water.device.adapter;
+
+import android.content.Context;
+import android.widget.RelativeLayout;
+
+import androidx.annotation.Nullable;
+
+import com.chad.library.adapter.base.BaseQuickAdapter;
+import com.chad.library.adapter.base.BaseViewHolder;
+import com.ckkj.water.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DeviceTimeSelectAdapter extends BaseQuickAdapter {
+ private Context mContext;
+ private List mDatas = new ArrayList<>();
+
+ private int mSelPostion = -1;
+ public DeviceTimeSelectAdapter(Context context,int layoutResId, @Nullable List data) {
+ super(layoutResId, data);
+ this.mContext = context;
+ this.mDatas = data;
+ }
+
+ @Override
+ protected void convert(BaseViewHolder helper, String item) {
+ helper.setText(R.id.tv_itme,item);
+ RelativeLayout relTextBg = helper.getView(R.id.rel_text_bg);
+ if(helper.getLayoutPosition() == mSelPostion){
+ relTextBg.setBackground(mContext.getResources().getDrawable(R.drawable.shape_green_button_bg));
+ helper.setTextColor(R.id.tv_itme,mContext.getResources().getColor(R.color.white));
+ }else{
+ relTextBg.setBackground(mContext.getResources().getDrawable(R.drawable.shape_f7f7f7_line_button_bg));
+ helper.setTextColor(R.id.tv_itme,mContext.getResources().getColor(R.color.color_838080));
+ }
+ }
+
+ public void setOnSelPosition(int position) {
+ this.mSelPostion = position;
+ notifyDataSetChanged();
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/device/mvp/DeviceContract.java b/app/src/main/java/com/ckkj/water/device/mvp/DeviceContract.java
new file mode 100644
index 0000000..baeb02c
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/device/mvp/DeviceContract.java
@@ -0,0 +1,15 @@
+package com.ckkj.water.device.mvp;
+
+import com.ckkj.water.base.IView;
+
+public class DeviceContract {
+ public interface CallBack {
+ void success(T msg);
+ void fail(String msg);
+ }
+ public interface DataModel{
+ }
+
+ public interface DeviceView extends IView {
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/device/mvp/DeviceImpl.java b/app/src/main/java/com/ckkj/water/device/mvp/DeviceImpl.java
new file mode 100644
index 0000000..208f4d2
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/device/mvp/DeviceImpl.java
@@ -0,0 +1,5 @@
+package com.ckkj.water.device.mvp;
+
+public class DeviceImpl implements DeviceContract.DataModel{
+
+}
diff --git a/app/src/main/java/com/ckkj/water/device/mvp/DevicePresenter.java b/app/src/main/java/com/ckkj/water/device/mvp/DevicePresenter.java
new file mode 100644
index 0000000..5146d77
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/device/mvp/DevicePresenter.java
@@ -0,0 +1,11 @@
+package com.ckkj.water.device.mvp;
+
+import com.ckkj.water.base.BasePresenter;
+
+public class DevicePresenter extends BasePresenter {
+ private DeviceImpl impl;
+
+ public DevicePresenter(DeviceImpl impl) {
+ this.impl = impl;
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/dialog/A_custom_base_Dialog.java b/app/src/main/java/com/ckkj/water/dialog/A_custom_base_Dialog.java
new file mode 100644
index 0000000..d793671
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/dialog/A_custom_base_Dialog.java
@@ -0,0 +1,109 @@
+package com.ckkj.water.dialog;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.Context;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.ckkj.water.R;
+
+
+/**
+ * @author messi.mo
+ * @date 2017/11/22
+ * 底部选择框
+ */
+
+public class A_custom_base_Dialog {
+
+ public static View layoutView;
+ private Dialog mDialog;
+ private Context mContext;
+ private TextView tvOneMonth,tvThreeMonth,tvSixMonth,tvStartTime,tvEndTime,tvClear,tvConfirm;
+ private ImageView imgClose,imgClear;
+ private String startTime = "";
+ private String endTime = "";
+ private DialogListener mListener;
+ private Animation animUp;
+ private Animation animDown;
+
+
+
+
+ public void setOnDialogListener(DialogListener listener){
+ this.mListener = listener;
+ }
+
+ public interface DialogListener{
+ void callBackDatas(String startTime,String endTime);
+ }
+
+
+ public A_custom_base_Dialog(Activity context, int layoutId) {
+ this.mContext = context;
+ mDialog = new Dialog(context, R.style.ActionSheetDialogStyle);
+ //填充对话框的布局
+ layoutView = LayoutInflater.from(context).inflate(layoutId, null);
+ //将布局设置给Dialog
+ mDialog.setContentView(layoutView);
+ // 为弹出和关闭设置动画
+ animUp = AnimationUtils.loadAnimation(mContext, R.anim.popup_anim_up);
+ animDown = AnimationUtils.loadAnimation(mContext, R.anim.popup_anim_down);
+ try {
+ //设置滑出动画
+ layoutView.startAnimation(animUp);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ //获取当前Activity所在的窗体
+ Window dialogWindow = mDialog.getWindow();
+ mDialog.setCanceledOnTouchOutside(true);
+ //设置Dialog从窗体底部弹出
+ if (dialogWindow != null) {
+ dialogWindow.setGravity(Gravity.BOTTOM);
+ //获得窗体的属性
+ WindowManager.LayoutParams lp = dialogWindow.getAttributes();
+ lp.width = context.getWindowManager().getDefaultDisplay().getWidth();
+ //设置Dialog距离底部的距离
+ //lp.y = 30;
+ // 将属性设置给窗体
+ dialogWindow.setAttributes(lp);
+ }
+ imgClose = layoutView.findViewById(R.id.img_close);
+ imgClose.setOnClickListener(onclick);
+ }
+
+ View.OnClickListener onclick = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()){
+ case R.id.img_close:
+ mDialog.dismiss();
+ break;
+ }
+ }
+ };
+
+
+ public void show() {
+ if(mDialog != null){
+ mDialog.show();
+ }
+ }
+
+ public void dismiss() {
+ if(mDialog != null){
+ mDialog.dismiss();
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ckkj/water/dialog/CenterTipDialog.java b/app/src/main/java/com/ckkj/water/dialog/CenterTipDialog.java
new file mode 100644
index 0000000..68e1504
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/dialog/CenterTipDialog.java
@@ -0,0 +1,95 @@
+package com.ckkj.water.dialog;
+
+import android.annotation.SuppressLint;
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.Gravity;
+import android.view.WindowManager;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.ckkj.water.R;
+
+import java.util.Objects;
+
+/**
+ * 居中提示弹窗
+ */
+public class CenterTipDialog extends Dialog {
+
+ private int imageRes = 0;
+ private String title = "";
+ private ImageView imageView;
+ private TextView textView;
+ private Handler handler = new Handler();
+ private Runnable dismissRunnable;
+
+ public CenterTipDialog(Context context) {
+ super(context);
+ }
+
+ public CenterTipDialog(Context context, int imageRes, String title) {
+ super(context);
+ this.imageRes = imageRes;
+ this.title = title;
+ }
+
+ public CenterTipDialog(Context context, int theme, int imageRes, String title) {
+ super(context, theme);
+ this.imageRes = imageRes;
+ this.title = title;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ init();
+ }
+
+ @SuppressLint("ResourceAsColor")
+ private void init() {
+ getWindow().setBackgroundDrawableResource(R.color.translucent_black_95);
+ getWindow().setDimAmount(0.5f);
+ getWindow().setGravity(Gravity.CENTER);
+ setCancelable(true);
+ setCanceledOnTouchOutside(true);
+ setContentView(R.layout.center_tip_dialog);
+
+ imageView = findViewById(R.id.image);
+ textView = findViewById(R.id.tv_title);
+
+ if (imageRes != 0) {
+ imageView.setImageResource(imageRes);
+ }
+ textView.setText(title);
+
+ WindowManager.LayoutParams params = Objects.requireNonNull(getWindow()).getAttributes();
+ params.width = WindowManager.LayoutParams.MATCH_PARENT;
+ params.height = WindowManager.LayoutParams.MATCH_PARENT;
+ getWindow().setAttributes(params);
+ }
+
+ @Override
+ public void show() {
+ super.show();
+ if (dismissRunnable != null) {
+ handler.postDelayed(dismissRunnable, 800);
+ }
+ }
+
+ public void setDismissDelay(long delayMillis, Runnable onDismiss) {
+ this.dismissRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (isShowing()) {
+ dismiss();
+ }
+ if (onDismiss != null) {
+ onDismiss.run();
+ }
+ }
+ };
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/dialog/ContentReportDialog.java b/app/src/main/java/com/ckkj/water/dialog/ContentReportDialog.java
new file mode 100644
index 0000000..29f535a
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/dialog/ContentReportDialog.java
@@ -0,0 +1,201 @@
+package com.ckkj.water.dialog;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.Context;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.ImageView;
+import android.widget.RadioButton;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.chad.library.adapter.base.BaseQuickAdapter;
+import com.chad.library.adapter.base.BaseViewHolder;
+import com.ckkj.water.R;
+import com.ckkj.water.mine.entity.ReportEntity;
+import com.ckkj.water.utils.show.ToastUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * @author messi.mo
+ * @date 2017/11/22
+ * 底部选择框
+ */
+
+public class ContentReportDialog {
+
+ public static View layoutView;
+ private Dialog mDialog;
+ private Context mContext;
+ private TextView tvCommit,tvThreeMonth,tvSixMonth,tvStartTime,tvEndTime,tvClear,tvConfirm;
+ private ImageView imgClose,imgClear;
+ private String startTime = "";
+ private String endTime = "";
+ private DialogListener mListener;
+ private Animation animUp;
+ private Animation animDown;
+ //////////////////////////////////////////////
+ private RecyclerView rvDatas;
+ private String mReason;
+ private List reportList = new ArrayList<>();
+
+
+
+ public void setOnDialogListener(DialogListener listener){
+ this.mListener = listener;
+ }
+
+ public interface DialogListener{
+ void callBackDatas(String reason);
+ }
+
+
+ public ContentReportDialog(Activity context, int layoutId, String reason) {
+ this.mContext = context;
+ this.mReason = reason;
+ mDialog = new Dialog(context, R.style.ActionSheetDialogStyle);
+ //填充对话框的布局
+ layoutView = LayoutInflater.from(context).inflate(layoutId, null);
+ //将布局设置给Dialog
+ mDialog.setContentView(layoutView);
+ // 为弹出和关闭设置动画
+ animUp = AnimationUtils.loadAnimation(mContext, R.anim.popup_anim_up);
+ animDown = AnimationUtils.loadAnimation(mContext, R.anim.popup_anim_down);
+ try {
+ //设置滑出动画
+ layoutView.startAnimation(animUp);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ //获取当前Activity所在的窗体
+ Window dialogWindow = mDialog.getWindow();
+ mDialog.setCanceledOnTouchOutside(true);
+ //设置Dialog从窗体底部弹出
+ if (dialogWindow != null) {
+ dialogWindow.setGravity(Gravity.BOTTOM);
+ //获得窗体的属性
+ WindowManager.LayoutParams lp = dialogWindow.getAttributes();
+ lp.width = context.getWindowManager().getDefaultDisplay().getWidth();
+ //设置Dialog距离底部的距离
+ //lp.y = 30;
+ // 将属性设置给窗体
+ dialogWindow.setAttributes(lp);
+ }
+ List reasonList = new ArrayList<>();
+ reasonList.add("有害/不安全");
+ reasonList.add("涉嫌广告");
+ reasonList.add("虚假信息");
+ reasonList.add("不准确");
+ reasonList.add("毫无作用");
+ reasonList.add("其他原因");
+ for(String content : reasonList){
+ ReportEntity entity = new ReportEntity(content,false);
+ reportList.add(entity);
+ }
+ imgClose = layoutView.findViewById(R.id.img_close);
+ rvDatas = layoutView.findViewById(R.id.rv_reasons);
+ tvCommit = layoutView.findViewById(R.id.tv_commit);
+ imgClose.setOnClickListener(onclick);
+ tvCommit.setOnClickListener(onclick);
+ ReasonAdapter adapter = new ReasonAdapter(R.layout.order_cancel_item_layout,reportList,mReason);
+ rvDatas.setAdapter(adapter);
+ adapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
+ @Override
+ public void onItemClick(BaseQuickAdapter ada, View view, int position) {
+ if(mListener != null){
+ adapter.setSelectPos(position);
+ }
+ }
+ });
+ }
+
+ View.OnClickListener onclick = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()){
+ case R.id.img_close:
+ mDialog.dismiss();
+ break;
+
+ case R.id.tv_commit:
+ boolean hasSel = false;
+ ReportEntity selEntity = null;
+ for(ReportEntity entity : reportList){
+ if(entity.isCheck()){
+ hasSel = true;
+ selEntity = entity;
+ }
+ }
+ if(!hasSel){
+ ToastUtil.showShort(mContext,"请选择举报/反馈理由");
+ return;
+ }
+ if(mListener != null){
+ mListener.callBackDatas(selEntity.getReportReason());
+ dismiss();
+ }
+ break;
+ }
+ }
+ };
+
+
+ public void show() {
+ if(mDialog != null){
+ mDialog.show();
+ }
+ }
+
+ public void dismiss() {
+ if(mDialog != null){
+ mDialog.dismiss();
+ }
+ }
+
+ class ReasonAdapter extends BaseQuickAdapter{
+ private String mReason = "";
+ private int mPos = -1;
+ public ReasonAdapter(int layoutResId, @Nullable List data,String reason) {
+ super(layoutResId, data);
+ mReason = reason;
+ }
+
+ public void setSelectPos(int position){
+ mPos = position;
+ List list = getData();
+ for (int i = 0; i < list.size(); i++) {
+ list.get(i).setCheck(i == position);
+ }
+ notifyDataSetChanged();
+ };
+
+ @Override
+ protected void convert(BaseViewHolder helper, ReportEntity item) {
+ RadioButton radioButton = helper.getView(R.id.radio6);
+ helper.setText(R.id.tv_reason, item.getReportReason());
+
+ int adapterPosition = helper.getAdapterPosition();
+ radioButton.setChecked(mPos == adapterPosition);
+
+ // 添加点击事件
+ helper.itemView.setOnClickListener(v -> {
+ if (mPos != adapterPosition) { // 不允许取消选中,只能换选项
+ setSelectPos(adapterPosition);
+ }
+ });
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ckkj/water/dialog/DialogAppUpdate.java b/app/src/main/java/com/ckkj/water/dialog/DialogAppUpdate.java
new file mode 100644
index 0000000..87bb700
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/dialog/DialogAppUpdate.java
@@ -0,0 +1,227 @@
+package com.ckkj.water.dialog;
+
+import android.Manifest;
+import android.app.Activity;
+import android.app.Dialog;
+import android.app.DownloadManager;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Environment;
+import android.text.Html;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import androidx.core.app.NotificationCompat;
+import androidx.core.content.ContextCompat;
+
+import com.chiclaim.android.downloader.DownloadRequest;
+import com.ckkj.water.AppConfig;
+import com.ckkj.water.R;
+import com.ckkj.water.main.entity.CheckVersionEntity;
+import com.ckkj.water.utils.DownLoadApk;
+import com.ckkj.water.utils.FileNameHelper;
+import com.ckkj.water.utils.show.ToastUtil;
+
+import java.io.File;
+
+
+/**
+ * @author messi.mo
+ * @date 2017/11/22
+ * 底部选择框
+ */
+
+public class DialogAppUpdate {
+
+ public static View layoutView;
+ private Dialog mDialog;
+ private Activity mContext;
+ private TextView tvTitle,tvContent,tvConfirm,tvCancel;
+ private RelativeLayout rlUpdateContent;
+ private ImageView imgClose,imgClear;
+ private String startTime = "";
+ private String endTime = "";
+ private DialogListener mListener;
+ private Animation animUp;
+ private Animation animDown;
+ private ProgressBar progressBar;
+ private String mDwonLoadUrl = "";
+// private String mDwonLoadUrl = "https://jsjk-private.oss-cn-hangzhou.aliyuncs.com/invoice/2024-11-26/3a9c5e76b4bb4ae297a219a663e9e82a.png";
+ private DownloadManager downloadManager;
+ private long downloadId;
+ private NotificationManager notificationManager;
+ private NotificationCompat.Builder notificationBuilder;
+ private final String CHANNEL_ID = "download_channel"; // 通知渠道ID
+ private boolean mIsForceUpdate;
+ private CheckVersionEntity mVersionBean;
+
+
+
+ public void setOnDialogListener(DialogListener listener){
+ this.mListener = listener;
+ }
+
+
+ public interface DialogListener{
+ void callBackGetPermisson(String permission);
+ void downLoadFinish();
+ }
+
+
+ public DialogAppUpdate(Activity context, int layoutId, boolean isForceUpdate, CheckVersionEntity bean) {
+ this.mContext = context;
+ this.mIsForceUpdate = isForceUpdate;
+ this.mVersionBean = bean;
+ mDwonLoadUrl = bean.getVersionUrl();
+ mDialog = new Dialog(context, R.style.ActionSheetDialogStyle);
+ //填充对话框的布局
+ layoutView = LayoutInflater.from(context).inflate(layoutId, null);
+ //将布局设置给Dialog
+ mDialog.setContentView(layoutView);
+ // 为弹出和关闭设置动画
+ animUp = AnimationUtils.loadAnimation(mContext, R.anim.popup_anim_up);
+ animDown = AnimationUtils.loadAnimation(mContext, R.anim.popup_anim_down);
+ try {
+ //设置滑出动画
+// layoutView.startAnimation(animUp);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ //获取当前Activity所在的窗体
+ Window dialogWindow = mDialog.getWindow();
+ mDialog.setOnKeyListener((dialog1, keyCode, event) -> {
+ if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) {
+ // 如果需要拦截返回键,返回 true
+ return true;
+ }
+ return false; // 不拦截其他按键
+ });
+
+ mDialog.setCanceledOnTouchOutside(false);
+ //设置Dialog从窗体底部弹出
+ if (dialogWindow != null) {
+ dialogWindow.setGravity(Gravity.CENTER_VERTICAL);
+ //获得窗体的属性
+ WindowManager.LayoutParams lp = dialogWindow.getAttributes();
+ lp.width = context.getWindowManager().getDefaultDisplay().getWidth();
+ //设置Dialog距离底部的距离
+ //lp.y = 30;
+ // 将属性设置给窗体
+ dialogWindow.setAttributes(lp);
+ }
+ tvTitle = layoutView.findViewById(R.id.tv_title);
+ tvContent = layoutView.findViewById(R.id.tv_update_content);
+ tvConfirm = layoutView.findViewById(R.id.tv_update);
+ tvCancel = layoutView.findViewById(R.id.tv_cencel);
+ progressBar = layoutView.findViewById(R.id.progress_bar);
+ rlUpdateContent = layoutView.findViewById(R.id.rel_file_content);
+ tvConfirm.setOnClickListener(onclick);
+ tvCancel.setOnClickListener(onclick);
+ if(isForceUpdate){
+ tvCancel.setVisibility(View.GONE);
+ }
+ // 初始化 DownloadManager
+ downloadManager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
+ notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ tvContent.setText(Html.fromHtml(bean.getVersionDescription(), Html.FROM_HTML_MODE_LEGACY));
+ } else {
+ }
+ String [] desStr = bean.getVersionDescription().split("#");
+ String desLine = "";
+ for(String str : desStr){
+ desLine = desLine + str + "\n";
+ }
+ tvContent.setText(desLine);
+ tvContent.setLineSpacing(0, 1.2f);
+ }
+
+ private DownloadRequest request;
+ View.OnClickListener onclick = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()){
+ case R.id.tv_update:
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ != PackageManager.PERMISSION_GRANTED) {
+ if(mListener != null){
+ mListener.callBackGetPermisson(Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ }
+ return;
+ }
+ }
+ startUpdateApp();
+ break;
+ case R.id.tv_cencel:
+ dismiss();
+ AppConfig.USER_CONFIRM_UPDATE_APP = true;
+ break;
+ }
+ }
+ };
+
+ public void startUpdateApp() {
+ //强制更新,不需要隐藏dialog
+ if(mIsForceUpdate){
+ progressBar.setVisibility(View.VISIBLE);
+ rlUpdateContent.setVisibility(View.GONE);
+ progressBar.setMax(100);
+ }else{
+ ToastUtil.showShort(mContext,"可在任务栏查看下载进度");
+ dismiss();
+ }
+ String fileName = FileNameHelper.getFileNameFromUrl(mDwonLoadUrl);
+ File downloadDir = Environment.getExternalStorageDirectory();
+ File apkFile = new File(downloadDir, fileName);
+ String apkPath = apkFile.getAbsolutePath();
+ DownLoadApk downloadTask = new DownLoadApk(mContext,mIsForceUpdate);
+ downloadTask.startDownload(mDwonLoadUrl, fileName,apkPath);
+ downloadTask.setDownLoadCallback(new DownLoadApk.DownLoadCallback() {
+ @Override
+ public void updateProgress(long currentProgress, long total) {
+ int progress = (int) ((currentProgress * 100) / total);
+ progressBar.setProgress(progress);
+ mContext.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ tvTitle.setText("正在下载 " + progress + "%");
+ }
+ });
+ }
+
+ @Override
+ public void downLoadFinish() {
+ if(mListener != null){
+ mListener.downLoadFinish();
+ }
+ }
+ });
+ }
+
+ public void show() {
+ if(mDialog != null){
+ mDialog.show();
+ }
+ }
+
+ public void dismiss() {
+ if(mDialog != null){
+ mDialog.dismiss();
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ckkj/water/dialog/LoadingDialog.java b/app/src/main/java/com/ckkj/water/dialog/LoadingDialog.java
new file mode 100644
index 0000000..c8c6c7d
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/dialog/LoadingDialog.java
@@ -0,0 +1,115 @@
+package com.ckkj.water.dialog;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.WindowManager;
+import android.view.animation.LinearInterpolator;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+
+import com.ckkj.water.R;
+
+import java.util.Objects;
+
+
+/**
+ * author:
+ * time: 2019/9/18 9:34
+ * desc:
+ * version: 1.0.0
+ */
+public class LoadingDialog extends Dialog {
+ private String string="";
+ private TextView textView;
+ private Context context;
+ private String [] strLoadContent = {"加载中.","加载中..","加载中..."};
+ private int mIndex = 0;
+ private TextView tvLoading;
+ private Handler handler = new Handler();
+
+ // 创建一个Runnable对象,用于定时执行任务
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ // 在这里编写需要定时执行的操作
+ // 比如更新UI、发送网络请求等
+ if(tvLoading != null){
+ tvLoading.setText(strLoadContent[mIndex]);
+ mIndex ++;
+ if(mIndex == strLoadContent.length){
+ mIndex = 0;
+ }
+ }
+ handler.postDelayed(this, 300);
+ }
+ };
+
+ public LoadingDialog(Context context) {
+ super(context); // 应用透明背景样式
+ this.context=context;
+ }
+ public LoadingDialog(Context context, String s) {
+ super(context);
+ this.string=s;
+ this.context=context;
+ }
+
+ public LoadingDialog(Context context, int theme) {
+ super(context, theme);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ init();
+ }
+
+ @SuppressLint("ResourceAsColor")
+ private void init() {
+ // 设置对话框背景为透明
+ getWindow().setBackgroundDrawable(context.getResources().getDrawable(R.color.translucent_black_95));
+ getWindow().setDimAmount(0.2f);
+ setCancelable(true);
+ setCanceledOnTouchOutside(false);
+ setContentView(R.layout.dialog_loading_my);//loading的xml文件
+ tvLoading = findViewById(R.id.tv_load_dialog);
+ ImageView imgLoading = findViewById(R.id.img_loading);
+ // 初始化 ObjectAnimator
+ ObjectAnimator rotateAnimator = ObjectAnimator.ofFloat(imgLoading, "rotation", 0f, 360f);
+ rotateAnimator.setDuration(1500);
+ rotateAnimator.setRepeatCount(ValueAnimator.INFINITE);
+ rotateAnimator.setInterpolator(new LinearInterpolator()); // 匀速旋转
+ rotateAnimator.start();
+ //加载动画资源
+// Drawable animation = (AnimationDrawable) imgLoading.getDrawable();
+// ((AnimationDrawable) animation).start();
+ WindowManager.LayoutParams params = Objects.requireNonNull(getWindow()).getAttributes();
+ params.width = WindowManager.LayoutParams.MATCH_PARENT;
+ params.height = WindowManager.LayoutParams.MATCH_PARENT;
+ getWindow().setAttributes(params);
+ // 可选: 设置窗口大小
+ getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT);
+ }
+
+ @Override
+ public void show() {//开启
+// if (context==null){
+// return;
+// }
+ super.show();
+ }
+
+ @Override
+ public void dismiss() {//关闭
+// if (context==null){
+// return;
+// }
+ super.dismiss();
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/dialog/PayTypeDialog.java b/app/src/main/java/com/ckkj/water/dialog/PayTypeDialog.java
new file mode 100644
index 0000000..3ffc5f5
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/dialog/PayTypeDialog.java
@@ -0,0 +1,141 @@
+package com.ckkj.water.dialog;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.Context;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.ImageView;
+import android.widget.RadioButton;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import com.ckkj.water.R;
+
+
+/**
+ * @author messi.mo
+ * @date 2017/11/22
+ * 底部选择框
+ */
+
+public class PayTypeDialog {
+
+ public static View layoutView;
+ private Dialog mDialog;
+ private Context mContext;
+ private ImageView imgClose,imgClear;
+ private DialogListener mListener;
+ private Animation animUp;
+ private Animation animDown;
+ private RelativeLayout rlAliPay,rlWxPay;
+ private TextView tvPay;
+ private RadioButton mRdBtnAlipay,mRdBtnWxpay;
+ private int mPayCode = 100;//100支付宝 200微信
+
+
+ public void setOnDialogListener(DialogListener listener){
+ this.mListener = listener;
+ }
+
+ public interface DialogListener{
+ void goToPay(int payCode);
+ }
+
+
+ public PayTypeDialog(Activity context, String payInfo, String payPriceInfo) {
+ this.mContext = context;
+ mDialog = new Dialog(context, R.style.ActionSheetDialogStyle);
+ //填充对话框的布局
+ layoutView = LayoutInflater.from(context).inflate(R.layout.dialog_pay_option_layout, null);
+ //将布局设置给Dialog
+ mDialog.setContentView(layoutView);
+ // 为弹出和关闭设置动画
+ animUp = AnimationUtils.loadAnimation(mContext, R.anim.popup_anim_up);
+ animDown = AnimationUtils.loadAnimation(mContext, R.anim.popup_anim_down);
+ try {
+ //设置滑出动画
+ layoutView.startAnimation(animUp);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ //获取当前Activity所在的窗体
+ Window dialogWindow = mDialog.getWindow();
+ mDialog.setCanceledOnTouchOutside(true);
+ //设置Dialog从窗体底部弹出
+ if (dialogWindow != null) {
+ dialogWindow.setGravity(Gravity.BOTTOM);
+ //获得窗体的属性
+ WindowManager.LayoutParams lp = dialogWindow.getAttributes();
+ lp.width = context.getWindowManager().getDefaultDisplay().getWidth();
+ //设置Dialog距离底部的距离
+ //lp.y = 30;
+ // 将属性设置给窗体
+ dialogWindow.setAttributes(lp);
+ }
+ imgClose = layoutView.findViewById(R.id.img_close);
+ rlAliPay = layoutView.findViewById(R.id.rl_zfb);
+ rlWxPay = layoutView.findViewById(R.id.rl_wx);
+ tvPay = layoutView.findViewById(R.id.tv_go_pay);
+ mRdBtnAlipay = layoutView.findViewById(R.id.radio_zdb);
+ mRdBtnWxpay = layoutView.findViewById(R.id.radio_wx);
+ TextView tvPayInfo = layoutView.findViewById(R.id.tv_pay_info);
+ TextView tvPayPriceInfo = layoutView.findViewById(R.id.tv_pay_price);
+ imgClose.setOnClickListener(onclick);
+ tvPay.setOnClickListener(onclick);
+ rlAliPay.setOnClickListener(onclick);
+ rlWxPay.setOnClickListener(onclick);
+ tvPayInfo.setText("支付账单信息:" + payInfo);
+ tvPayPriceInfo.setText("实付金额: ¥" + payPriceInfo);
+ }
+
+ View.OnClickListener onclick = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()){
+ case R.id.img_close:
+ mDialog.dismiss();
+ break;
+
+ case R.id.rl_zfb:
+ mPayCode = 100;
+ mRdBtnAlipay.setChecked(true);
+ mRdBtnWxpay.setChecked(false);
+ break;
+
+ case R.id.rl_wx:
+ mPayCode = 200;
+ mRdBtnAlipay.setChecked(false);
+ mRdBtnWxpay.setChecked(true);
+ break;
+
+ case R.id.tv_go_pay:
+ if(mListener != null){
+ mListener.goToPay(mPayCode);
+ dismiss();
+ }
+ break;
+ }
+ }
+ };
+
+
+ public void show() {
+ if(mDialog != null){
+ mDialog.show();
+ }
+ }
+
+ public void dismiss() {
+ if(mDialog != null){
+ mDialog.dismiss();
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ckkj/water/dialog/ShareQrcodeDialog.java b/app/src/main/java/com/ckkj/water/dialog/ShareQrcodeDialog.java
new file mode 100644
index 0000000..bb92662
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/dialog/ShareQrcodeDialog.java
@@ -0,0 +1,197 @@
+package com.ckkj.water.dialog;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import com.google.zxing.client.android.utils.ZXingUtils;
+import com.ckkj.water.AppConfig;
+import com.ckkj.water.R;
+import com.ckkj.water.utils.show.ToastUtil;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * @author messi.mo
+ * @date 2017/11/22
+ * 底部选择框
+ */
+
+public class ShareQrcodeDialog {
+
+ public static View layoutView;
+ private Dialog mDialog;
+ private Context mContext;
+ private TextView tvOneMonth,tvThreeMonth,tvSixMonth,tvStartTime,tvEndTime,tvClear,tvConfirm;
+ private ImageView imgDown,imgCopy,imgHb,imgQrcode;
+ private String startTime = "";
+ private RelativeLayout rlHaibao;
+ private String endTime = "";
+ private DialogListener mListener;
+ private Animation animUp;
+ private Animation animDown;
+ private List mImgUrls = new ArrayList<>();
+ private String mInviteeCode = "";
+
+
+
+ public void setOnDialogListener(DialogListener listener){
+ this.mListener = listener;
+ }
+
+ public interface DialogListener{
+ void downShape();
+ void copyLink(String url);
+ }
+
+
+ public ShareQrcodeDialog(Activity context, int layoutId,String shareCode) {
+ this.mContext = context;
+ this.mInviteeCode = shareCode;
+ mDialog = new Dialog(context, R.style.ActionShareDialogStyle);
+ //填充对话框的布局
+ layoutView = LayoutInflater.from(context).inflate(layoutId, null);
+ //将布局设置给Dialog
+ mDialog.setContentView(layoutView);
+ // 为弹出和关闭设置动画
+ animUp = AnimationUtils.loadAnimation(mContext, R.anim.popup_anim_up);
+ animDown = AnimationUtils.loadAnimation(mContext, R.anim.popup_anim_down);
+ try {
+ //设置滑出动画
+ layoutView.startAnimation(animUp);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ //获取当前Activity所在的窗体
+ Window dialogWindow = mDialog.getWindow();
+ mDialog.setCanceledOnTouchOutside(true);
+ //设置Dialog在屏幕中间显示
+ if (dialogWindow != null) {
+ dialogWindow.setGravity(Gravity.CENTER);
+ //获得窗体的属性
+ WindowManager.LayoutParams lp = dialogWindow.getAttributes();
+ // 设置Dialog宽度为屏幕宽度的85%(可根据需要调整)
+ int screenWidth = context.getWindowManager().getDefaultDisplay().getWidth();
+ lp.width = (int) (screenWidth * 0.85);
+ int screenHeight = context.getWindowManager().getDefaultDisplay().getHeight();
+ lp.height = (int) (screenHeight * 0.85);
+ //将属性设置给窗体
+ dialogWindow.setAttributes(lp);
+ }
+ //添加图片集合
+ mImgUrls.add(mContext.getResources().getDrawable(R.mipmap.img_share_bg));
+ imgDown = layoutView.findViewById(R.id.img_down);
+ imgCopy = layoutView.findViewById(R.id.img_copy);
+ imgHb = layoutView.findViewById(R.id.img_haibao);
+ imgQrcode = layoutView.findViewById(R.id.img_qr_code);
+ rlHaibao = layoutView.findViewById(R.id.relative_hb);
+ imgDown.setOnClickListener(onclick);
+ imgCopy.setOnClickListener(onclick);
+ authUrlToQrcode(shareCode);
+
+ }
+
+ View.OnClickListener onclick = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()){
+ case R.id.img_down:
+ saveLayoutAsImage();
+ break;
+
+ case R.id.img_copy:
+ ClipboardManager clipboard = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
+ ClipData clip = ClipData.newPlainText("label", AppConfig.USER_SHARE_URL + mInviteeCode);
+ clipboard.setPrimaryClip(clip);
+ ToastUtil.showShort(mContext,"已复制邀请链接");
+ mDialog.dismiss();
+ break;
+ }
+ }
+ };
+
+ /**
+ * 保存名片的 bitmap 到手机相册中
+ */
+ private void saveLayoutAsImage() {
+ // 1. 创建 Bitmap
+ Bitmap bitmap = Bitmap.createBitmap(rlHaibao.getWidth(), rlHaibao.getHeight(), Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ rlHaibao.draw(canvas);
+
+ // 2. 保存到 MediaStore(Android 10+ 适用)
+ String filename = "layout_" + System.currentTimeMillis() + ".png";
+ ContentValues values = new ContentValues();
+ values.put(MediaStore.Images.Media.DISPLAY_NAME, filename);
+ values.put(MediaStore.Images.Media.MIME_TYPE, "image/png");
+ values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/MyLayouts");
+
+ ContentResolver resolver = mContext.getContentResolver();
+ Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+
+ if (uri != null) {
+ try (OutputStream out = resolver.openOutputStream(uri)) {
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
+ ToastUtil.showShort(mContext,"保存成功");
+ mDialog.dismiss();
+ } catch (IOException e) {
+ e.printStackTrace();
+ ToastUtil.showShort(mContext,"保存失败:" + e.getMessage());
+ mDialog.dismiss();
+ }
+ }
+ }
+
+ /**
+ * url转为二维码显示
+ * @param data
+ */
+ private void authUrlToQrcode(String data) {
+ try {
+ Bitmap qrImage = ZXingUtils.createQRCodeImage(AppConfig.USER_SHARE_URL + data);
+ imgQrcode.setImageBitmap(qrImage);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+
+ public void show() {
+ if(mDialog != null){
+ mDialog.show();
+ }
+ }
+
+ public void dismiss() {
+ if(mDialog != null){
+ mDialog.dismiss();
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ckkj/water/fate/adapter/FateHistoryAdapter.java b/app/src/main/java/com/ckkj/water/fate/adapter/FateHistoryAdapter.java
new file mode 100644
index 0000000..9d6e086
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/fate/adapter/FateHistoryAdapter.java
@@ -0,0 +1,52 @@
+package com.ckkj.water.fate.adapter;
+
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+
+import com.chad.library.adapter.base.BaseQuickAdapter;
+import com.chad.library.adapter.base.BaseViewHolder;
+import com.ckkj.water.R;
+import com.ckkj.water.fate.entity.FateHistoryEntity;
+import com.ckkj.water.utils.color.FateDataUtil;
+
+import java.util.List;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/24
+ * @description TODO: 描述该类的作用
+ */
+
+public class FateHistoryAdapter extends BaseQuickAdapter {
+ private Context mContext;
+ public FateHistoryAdapter(Context context,int layoutResId, @Nullable List data) {
+ super(layoutResId, data);
+ this.mContext = context;
+ }
+
+ @Override
+ protected void convert(BaseViewHolder holder, FateHistoryEntity.HistoryList item) {
+ holder.setText(R.id.tv_date,item.getRecordDate());
+ holder.setText(R.id.tv_week,item.getDayOfWeek());
+ if(item.isToday()){
+ holder.setVisible(R.id.tv_today,true);
+ }else{
+ holder.setVisible(R.id.tv_today,false);
+ }
+ holder.setText(R.id.tv_name,item.getName());
+ holder.setText(R.id.tv_location,item.getCurrentPlace());
+ holder.setText(R.id.tv_weather,item.getWeatherCondition());
+ holder.setText(R.id.tv_star,item.getConstellation());
+ holder.setText(R.id.tv_score,item.getOverallScore() + "");
+ holder.setText(R.id.tv_syy_score,item.getCareerScore() + "");
+ holder.setText(R.id.tv_gqy_score,item.getLoveScore() + "");
+ holder.setText(R.id.tv_jky_score,item.getHealthScore() + "");
+ holder.setText(R.id.tv_cfy_score,item.getWealthScore() + "");
+ holder.setTextColor(R.id.tv_score, FateDataUtil.getScoreColor(item.getOverallScore()));
+ holder.setTextColor(R.id.tv_syy_score, FateDataUtil.getScoreColor(item.getCareerScore()));
+ holder.setTextColor(R.id.tv_gqy_score, FateDataUtil.getScoreColor(item.getLoveScore()));
+ holder.setTextColor(R.id.tv_jky_score, FateDataUtil.getScoreColor(item.getHealthScore()));
+ holder.setTextColor(R.id.tv_cfy_score, FateDataUtil.getScoreColor(item.getWealthScore()));
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/fate/adapter/SelectCityAdapter.java b/app/src/main/java/com/ckkj/water/fate/adapter/SelectCityAdapter.java
new file mode 100644
index 0000000..0b94d40
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/fate/adapter/SelectCityAdapter.java
@@ -0,0 +1,50 @@
+package com.ckkj.water.fate.adapter;
+
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+
+import com.chad.library.adapter.base.BaseQuickAdapter;
+import com.chad.library.adapter.base.BaseViewHolder;
+import com.ckkj.water.R;
+import com.ckkj.water.fate.entity.CityDataEntity;
+
+import java.util.List;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/18
+ * @description TODO: 描述该类的作用
+ */
+
+public class SelectCityAdapter extends BaseQuickAdapter {
+ private Context mContext;
+ private int mSelPos = -1;
+ public SelectCityAdapter(Context context,int layoutResId, @Nullable List data) {
+ super(layoutResId, data);
+ this.mContext = context;
+ }
+
+ @Override
+ protected void convert(BaseViewHolder helper, CityDataEntity item) {
+ helper.setText(R.id.tv_city_name,item.getCityName());
+ if(mSelPos == helper.getLayoutPosition()){
+ helper.setVisible(R.id.img_select,true);
+ }else{
+ helper.setVisible(R.id.img_select,false);
+ }
+ }
+
+ public void setSelPos(int pos){
+ this.mSelPos = pos;
+ notifyDataSetChanged();
+ }
+
+ public int getmSelPos() {
+ return mSelPos;
+ }
+
+ public void setmSelPos(int mSelPos) {
+ this.mSelPos = mSelPos;
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/fate/entity/CityDataEntity.java b/app/src/main/java/com/ckkj/water/fate/entity/CityDataEntity.java
new file mode 100644
index 0000000..cb32503
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/fate/entity/CityDataEntity.java
@@ -0,0 +1,203 @@
+package com.ckkj.water.fate.entity;
+
+import java.io.Serializable;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/18
+ * @description TODO: 描述该类的作用
+ */
+
+public class CityDataEntity implements Serializable {
+ private int id;
+ private String cityCode;
+ private String cityName;
+ private String cityNameEn;
+ private String provinceCode;
+ private String provinceName;
+ private int cityLevel;
+ private String cityType;
+ private double longitude;
+ private double latitude;
+ private String population;
+ private String area;
+ private String gdp;
+ private String zipCode;
+ private String phoneCode;
+ private int isCapital;
+ private int isActive;
+ private int sortOrder;
+ private String remark;
+ private String createdTime;
+ private String updatedTime;
+ private String capitalCity;
+
+ public String getCapitalCity() {
+ return capitalCity;
+ }
+
+ public void setCapitalCity(String capitalCity) {
+ this.capitalCity = capitalCity;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public String getCityCode() {
+ return cityCode;
+ }
+
+ public void setCityCode(String cityCode) {
+ this.cityCode = cityCode;
+ }
+
+ public String getCityName() {
+ return cityName;
+ }
+
+ public void setCityName(String cityName) {
+ this.cityName = cityName;
+ }
+
+ public String getCityNameEn() {
+ return cityNameEn;
+ }
+
+ public void setCityNameEn(String cityNameEn) {
+ this.cityNameEn = cityNameEn;
+ }
+
+ public String getProvinceCode() {
+ return provinceCode;
+ }
+
+ public void setProvinceCode(String provinceCode) {
+ this.provinceCode = provinceCode;
+ }
+
+ public String getProvinceName() {
+ return provinceName;
+ }
+
+ public void setProvinceName(String provinceName) {
+ this.provinceName = provinceName;
+ }
+
+ public int getCityLevel() {
+ return cityLevel;
+ }
+
+ public void setCityLevel(int cityLevel) {
+ this.cityLevel = cityLevel;
+ }
+
+ public String getCityType() {
+ return cityType;
+ }
+
+ public void setCityType(String cityType) {
+ this.cityType = cityType;
+ }
+
+ public double getLongitude() {
+ return longitude;
+ }
+
+ public void setLongitude(double longitude) {
+ this.longitude = longitude;
+ }
+
+ public double getLatitude() {
+ return latitude;
+ }
+
+ public void setLatitude(double latitude) {
+ this.latitude = latitude;
+ }
+
+ public String getPopulation() {
+ return population;
+ }
+
+ public void setPopulation(String population) {
+ this.population = population;
+ }
+
+ public String getArea() {
+ return area;
+ }
+
+ public void setArea(String area) {
+ this.area = area;
+ }
+
+ public String getGdp() {
+ return gdp;
+ }
+
+ public void setGdp(String gdp) {
+ this.gdp = gdp;
+ }
+
+ public String getZipCode() {
+ return zipCode;
+ }
+
+ public void setZipCode(String zipCode) {
+ this.zipCode = zipCode;
+ }
+
+ public String getPhoneCode() {
+ return phoneCode;
+ }
+
+ public void setPhoneCode(String phoneCode) {
+ this.phoneCode = phoneCode;
+ }
+
+ public int getIsCapital() {
+ return isCapital;
+ }
+
+ public void setIsCapital(int isCapital) {
+ this.isCapital = isCapital;
+ }
+
+ public int getIsActive() {
+ return isActive;
+ }
+
+ public void setIsActive(int isActive) {
+ this.isActive = isActive;
+ }
+
+ public int getSortOrder() {
+ return sortOrder;
+ }
+
+ public void setSortOrder(int sortOrder) {
+ this.sortOrder = sortOrder;
+ }
+
+ public String getRemark() {
+ return remark;
+ }
+
+ public void setRemark(String remark) {
+ this.remark = remark;
+ }
+
+ public void setCreatedTime(String createdTime) {
+ this.createdTime = createdTime;
+ }
+
+ public void setUpdatedTime(String updatedTime) {
+ this.updatedTime = updatedTime;
+ }
+
+}
diff --git a/app/src/main/java/com/ckkj/water/fate/entity/FateHistoryEntity.java b/app/src/main/java/com/ckkj/water/fate/entity/FateHistoryEntity.java
new file mode 100644
index 0000000..e2e55d3
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/fate/entity/FateHistoryEntity.java
@@ -0,0 +1,139 @@
+package com.ckkj.water.fate.entity;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/25
+ * @description TODO: 描述该类的作用
+ */
+
+public class FateHistoryEntity {
+ private List list;
+ private int total;
+ public void setList(List list) {
+ this.list = list;
+ }
+ public List getList() {
+ return list;
+ }
+
+ public void setTotal(int total) {
+ this.total = total;
+ }
+ public int getTotal() {
+ return total;
+ }
+
+ public static class HistoryList implements Serializable{
+ private String recordDate;
+ private String dayOfWeek;
+ private String name;
+ private String currentPlace;
+ private String weatherCondition;
+ private String constellation;
+ private int overallScore;
+ private int careerScore;
+ private int loveScore;
+ private int healthScore;
+ private int wealthScore;
+ private boolean isToday;
+
+ public String getRecordDate() {
+ return recordDate;
+ }
+
+ public void setRecordDate(String recordDate) {
+ this.recordDate = recordDate;
+ }
+
+ public String getDayOfWeek() {
+ return dayOfWeek;
+ }
+
+ public void setDayOfWeek(String dayOfWeek) {
+ this.dayOfWeek = dayOfWeek;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getCurrentPlace() {
+ return currentPlace;
+ }
+
+ public void setCurrentPlace(String currentPlace) {
+ this.currentPlace = currentPlace;
+ }
+
+ public String getWeatherCondition() {
+ return weatherCondition;
+ }
+
+ public void setWeatherCondition(String weatherCondition) {
+ this.weatherCondition = weatherCondition;
+ }
+
+ public String getConstellation() {
+ return constellation;
+ }
+
+ public void setConstellation(String constellation) {
+ this.constellation = constellation;
+ }
+
+ public int getOverallScore() {
+ return overallScore;
+ }
+
+ public void setOverallScore(int overallScore) {
+ this.overallScore = overallScore;
+ }
+
+ public int getCareerScore() {
+ return careerScore;
+ }
+
+ public void setCareerScore(int careerScore) {
+ this.careerScore = careerScore;
+ }
+
+ public int getLoveScore() {
+ return loveScore;
+ }
+
+ public void setLoveScore(int loveScore) {
+ this.loveScore = loveScore;
+ }
+
+ public int getHealthScore() {
+ return healthScore;
+ }
+
+ public void setHealthScore(int healthScore) {
+ this.healthScore = healthScore;
+ }
+
+ public int getWealthScore() {
+ return wealthScore;
+ }
+
+ public void setWealthScore(int wealthScore) {
+ this.wealthScore = wealthScore;
+ }
+
+ public boolean isToday() {
+ return isToday;
+ }
+
+ public void setToday(boolean today) {
+ isToday = today;
+ }
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/fate/entity/LunarYearEntity.java b/app/src/main/java/com/ckkj/water/fate/entity/LunarYearEntity.java
new file mode 100644
index 0000000..479cc73
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/fate/entity/LunarYearEntity.java
@@ -0,0 +1,66 @@
+package com.ckkj.water.fate.entity;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/16
+ * @description TODO: 描述该类的作用
+ */
+
+public class LunarYearEntity implements Serializable {
+ public String year; // 年份
+ public List month; // 月份列表
+ // Gson需要无参构造
+ public LunarYearEntity() {}
+
+ public String getYear() {
+ return year;
+ }
+
+ public void setYear(String year) {
+ this.year = year;
+ }
+
+ public List getMonth() {
+ return month;
+ }
+
+ public void setMonth(List month) {
+ this.month = month;
+ }
+
+ public static class LunarMonth implements Serializable{
+ public String name; // 月份数字(字符串形式)
+ public int isRun; // 是否闰月(0=否,1=是)
+ public int isAllDay; // 是否包含三十(0=29天,1=30天)
+
+ // Gson需要无参构造
+ public LunarMonth() {}
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getIsRun() {
+ return isRun;
+ }
+
+ public void setIsRun(int isRun) {
+ this.isRun = isRun;
+ }
+
+ public int getIsAllDay() {
+ return isAllDay;
+ }
+
+ public void setIsAllDay(int isAllDay) {
+ this.isAllDay = isAllDay;
+ }
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/fate/entity/TodayFateEntity.java b/app/src/main/java/com/ckkj/water/fate/entity/TodayFateEntity.java
new file mode 100644
index 0000000..39c3a3e
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/fate/entity/TodayFateEntity.java
@@ -0,0 +1,526 @@
+package com.ckkj.water.fate.entity;
+
+import com.ckkj.water.base.entity.UserInfoEntity;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/21
+ * @description TODO: 描述该类的作用
+ */
+
+public class TodayFateEntity implements Serializable {
+ private FortuneAnalysis fortuneAnalysis;
+ private UserInfoEntity userInfo;
+ private TodayInfo todayInfo;
+ private String weatherCondition;
+ private String weatherTemperature;
+ private int todayVisitCount;
+ private int totalVisitDays;
+ public void setFortuneAnalysis(FortuneAnalysis fortuneAnalysis) {
+ this.fortuneAnalysis = fortuneAnalysis;
+ }
+ public FortuneAnalysis getFortuneAnalysis() {
+ return fortuneAnalysis;
+ }
+
+ public void setUserInfo(UserInfoEntity userInfo) {
+ this.userInfo = userInfo;
+ }
+ public UserInfoEntity getUserInfo() {
+ return userInfo;
+ }
+
+ public void setTodayInfo(TodayInfo todayInfo) {
+ this.todayInfo = todayInfo;
+ }
+ public TodayInfo getTodayInfo() {
+ return todayInfo;
+ }
+
+ public void setWeatherCondition(String weatherCondition) {
+ this.weatherCondition = weatherCondition;
+ }
+ public String getWeatherCondition() {
+ return weatherCondition;
+ }
+
+ public void setWeatherTemperature(String weatherTemperature) {
+ this.weatherTemperature = weatherTemperature;
+ }
+ public String getWeatherTemperature() {
+ return weatherTemperature;
+ }
+
+ public void setTodayVisitCount(int todayVisitCount) {
+ this.todayVisitCount = todayVisitCount;
+ }
+ public int getTodayVisitCount() {
+ return todayVisitCount;
+ }
+
+ public void setTotalVisitDays(int totalVisitDays) {
+ this.totalVisitDays = totalVisitDays;
+ }
+ public int getTotalVisitDays() {
+ return totalVisitDays;
+ }
+
+ /**
+ * Copyright 2025 bejson.com
+ */
+ /**
+ * Auto-generated: 2025-07-21 17:23:17
+ *
+ * @author bejson.com (i@bejson.com)
+ * @website http://www.bejson.com/java2pojo/
+ */
+ public static class TodayInfo implements Serializable{
+
+ private Date date;
+ private String weekDay;
+ private int year;
+ private int month;
+ private int day;
+ public void setDate(Date date) {
+ this.date = date;
+ }
+ public Date getDate() {
+ return date;
+ }
+
+ public void setWeekDay(String weekDay) {
+ this.weekDay = weekDay;
+ }
+ public String getWeekDay() {
+ return weekDay;
+ }
+
+ public void setYear(int year) {
+ this.year = year;
+ }
+ public int getYear() {
+ return year;
+ }
+
+ public void setMonth(int month) {
+ this.month = month;
+ }
+ public int getMonth() {
+ return month;
+ }
+
+ public void setDay(int day) {
+ this.day = day;
+ }
+ public int getDay() {
+ return day;
+ }
+
+ }
+
+ /**
+ * Copyright 2025 bejson.com
+ */
+ /**
+ * Auto-generated: 2025-07-21 17:23:17
+ *
+ * @author bejson.com (i@bejson.com)
+ * @website http://www.bejson.com/java2pojo/
+ */
+ public static class FortuneAnalysis implements Serializable{
+
+ private int overallScore;
+ private String overallDescription;
+ private String destiny;
+ private String todayGanzhi;
+ private BaziAnalysis baziAnalysis;
+ private Weather weather;
+ private AstrologyInfluence astrologyInfluence;
+ private LuckyInfo luckyInfo;
+ private int careerScore;
+ private String careerDescription;
+ private int loveScore;
+ private String loveDescription;
+ private int healthScore;
+ private String healthDescription;
+ private int wealthScore;
+ private String wealthDescription;
+ private Suggestions suggestions;
+ private String fiveElementsAnalysis;
+ public void setOverallScore(int overallScore) {
+ this.overallScore = overallScore;
+ }
+ public int getOverallScore() {
+ return overallScore;
+ }
+
+ public void setOverallDescription(String overallDescription) {
+ this.overallDescription = overallDescription;
+ }
+ public String getOverallDescription() {
+ return overallDescription;
+ }
+
+ public void setDestiny(String destiny) {
+ this.destiny = destiny;
+ }
+ public String getDestiny() {
+ return destiny;
+ }
+
+ public void setTodayGanzhi(String todayGanzhi) {
+ this.todayGanzhi = todayGanzhi;
+ }
+ public String getTodayGanzhi() {
+ return todayGanzhi;
+ }
+
+ public void setBaziAnalysis(BaziAnalysis baziAnalysis) {
+ this.baziAnalysis = baziAnalysis;
+ }
+ public BaziAnalysis getBaziAnalysis() {
+ return baziAnalysis;
+ }
+
+ public void setWeather(Weather weather) {
+ this.weather = weather;
+ }
+ public Weather getWeather() {
+ return weather;
+ }
+
+ public void setAstrologyInfluence(AstrologyInfluence astrologyInfluence) {
+ this.astrologyInfluence = astrologyInfluence;
+ }
+ public AstrologyInfluence getAstrologyInfluence() {
+ return astrologyInfluence;
+ }
+
+ public void setLuckyInfo(LuckyInfo luckyInfo) {
+ this.luckyInfo = luckyInfo;
+ }
+ public LuckyInfo getLuckyInfo() {
+ return luckyInfo;
+ }
+
+ public void setCareerScore(int careerScore) {
+ this.careerScore = careerScore;
+ }
+ public int getCareerScore() {
+ return careerScore;
+ }
+
+ public void setCareerDescription(String careerDescription) {
+ this.careerDescription = careerDescription;
+ }
+ public String getCareerDescription() {
+ return careerDescription;
+ }
+
+ public void setLoveScore(int loveScore) {
+ this.loveScore = loveScore;
+ }
+ public int getLoveScore() {
+ return loveScore;
+ }
+
+ public void setLoveDescription(String loveDescription) {
+ this.loveDescription = loveDescription;
+ }
+ public String getLoveDescription() {
+ return loveDescription;
+ }
+
+ public void setHealthScore(int healthScore) {
+ this.healthScore = healthScore;
+ }
+ public int getHealthScore() {
+ return healthScore;
+ }
+
+ public void setHealthDescription(String healthDescription) {
+ this.healthDescription = healthDescription;
+ }
+ public String getHealthDescription() {
+ return healthDescription;
+ }
+
+ public void setWealthScore(int wealthScore) {
+ this.wealthScore = wealthScore;
+ }
+ public int getWealthScore() {
+ return wealthScore;
+ }
+
+ public void setWealthDescription(String wealthDescription) {
+ this.wealthDescription = wealthDescription;
+ }
+ public String getWealthDescription() {
+ return wealthDescription;
+ }
+
+ public void setSuggestions(Suggestions suggestions) {
+ this.suggestions = suggestions;
+ }
+ public Suggestions getSuggestions() {
+ return suggestions;
+ }
+
+ public void setFiveElementsAnalysis(String fiveElementsAnalysis) {
+ this.fiveElementsAnalysis = fiveElementsAnalysis;
+ }
+ public String getFiveElementsAnalysis() {
+ return fiveElementsAnalysis;
+ }
+
+ }
+
+
+ /**
+ * Copyright 2025 bejson.com
+ */
+ /**
+ * Auto-generated: 2025-07-21 17:23:17
+ *
+ * @author bejson.com (i@bejson.com)
+ * @website http://www.bejson.com/java2pojo/
+ */
+ public static class Suggestions implements Serializable{
+
+ private String positive;
+ private String negative;
+ public void setPositive(String positive) {
+ this.positive = positive;
+ }
+ public String getPositive() {
+ return positive;
+ }
+
+ public void setNegative(String negative) {
+ this.negative = negative;
+ }
+ public String getNegative() {
+ return negative;
+ }
+
+ }
+
+
+ /**
+ * Copyright 2025 bejson.com
+ */
+ /**
+ * Auto-generated: 2025-07-21 17:23:17
+ *
+ * @author bejson.com (i@bejson.com)
+ * @website http://www.bejson.com/java2pojo/
+ */
+ public static class LuckyInfo implements Serializable{
+
+ private String[] numbers;
+ private String color;
+ private String direction;
+ private List luckyTimes;
+ private String flower;
+ private String food;
+
+ public String[] getNumbers() {
+ return numbers;
+ }
+
+ public void setNumbers(String[] numbers) {
+ this.numbers = numbers;
+ }
+
+ public void setColor(String color) {
+ this.color = color;
+ }
+ public String getColor() {
+ return color;
+ }
+
+ public void setDirection(String direction) {
+ this.direction = direction;
+ }
+ public String getDirection() {
+ return direction;
+ }
+
+ public void setLuckyTimes(List luckyTimes) {
+ this.luckyTimes = luckyTimes;
+ }
+ public List getLuckyTimes() {
+ return luckyTimes;
+ }
+
+ public void setFlower(String flower) {
+ this.flower = flower;
+ }
+ public String getFlower() {
+ return flower;
+ }
+
+ public void setFood(String food) {
+ this.food = food;
+ }
+ public String getFood() {
+ return food;
+ }
+
+ }
+
+
+ /**
+ * Copyright 2025 bejson.com
+ */
+ /**
+ * Auto-generated: 2025-07-21 17:23:17
+ *
+ * @author bejson.com (i@bejson.com)
+ * @website http://www.bejson.com/java2pojo/
+ */
+ public static class LuckyTimes implements Serializable{
+
+ private String time;
+ private String description;
+ public void setTime(String time) {
+ this.time = time;
+ }
+ public String getTime() {
+ return time;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+ public String getDescription() {
+ return description;
+ }
+
+ }
+
+ /**
+ * Copyright 2025 bejson.com
+ */
+ /**
+ * Auto-generated: 2025-07-21 17:23:17
+ *
+ * @author bejson.com (i@bejson.com)
+ * @website http://www.bejson.com/java2pojo/
+ */
+ public static class AstrologyInfluence implements Serializable{
+
+ private String constellation;
+ private String value;
+ private String description;
+ public void setConstellation(String constellation) {
+ this.constellation = constellation;
+ }
+ public String getConstellation() {
+ return constellation;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+ public String getValue() {
+ return value;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+ public String getDescription() {
+ return description;
+ }
+
+ }
+
+ /**
+ * Copyright 2025 bejson.com
+ */
+ /**
+ * Auto-generated: 2025-07-21 17:23:17
+ *
+ * @author bejson.com (i@bejson.com)
+ * @website http://www.bejson.com/java2pojo/
+ */
+ public static class Weather implements Serializable{
+
+ private String condition;
+ private String temperature;
+ public void setCondition(String condition) {
+ this.condition = condition;
+ }
+ public String getCondition() {
+ return condition;
+ }
+
+ public void setTemperature(String temperature) {
+ this.temperature = temperature;
+ }
+ public String getTemperature() {
+ return temperature;
+ }
+
+ }
+
+
+ /**
+ * Copyright 2025 bejson.com
+ */
+ /**
+ * Auto-generated: 2025-07-21 17:23:17
+ *
+ * @author bejson.com (i@bejson.com)
+ * @website http://www.bejson.com/java2pojo/
+ */
+ public static class BaziAnalysis implements Serializable{
+
+ private String nianzhu;
+ private String yuezhu;
+ private String rizhu;
+ private String shizhu;
+ private String todayInfluence;
+ public void setNianzhu(String nianzhu) {
+ this.nianzhu = nianzhu;
+ }
+ public String getNianzhu() {
+ return nianzhu;
+ }
+
+ public void setYuezhu(String yuezhu) {
+ this.yuezhu = yuezhu;
+ }
+ public String getYuezhu() {
+ return yuezhu;
+ }
+
+ public void setRizhu(String rizhu) {
+ this.rizhu = rizhu;
+ }
+ public String getRizhu() {
+ return rizhu;
+ }
+
+ public void setShizhu(String shizhu) {
+ this.shizhu = shizhu;
+ }
+ public String getShizhu() {
+ return shizhu;
+ }
+
+ public void setTodayInfluence(String todayInfluence) {
+ this.todayInfluence = todayInfluence;
+ }
+ public String getTodayInfluence() {
+ return todayInfluence;
+ }
+
+ }
+
+}
diff --git a/app/src/main/java/com/ckkj/water/fate/mvp/FateContract.java b/app/src/main/java/com/ckkj/water/fate/mvp/FateContract.java
new file mode 100644
index 0000000..b58f995
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/fate/mvp/FateContract.java
@@ -0,0 +1,56 @@
+package com.ckkj.water.fate.mvp;
+
+import com.ckkj.water.base.IView;
+import com.ckkj.water.base.entity.MineUserInfoBean;
+import com.ckkj.water.fate.entity.CityDataEntity;
+import com.ckkj.water.fate.entity.FateHistoryEntity;
+import com.ckkj.water.fate.entity.TodayFateEntity;
+import com.ckkj.water.network.BaseBean;
+
+import org.json.JSONObject;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/15
+ * @description TODO: 描述该类的作用
+ */
+
+public class FateContract {
+
+ public interface CallBack {
+ void success(T msg);
+ void fail(String msg);
+ }
+
+ public interface DataModel{
+ //查询所有城市
+ void getAllCityDatas(Map map, CallBack callBack);
+ //保存用户生日信息
+ void saveUserBirthInfo(JSONObject json, CallBack callBack);
+ //用户修改生日信息
+ void saveUserEditBirthInfo(JSONObject json, CallBack callBack);
+ //查询用户今日运势
+ void getUserTodayFateDatas(Map map, CallBack callBack);
+ //查询历史运势
+ void getUserFateHistory(Map map, CallBack callBack);
+ }
+
+
+ public interface FateView extends IView{
+ //查询所有城市成功
+ void getAllCitySuccess(List list);
+ //获取今日运势成功
+ void getUserTodayFateDataSuccess(TodayFateEntity entity);
+ //获取历史运势成功
+ void getFateHistorySuccess(FateHistoryEntity entity);
+ //用户保存生辰八字成功
+ void userSaveBirthInfoSuccess(TodayFateEntity bean);
+ //用户信息
+ void getUserInfoSuccess(MineUserInfoBean bean);
+ //提交举报反馈成功
+ void commitReportSuccess(BaseBean bean);
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/fate/mvp/FateImpl.java b/app/src/main/java/com/ckkj/water/fate/mvp/FateImpl.java
new file mode 100644
index 0000000..f4f7913
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/fate/mvp/FateImpl.java
@@ -0,0 +1,232 @@
+package com.ckkj.water.fate.mvp;
+
+import com.ckkj.water.fate.entity.CityDataEntity;
+import com.ckkj.water.fate.entity.FateHistoryEntity;
+import com.ckkj.water.fate.entity.TodayFateEntity;
+import com.ckkj.water.network.BaseBean;
+import com.ckkj.water.network.NetWorkManager;
+
+import org.json.JSONObject;
+
+import java.util.List;
+import java.util.Map;
+
+import io.reactivex.Observer;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.annotations.NonNull;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.schedulers.Schedulers;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/15
+ * @description TODO: 描述该类的作用
+ */
+
+public class FateImpl implements FateContract.DataModel{
+
+ /**
+ * 所有城市
+ * @param map
+ * @param callBack
+ */
+ @Override
+ public void getAllCityDatas(Map map, FateContract.CallBack callBack) {
+ try {
+ NetWorkManager.getRequest().getCityDatas(NetWorkManager.getToken(),map)
+ .subscribeOn(Schedulers.io())
+ .unsubscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new Observer>>() {
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {
+
+ }
+
+ @Override
+ public void onNext(@NonNull BaseBean> bean) {
+ if(bean.getCode() == 200){
+ callBack.success(bean.getData());
+ }else{
+ callBack.fail(bean.getMessage());
+ }
+ }
+
+ @Override
+ public void onError(@NonNull Throwable e) {
+ callBack.fail(e.getMessage());
+ }
+
+ @Override
+ public void onComplete() {
+ }
+ });
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 保存用户生辰信息
+ * @param json
+ * @param callBack
+ */
+ @Override
+ public void saveUserBirthInfo(JSONObject json, FateContract.CallBack callBack) {
+ try {
+ NetWorkManager.getRequest().saveUserBirthInfo(NetWorkManager.getToken(),NetWorkManager.toRequestBody(json))
+ .subscribeOn(Schedulers.io())
+ .unsubscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new Observer() {
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {
+
+ }
+
+ @Override
+ public void onNext(@NonNull BaseBean bean) {
+ if(bean.getCode() == 200){
+ callBack.success(bean);
+ }else{
+ callBack.fail(bean.getMessage());
+ }
+ }
+
+ @Override
+ public void onError(@NonNull Throwable e) {
+ callBack.fail(e.getMessage());
+ }
+
+ @Override
+ public void onComplete() {
+ }
+ });
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 用户修改生辰八字
+ * @param json
+ * @param callBack
+ */
+ @Override
+ public void saveUserEditBirthInfo(JSONObject json, FateContract.CallBack callBack) {
+ try {
+ NetWorkManager.getRequest().saveUserEditBirthInfo(NetWorkManager.getToken(),NetWorkManager.toRequestBody(json))
+ .subscribeOn(Schedulers.io())
+ .unsubscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new Observer>() {
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {
+
+ }
+
+ @Override
+ public void onNext(@NonNull BaseBean bean) {
+ if(bean.getCode() == 200){
+ callBack.success(bean.getData());
+ }else{
+ callBack.fail(bean.getMessage());
+ }
+ }
+
+ @Override
+ public void onError(@NonNull Throwable e) {
+ callBack.fail(e.getMessage());
+ }
+
+ @Override
+ public void onComplete() {
+ }
+ });
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 获取今日运势
+ * @param map
+ * @param callBack
+ */
+ @Override
+ public void getUserTodayFateDatas(Map map, FateContract.CallBack callBack) {
+ try {
+ NetWorkManager.getRequest().getUserTodayFateDatas(NetWorkManager.getToken())
+ .subscribeOn(Schedulers.io())
+ .unsubscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new Observer>() {
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {
+
+ }
+
+ @Override
+ public void onNext(@NonNull BaseBean bean) {
+ if(bean.getCode() == 200){
+ callBack.success(bean.getData());
+ }else{
+ callBack.fail(bean.getMessage());
+ }
+ }
+
+ @Override
+ public void onError(@NonNull Throwable e) {
+ callBack.fail(e.getMessage());
+ }
+
+ @Override
+ public void onComplete() {
+ }
+ });
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 获取历史数据
+ * @param map
+ * @param callBack
+ */
+ @Override
+ public void getUserFateHistory(Map map, FateContract.CallBack callBack) {
+ try {
+ NetWorkManager.getRequest().getUserFateHistory(NetWorkManager.getToken())
+ .subscribeOn(Schedulers.io())
+ .unsubscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new Observer>() {
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {
+
+ }
+
+ @Override
+ public void onNext(@NonNull BaseBean bean) {
+ if(bean.getCode() == 200){
+ callBack.success(bean.getData());
+ }else{
+ callBack.fail(bean.getMessage());
+ }
+ }
+
+ @Override
+ public void onError(@NonNull Throwable e) {
+ callBack.fail(e.getMessage());
+ }
+
+ @Override
+ public void onComplete() {
+ }
+ });
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/fate/mvp/FatePresenter.java b/app/src/main/java/com/ckkj/water/fate/mvp/FatePresenter.java
new file mode 100644
index 0000000..af9e6cd
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/fate/mvp/FatePresenter.java
@@ -0,0 +1,191 @@
+package com.ckkj.water.fate.mvp;
+
+import com.ckkj.water.base.BasePresenter;
+import com.ckkj.water.base.entity.MineUserInfoBean;
+import com.ckkj.water.chat.mvp.IndexContract;
+import com.ckkj.water.chat.mvp.IndexImpl;
+import com.ckkj.water.fate.entity.CityDataEntity;
+import com.ckkj.water.fate.entity.FateHistoryEntity;
+import com.ckkj.water.fate.entity.TodayFateEntity;
+import com.ckkj.water.login.mvp.LoginContract;
+import com.ckkj.water.mine.mvp.MineImpl;
+import com.ckkj.water.network.BaseBean;
+
+import org.json.JSONObject;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/15
+ * @description TODO: 描述该类的作用
+ */
+
+public class FatePresenter extends BasePresenter {
+ private FateImpl impl;
+
+ public FatePresenter(FateImpl impl) {
+ this.impl = impl;
+ }
+
+ /**
+ * 获取城市列表
+ */
+ public void getCityDatas() {
+ Map map = new HashMap<>();
+ impl.getAllCityDatas(map, new FateContract.CallBack>() {
+ @Override
+ public void success(List list) {
+ if(FatePresenter.this.getView() != null){
+ FatePresenter.this.getView().getAllCitySuccess(list);
+ }
+ }
+
+ @Override
+ public void fail(String msg) {
+ if(FatePresenter.this.getView() != null){
+ FatePresenter.this.getView().showError(msg);
+ }
+ }
+ });
+ }
+
+ /**
+ * 保存用户生辰
+ * @param json
+ */
+ public void saveUserBirthInfo(JSONObject json) {
+ impl.saveUserBirthInfo(json, new FateContract.CallBack() {
+ @Override
+ public void success(BaseBean bean) {
+ if(FatePresenter.this.getView() != null){
+ FatePresenter.this.getView().userSaveBirthInfoSuccess(null);
+ }
+ }
+
+ @Override
+ public void fail(String msg) {
+ if(FatePresenter.this.getView() != null){
+ FatePresenter.this.getView().showError(msg);
+ }
+ }
+ });
+ }
+
+ /**
+ * 获取今日运势
+ */
+ public void getTodayFateDatas() {
+ impl.getUserTodayFateDatas(new HashMap<>(), new FateContract.CallBack() {
+ @Override
+ public void success(TodayFateEntity bean) {
+ if(FatePresenter.this.getView() != null){
+ FatePresenter.this.getView().getUserTodayFateDataSuccess(bean);
+ }
+ }
+
+ @Override
+ public void fail(String msg) {
+ if(FatePresenter.this.getView() != null){
+ FatePresenter.this.getView().showError(msg);
+ }
+ }
+ });
+ }
+
+ /**
+ * 获取历史运势
+ * @param page
+ */
+ public void getHistoryDatas(int page) {
+ Map map = new HashMap<>();
+ map.put("pageNum", page);
+ map.put("pageSize", 10);
+ impl.getUserFateHistory(map, new FateContract.CallBack() {
+
+ @Override
+ public void success(FateHistoryEntity entity) {
+ if(FatePresenter.this.getView() != null){
+ FatePresenter.this.getView().getFateHistorySuccess(entity);
+ }
+ }
+
+ @Override
+ public void fail(String msg) {
+ if(FatePresenter.this.getView() != null){
+ FatePresenter.this.getView().showError(msg);
+ }
+ }
+ });
+ }
+
+ /**
+ * 用户修改的生辰八字保存
+ * @param json
+ */
+ public void saveEditBirthInfo(JSONObject json) {
+ impl.saveUserEditBirthInfo(json, new FateContract.CallBack() {
+ @Override
+ public void success(TodayFateEntity bean) {
+ if(FatePresenter.this.getView() != null){
+ FatePresenter.this.getView().userSaveBirthInfoSuccess(bean);
+ }
+ }
+
+ @Override
+ public void fail(String msg) {
+ if(FatePresenter.this.getView() != null){
+ FatePresenter.this.getView().showError(msg);
+ }
+ }
+ });
+ }
+
+ /**
+ * 获取个人信息
+ */
+ public void getUserInfo() {
+ Map map = new HashMap<>();
+// new MineImpl().getUserInfo(map, new LoginContract.CallBack() {
+// @Override
+// public void success(MineUserInfoBean bean) {
+// if( FatePresenter.this.getView() != null){
+// FatePresenter.this.getView().getUserInfoSuccess(bean);
+// }
+// }
+//
+// @Override
+// public void fail(String msg) {
+// if( FatePresenter.this.getView() != null){
+// FatePresenter.this.getView().showError(msg);
+// }
+// }
+// });
+ }
+
+ /**
+ * 举报
+ * @param reason
+ */
+ public void commitReport(String reason) {
+ Map map = new HashMap<>();
+ map.put("content", reason);
+ new IndexImpl().commitReport(map, new IndexContract.CallBack() {
+ @Override
+ public void success(BaseBean bean) {
+ if (FatePresenter.this.getView() != null) {
+ FatePresenter.this.getView().commitReportSuccess(bean);
+ }
+ }
+
+ @Override
+ public void fail(String msg) {
+ if (FatePresenter.this.getView() != null) {
+ FatePresenter.this.getView().showError(msg);
+ }
+ }
+ });
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/fate/util/LunarDateLoader.java b/app/src/main/java/com/ckkj/water/fate/util/LunarDateLoader.java
new file mode 100644
index 0000000..46513f8
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/fate/util/LunarDateLoader.java
@@ -0,0 +1,75 @@
+package com.ckkj.water.fate.util;
+
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+
+import org.json.JSONArray;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+public class LunarDateLoader {
+
+ // 缓存静态变量
+ private static JSONArray cachedLunarJsonArray = null;
+
+ /**
+ * 初始化缓存(异步调用,建议在 Application 中调用)
+ */
+ public static void preloadLunarJson(final Context context) {
+ if (cachedLunarJsonArray != null) return; // 已加载过则跳过
+
+ new Thread(() -> {
+ try {
+ InputStream inputStream = context.getAssets().open("lunar_1899_2099.json");
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ byte[] buffer = new byte[1024];
+ int len;
+
+ while ((len = inputStream.read(buffer)) != -1) {
+ outputStream.write(buffer, 0, len);
+ }
+
+ inputStream.close();
+ String jsonStr = outputStream.toString("UTF-8");
+ cachedLunarJsonArray = new JSONArray(jsonStr);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }).start();
+ }
+
+ /**
+ * 获取缓存的农历 JSON 数据(同步获取)
+ * 可在页面中直接调用
+ */
+ @Nullable
+ public static JSONArray getCachedLunarJson() {
+ return cachedLunarJsonArray;
+ }
+
+ /**
+ * 同步加载(兜底)
+ */
+ @Nullable
+ public static JSONArray loadJsonFromAssets(Context context, String fileName) {
+ try {
+ InputStream inputStream = context.getAssets().open(fileName);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ byte[] buffer = new byte[1024];
+ int len;
+
+ while ((len = inputStream.read(buffer)) != -1) {
+ outputStream.write(buffer, 0, len);
+ }
+
+ inputStream.close();
+ String jsonStr = outputStream.toString("UTF-8");
+ return new JSONArray(jsonStr);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ckkj/water/fate/util/LunarDatePicker.java b/app/src/main/java/com/ckkj/water/fate/util/LunarDatePicker.java
new file mode 100644
index 0000000..75813d3
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/fate/util/LunarDatePicker.java
@@ -0,0 +1,96 @@
+package com.ckkj.water.fate.util;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import com.ckkj.water.fate.entity.LunarYearEntity;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+
+public class LunarDatePicker {
+ private List lunarYears; // 所有年份数据
+ private List currentYearMonths; // 当前选中年的月份数据
+
+ // 月份名称映射(数字->中文)
+ private static final String[] MONTH_NAMES = {
+ "正月", "二月", "三月", "四月", "五月", "六月",
+ "七月", "八月", "九月", "十月", "冬月", "腊月"
+ };
+
+ // 日期名称列表(初一到三十)
+ private static final String[] DAY_NAMES = {
+ "初一", "初二", "初三", "初四", "初五", "初六", "初七", "初八", "初九", "初十",
+ "十一", "十二", "十三", "十四", "十五", "十六", "十七", "十八", "十九", "二十",
+ "廿一", "廿二", "廿三", "廿四", "廿五", "廿六", "廿七", "廿八", "廿九", "三十"
+ };
+
+ public LunarDatePicker(String jsonData) {
+ // 解析JSON数据
+ parseLunarData(jsonData);
+ }
+
+ // 解析JSON数据
+ private void parseLunarData(String jsonData) {
+ Gson gson = new Gson();
+ Type type = new TypeToken>(){}.getType();
+ lunarYears = gson.fromJson(jsonData, type);
+ }
+
+ // 获取年份列表(用于年的WheelView)
+ public List getYearList() {
+ List years = new ArrayList<>();
+ for (LunarYearEntity year : lunarYears) {
+ years.add(year.getYear());
+ }
+ return years;
+ }
+
+ // 根据选中的年份索引获取月份列表(用于月的WheelView)
+ public List getMonthList(int yearIndex) {
+ if (yearIndex < 0 || yearIndex >= lunarYears.size()) {
+ return new ArrayList<>();
+ }
+
+ LunarYearEntity selectedYear = lunarYears.get(yearIndex);
+ currentYearMonths = selectedYear.getMonth(); // 保存当前年的月份数据
+
+ List monthList = new ArrayList<>();
+ for (LunarYearEntity.LunarMonth month : selectedYear.month) {
+ int monthNum = Integer.parseInt(month.name);
+ String monthName = MONTH_NAMES[monthNum - 1]; // 数组索引从0开始
+
+ // 处理闰月
+ if (month.isRun == 1) {
+ monthName = "闰" + monthName;
+ }
+
+ monthList.add(monthName);
+ }
+ return monthList;
+ }
+
+ // 根据选中的月份索引获取日期列表(用于日的WheelView)
+ public List getDayList(int monthIndex) {
+ if (currentYearMonths == null || monthIndex < 0 || monthIndex >= currentYearMonths.size()) {
+ return new ArrayList<>();
+ }
+
+ LunarYearEntity.LunarMonth selectedMonth = currentYearMonths.get(monthIndex);
+ int days = selectedMonth.isAllDay == 1 ? 30 : 29; // 确定天数
+
+ List dayList = new ArrayList<>();
+ for (int i = 0; i < days; i++) {
+ dayList.add(DAY_NAMES[i]);
+ }
+ return dayList;
+ }
+
+ // 获取选中的完整日期(年、月、日索引)
+ public String getSelectedDate(int yearIndex, int monthIndex, int dayIndex) {
+ String year = lunarYears.get(yearIndex).year + "年";
+ String month = getMonthList(yearIndex).get(monthIndex);
+ String day = getDayList(monthIndex).get(dayIndex);
+ return year + month + day;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ckkj/water/fate/view/FateFragment.java b/app/src/main/java/com/ckkj/water/fate/view/FateFragment.java
new file mode 100644
index 0000000..5e754ab
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/fate/view/FateFragment.java
@@ -0,0 +1,758 @@
+package com.ckkj.water.fate.view;
+
+import static android.app.Activity.RESULT_OK;
+import static com.qweather.sdk.QWeather.instance;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.view.animation.AccelerateDecelerateInterpolator;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.amap.api.services.core.AMapException;
+import com.amap.api.services.core.LatLonPoint;
+import com.amap.api.services.geocoder.GeocodeAddress;
+import com.amap.api.services.geocoder.GeocodeQuery;
+import com.amap.api.services.geocoder.GeocodeResult;
+import com.amap.api.services.geocoder.GeocodeSearch;
+import com.google.android.material.progressindicator.CircularProgressIndicator;
+import com.google.android.material.progressindicator.LinearProgressIndicator;
+import com.ckkj.water.AppConfig;
+import com.ckkj.water.ConstantUtil;
+import com.ckkj.water.EventMessage;
+import com.ckkj.water.R;
+import com.ckkj.water.base.BaseFragment;
+import com.ckkj.water.base.entity.MineUserInfoBean;
+import com.ckkj.water.custom.view.CustomRefreshHeader;
+import com.ckkj.water.databinding.FateLayoutBinding;
+import com.ckkj.water.dialog.ContentReportDialog;
+import com.ckkj.water.fate.entity.CityDataEntity;
+import com.ckkj.water.fate.entity.FateHistoryEntity;
+import com.ckkj.water.fate.entity.TodayFateEntity;
+import com.ckkj.water.fate.mvp.FateContract;
+import com.ckkj.water.fate.mvp.FateImpl;
+import com.ckkj.water.fate.mvp.FatePresenter;
+import com.ckkj.water.network.BaseBean;
+import com.ckkj.water.utils.WeatherIconUtil;
+import com.ckkj.water.utils.click.ClickUtil;
+import com.ckkj.water.utils.color.FateDataUtil;
+import com.ckkj.water.utils.dataUtil.SharePreUtil;
+import com.ckkj.water.utils.manager.MingDengUserInfoManager;
+import com.ckkj.water.utils.number.NumberDecimalFilter;
+import com.ckkj.water.utils.show.ToastUtil;
+import com.ckkj.water.utils.time.ZYTimeUtils;
+import com.qweather.sdk.Callback;
+import com.qweather.sdk.JWTGenerator;
+import com.qweather.sdk.QWeather;
+import com.qweather.sdk.basic.Lang;
+import com.qweather.sdk.basic.Unit;
+import com.qweather.sdk.parameter.weather.WeatherParameter;
+import com.qweather.sdk.response.error.ErrorResponse;
+import com.qweather.sdk.response.geo.GeoPoiResponse;
+import com.qweather.sdk.response.weather.WeatherNowResponse;
+import com.scwang.smart.refresh.layout.api.RefreshHeader;
+
+import java.util.List;
+import java.util.Calendar;
+import android.os.Handler;
+import android.os.Looper;
+
+import de.greenrobot.event.Subscribe;
+import de.greenrobot.event.ThreadMode;
+import pub.devrel.easypermissions.EasyPermissions;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/7
+ * @description TODO: 描述该类的作用
+ */
+
+public class FateFragment extends BaseFragment
+ implements FateContract.FateView , EasyPermissions.PermissionCallbacks{
+
+ private TodayFateEntity mTodayEntity;
+ // 添加动画执行标志位
+ private boolean isCareerAnimPlayed = false;
+ private boolean isLoveAnimPlayed = false;
+ private boolean isHealthAnimPlayed = false;
+ private boolean isWealthAnimPlayed = false;
+ private boolean isOverallAnimPlayed = false;
+ //是否已经加载过
+ private boolean hasLoaded = false;
+
+ // 添加Toast相关的成员变量
+ private Handler toastHandler = new Handler(Looper.getMainLooper());
+ private Runnable toastRunnable;
+ private boolean isToastTaskRunning = false;
+ private int currentToastIndex = 0;
+ private String[] toastMessages = {
+ "正在查看您的今日能量值",
+ "让我帮你看看今天的星座运势",
+ "正在生成您的幸运元素",
+ "别着急,让我来想想今天的事宜"
+ };
+
+
+ @Override
+ public void setUserVisibleHint(boolean isVisibleToUser) {
+ super.setUserVisibleHint(isVisibleToUser);
+ }
+
+
+ @Override
+ protected void onResumeInitData() {
+ super.onResumeInitData();
+ // 确保在Fragment可见时检查动画
+ viewBinding.getRoot().postDelayed(() -> {
+ checkAndAnimateProgressBars();
+ }, 300);
+ if(AppConfig.USER_LOGIN){
+ mPresenter.getUserInfo();
+ }else{
+ viewBinding.llInput.setVisibility(View.VISIBLE);
+ }
+ if (getUserVisibleHint() && !hasLoaded) {
+ if(AppConfig.USER_LOGIN){
+ showLoadingDialog();
+ // 调用接口并启动延迟Toast任务
+ startFateDataRequest();
+ hasLoaded = true;
+ }
+ }
+ }
+
+
+ @Override
+ public void initData(Bundle state) {
+ //请求定位权限
+// String permission = Manifest.permission.ACCESS_FINE_LOCATION;
+// if(!hasRecordPermission(permission)){
+// getPermissionRequst(permission,"定位权限申请","叮叮车需要您的授权,获取您当前所在的城市",REQUEST_EVENT_CODE_10002);
+// }else{
+// AMapLocationClient.updatePrivacyAgree(mContext, true);
+// initLocationInfo();
+// }
+
+ // 设置当前日期和星期几
+ setCurrentDate();
+
+ //和风天气 init
+ try {
+ JWTGenerator jwt = new JWTGenerator(
+ "-----BEGIN PRIVATE KEY-----\n" +
+ "MC4CAQAwBQYDK2VwBCIEIFH+qB/0rER1mEpgAIwpBiAShW9pnSAj8OipGY/eFLRb\n" +
+ "-----END PRIVATE KEY-----",//密钥
+ "2A89MF5WVU",//项目 ID
+ "K5PU3M4P2A");//凭据 ID
+
+ instance = QWeather.getInstance(mContext, "mr3597qqpv.re.qweatherapi.com")
+ .setTokenGenerator(jwt)
+ .setLogEnable(true);
+ Log.d(TAG,"和风天气初始化成功");
+ } catch (Throwable e) {
+ Log.d(TAG,"和风天气初始化异常");
+ throw new RuntimeException(e);
+ }
+ // 添加视图可见性监听
+ setupProgressAnimations();
+ ClickUtil.addClickPart(viewBinding.imgHistory,60);
+ // 设置自定义刷新头
+ RefreshHeader header = new CustomRefreshHeader(mContext);
+ viewBinding.refresh.setRefreshHeader(header);
+// viewBinding.refresh.setRefreshFooter(new ClassicsFooter(mContext));
+ viewBinding.refresh.setOnRefreshListener(layout -> {
+ if(AppConfig.USER_LOGIN){
+ showLoadingDialog();
+ // 调用接口并启动延迟Toast任务
+ startFateDataRequest();
+ hasLoaded = true;
+ }
+ });
+ }
+
+ /**
+ * 设置当前日期和星期几
+ */
+ private void setCurrentDate() {
+ Calendar calendar = Calendar.getInstance();
+ int month = calendar.get(Calendar.MONTH) + 1; // Calendar.MONTH 从0开始,所以要+1
+ int day = calendar.get(Calendar.DAY_OF_MONTH);
+ int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
+
+ // 将数字星期转换为中文
+ String[] weekDays = {"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
+ String weekDayStr = weekDays[dayOfWeek - 1]; // Calendar.DAY_OF_WEEK 从1开始(星期日=1)
+
+ // 格式化日期字符串:例如 "9 月 9 日 星期三"
+ String dateStr = month + " 月 " + day + " 日 " + weekDayStr;
+
+ // 设置到TextView
+ viewBinding.tvDate.setText(dateStr);
+ }
+
+ private void setupProgressAnimations() {
+ // 监听父滚动视图(假设外层是NestedScrollView)
+ View scrollView = viewBinding.getRoot().findViewById(R.id.nesd_scrollview); // 替换为你的NestedScrollView id
+
+ if (scrollView != null) {
+ scrollView.getViewTreeObserver().addOnScrollChangedListener(() -> {
+ checkAndAnimateProgressBars();
+ });
+ }
+
+ // 初始检查(如果视图已经可见)
+ viewBinding.getRoot().postDelayed(() -> {
+ checkAndAnimateProgressBars();
+ }, 300); // 延迟300ms确保布局完成
+ }
+
+ private void checkAndAnimateProgressBars() {
+ if(mTodayEntity == null){
+ return;
+ }
+ // 圆形进度条(运势分数)
+ if (isViewVisible(viewBinding.includedTodayFateDatas.progressIndicator) && !isOverallAnimPlayed) {
+ animateCircularProgress(
+ viewBinding.includedTodayFateDatas.progressIndicator,
+ mTodayEntity.getFortuneAnalysis().getOverallScore()
+ );
+ isOverallAnimPlayed = true;
+ }
+ // 检查每个进度条是否需要执行动画
+ if (isViewVisible(viewBinding.includedSyy.proSyy) && !isCareerAnimPlayed) {
+ animateProgress(viewBinding.includedSyy.proSyy,
+ mTodayEntity.getFortuneAnalysis().getCareerScore());
+ isCareerAnimPlayed = true;
+ }
+
+ if (isViewVisible(viewBinding.includedSyy.proGqy) && !isLoveAnimPlayed) {
+ animateProgress(viewBinding.includedSyy.proGqy,
+ mTodayEntity.getFortuneAnalysis().getLoveScore());
+ isLoveAnimPlayed = true;
+ }
+
+ if (isViewVisible(viewBinding.includedSyy.proJky) && !isHealthAnimPlayed) {
+ animateProgress(viewBinding.includedSyy.proJky,
+ mTodayEntity.getFortuneAnalysis().getHealthScore());
+ isHealthAnimPlayed = true;
+ }
+
+ if (isViewVisible(viewBinding.includedSyy.proCfy) && !isWealthAnimPlayed) {
+ animateProgress(viewBinding.includedSyy.proCfy,
+ mTodayEntity.getFortuneAnalysis().getWealthScore());
+ isWealthAnimPlayed = true;
+ }
+ }
+
+ // 判断视图是否可见
+ private boolean isViewVisible(View view) {
+ if (view == null) return false;
+
+ Rect scrollBounds = new Rect();
+ View scrollView = viewBinding.getRoot().findViewById(R.id.nesd_scrollview);
+
+ if (scrollView != null) {
+ scrollView.getHitRect(scrollBounds);
+ return view.getLocalVisibleRect(scrollBounds);
+ }
+ return false;
+ }
+
+ // 执行进度条动画
+ private void animateProgress(LinearProgressIndicator progressBar, int targetProgress) {
+ // 保存原始进度值
+ final int originalProgress = progressBar.getProgress();
+
+ // 先重置为0
+ progressBar.setProgress(0);
+
+ // 创建动画
+ ObjectAnimator animator = ObjectAnimator.ofInt(progressBar, "progress", 0, targetProgress);
+ animator.setDuration(800); // 动画时长800ms
+ animator.setInterpolator(new AccelerateDecelerateInterpolator());
+
+ // 添加脉冲效果
+ animator.addUpdateListener(animation -> {
+ float fraction = animation.getAnimatedFraction();
+ float scale = 1 + 0.2f * (float) Math.sin(fraction * Math.PI);
+ progressBar.setScaleX(scale);
+ progressBar.setScaleY(scale);
+ });
+
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // 恢复原始大小
+ progressBar.animate()
+ .scaleX(1f)
+ .scaleY(1f)
+ .setDuration(200)
+ .start();
+ }
+ });
+ animator.start();
+ }
+
+
+ // 执行圆形进度条动画(带旋转效果)
+ private void animateCircularProgress(CircularProgressIndicator progressBar, int targetProgress) {
+ // 保存原始进度值
+ final int originalProgress = progressBar.getProgress();
+
+ // 先重置为0
+ progressBar.setProgress(0);
+
+ // 创建进度动画
+ ValueAnimator progressAnimator = ValueAnimator.ofInt(0, targetProgress);
+ progressAnimator.setDuration(1000); // 动画时长1000ms
+ progressAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
+
+ // 创建旋转动画
+ ObjectAnimator rotationAnimator = ObjectAnimator.ofFloat(progressBar, "rotation", 0f, 360f);
+ rotationAnimator.setDuration(800);
+ rotationAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
+
+ // 组合动画
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.playTogether(progressAnimator, rotationAnimator);
+
+ // 添加脉冲缩放效果
+ progressAnimator.addUpdateListener(animation -> {
+ int progress = (int) animation.getAnimatedValue();
+ progressBar.setProgress(progress);
+
+ // 脉冲效果:在动画过程中轻微缩放
+ float fraction = animation.getAnimatedFraction();
+ float scale = 1 + 0.12f * (float) Math.sin(fraction * Math.PI);
+ progressBar.setScaleX(scale);
+ progressBar.setScaleY(scale);
+ });
+
+ animatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // 恢复原始大小
+ progressBar.animate()
+ .scaleX(1f)
+ .scaleY(1f)
+ .setDuration(200)
+ .start();
+ }
+ });
+
+ animatorSet.start();
+ }
+
+ @Override
+ public FatePresenter createPresenter() {
+ return new FatePresenter(new FateImpl());
+ }
+
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if(data != null){
+ if(resultCode == RESULT_OK){
+ switch (requestCode){
+ case REQUEST_EVENT_CODE_10002:
+ break;
+
+ case REQUEST_EVENT_CODE_10001:
+
+ break;
+ }
+ }
+ }
+ }
+
+ @Subscribe(threadMode = ThreadMode.MainThread)
+ public void onGetMessage(EventMessage message) {
+ switch (message.msg){
+ case EventMessage.DATA_REFRESH_EVENT:
+ showLoadingDialog();
+ startFateDataRequest();
+ break;
+
+ case EventMessage.USER_LOGIN_SUCCESS:
+ startFateDataRequest();
+ break;
+ }
+ }
+
+ /**
+ * 启动今日运势数据请求并开始延迟Toast任务
+ */
+ private void startFateDataRequest() {
+ // 停止之前的Toast任务(如果有的话)
+ stopToastTask();
+
+ // 调用接口
+ mPresenter.getTodayFateDatas();
+
+ // 启动延迟Toast任务
+ startDelayedToastTask();
+ }
+
+ /**
+ * 启动延迟Toast任务
+ */
+ private void startDelayedToastTask() {
+ // 5秒后开始显示Toast
+ toastHandler.postDelayed(() -> {
+ if (!isToastTaskRunning) {
+ isToastTaskRunning = true;
+ currentToastIndex = 0;
+ startToastLoop();
+ }
+ }, 5000);
+ }
+
+ /**
+ * 开始Toast循环显示
+ */
+ private void startToastLoop() {
+ if (!isToastTaskRunning) {
+ return;
+ }
+
+ toastRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (isToastTaskRunning && currentToastIndex < toastMessages.length) {
+ // 显示当前Toast
+ ToastUtil.showLong(mContext, toastMessages[currentToastIndex]);
+ currentToastIndex++;
+
+ // 如果还有下一条消息,5秒后显示
+ if (currentToastIndex < toastMessages.length && isToastTaskRunning) {
+ toastHandler.postDelayed(this, 5000);
+ } else {
+ // 所有消息显示完毕,重新开始循环
+ if (isToastTaskRunning) {
+ currentToastIndex = 0;
+ toastHandler.postDelayed(this, 5000);
+ }
+ }
+ }
+ }
+ };
+
+ // 立即执行第一次
+ toastRunnable.run();
+ }
+
+ /**
+ * 停止Toast任务
+ */
+ private void stopToastTask() {
+ isToastTaskRunning = false;
+ if (toastRunnable != null) {
+ toastHandler.removeCallbacks(toastRunnable);
+ }
+ // 移除所有待执行的任务
+ toastHandler.removeCallbacksAndMessages(null);
+ }
+
+ /**
+ * 今日运势请求成功
+ * @param entity
+ */
+ @Override
+ public void getUserTodayFateDataSuccess(TodayFateEntity entity) {
+ // 立即停止Toast任务
+ stopToastTask();
+
+ hideDialogLoading();
+ mTodayEntity = entity;
+ getLatLngByCityName(entity.getUserInfo().getCurrentPlace());
+ setDataShow();
+ viewBinding.refresh.finishRefresh(300);
+ }
+
+ @Override
+ public void getFateHistorySuccess(FateHistoryEntity entity) {
+
+ }
+
+ @Override
+ public void userSaveBirthInfoSuccess(TodayFateEntity bean) {
+
+ }
+
+ @Override
+ public void getUserInfoSuccess(MineUserInfoBean bean) {
+ if(bean != null){
+ MingDengUserInfoManager.getInstance().setUserInfo(bean.getUserInfo());
+ SharePreUtil.putBoolean(ConstantUtil.SHARE_PRE_NAME,mContext,ConstantUtil.USER_HAS_SET_BIRTH,bean.isHasEightCharacters());
+ AppConfig.USER_SET_BIRTH_INFO = bean.isHasEightCharacters();
+ //是否设置过生辰
+ if(AppConfig.USER_SET_BIRTH_INFO){
+ viewBinding.llInput.setVisibility(View.GONE);
+ viewBinding.refresh.setVisibility(View.VISIBLE);
+ }else{
+ viewBinding.llInput.setVisibility(View.VISIBLE);
+ viewBinding.refresh.setVisibility(View.GONE);
+ }
+ }
+ }
+
+ /**
+ * 提交举报成功
+ * @param bean
+ */
+ @Override
+ public void commitReportSuccess(BaseBean bean) {
+ ToastUtil.showShort(mContext, "星语明灯感谢您的反馈");
+ hideDialogLoading();
+ }
+
+ /**
+ * 设置显示内容
+ */
+ private void setDataShow() {
+ //出生日期
+ String birthDay = mTodayEntity.getUserInfo().getBirthTime();
+ viewBinding.includedBirthInfo.tvBirthdayTime.setText(ZYTimeUtils.formatDateString(birthDay));
+ //真太阳时
+ String realSunTime = mTodayEntity.getUserInfo().getTrueSolarTime();
+ viewBinding.includedBirthInfo.tvZhentaiyangValue.setText(TextUtils.isEmpty(realSunTime) ?
+ "未知":ZYTimeUtils.formatToChineseDate(realSunTime));
+ //命格
+ viewBinding.includedBirthInfo.tvMingge.setText(TextUtils.isEmpty(mTodayEntity.getFortuneAnalysis().getDestiny()) ?
+ "未知": mTodayEntity.getFortuneAnalysis().getDestiny());
+ //经纬度
+ String lng = NumberDecimalFilter.truncateToTwoDecimal(Double.parseDouble(mTodayEntity.getUserInfo().getLongitude()));
+ String lat = NumberDecimalFilter.truncateToTwoDecimal(Double.parseDouble(mTodayEntity.getUserInfo().getLatitude()));
+ viewBinding.includedBirthInfo.tvLonglat.setText(lng + "," + lat);
+ //出生地
+ viewBinding.includedBirthInfo.tvBirthAddress.setText(mTodayEntity.getUserInfo().getBirthPlace());
+ //星座
+ viewBinding.includedBirthInfo.tvXingzuo.setText(mTodayEntity.getUserInfo().getConstellation());
+ //运势分数
+ int todayScore = mTodayEntity.getFortuneAnalysis().getOverallScore();
+ int color = FateDataUtil.getScoreColor(todayScore);
+ viewBinding.includedTodayFateDatas.progressIndicator.setIndicatorColor(color);
+// viewBinding.includedTodayFateDatas.progressIndicator.setProgress(todayScore,true);
+ viewBinding.includedTodayFateDatas.tvFeenValue.setText(String.valueOf(todayScore));
+ viewBinding.includedTodayFateDatas.tvFeenValue.setTextColor(color);
+ //运势内容
+ viewBinding.includedTodayFateDatas.tvYunshiContent.setText(TextUtils.isEmpty(mTodayEntity.getFortuneAnalysis().getOverallDescription())?
+ "暂无内容" : mTodayEntity.getFortuneAnalysis().getOverallDescription());
+ //今日吉时
+ viewBinding.includedTodayFateDatas.tvTimeZc.setText(mTodayEntity.getFortuneAnalysis().getLuckyInfo().getLuckyTimes().get(0).getTime());
+ viewBinding.includedTodayFateDatas.tvThingZc.setText(mTodayEntity.getFortuneAnalysis().getLuckyInfo().getLuckyTimes().get(0).getDescription());
+ viewBinding.includedTodayFateDatas.tvTimeZw.setText(mTodayEntity.getFortuneAnalysis().getLuckyInfo().getLuckyTimes().get(1).getTime());
+ viewBinding.includedTodayFateDatas.tvThingZw.setText(mTodayEntity.getFortuneAnalysis().getLuckyInfo().getLuckyTimes().get(1).getDescription());
+ viewBinding.includedTodayFateDatas.tvTimeXw.setText(mTodayEntity.getFortuneAnalysis().getLuckyInfo().getLuckyTimes().get(2).getTime());
+ viewBinding.includedTodayFateDatas.tvThingXw.setText(mTodayEntity.getFortuneAnalysis().getLuckyInfo().getLuckyTimes().get(2).getDescription());
+ //天象星座
+ viewBinding.includedXingzuo.tvXingzuo.setText(mTodayEntity.getUserInfo().getConstellation());
+ viewBinding.includedXingzuo.tvWendu.setText(mTodayEntity.getWeatherTemperature());
+ viewBinding.includedXingzuo.tvTianqi.setText(mTodayEntity.getWeatherCondition());
+ viewBinding.includedXingzuo.tvXzType.setText(FateDataUtil.getConstellationType(mTodayEntity.getUserInfo().getConstellation()));
+ viewBinding.includedXingzuo.tvXingzuoYingxiang.setText(mTodayEntity.getFortuneAnalysis().getAstrologyInfluence().getDescription());
+ //事业运
+ viewBinding.includedSyy.tvSyyScore.setText(String.valueOf(mTodayEntity.getFortuneAnalysis().getCareerScore()));
+// viewBinding.includedSyy.proSyy.setProgress(mTodayEntity.getFortuneAnalysis().getCareerScore());
+ viewBinding.includedSyy.tvSyyContent.setText(mTodayEntity.getFortuneAnalysis().getCareerDescription());
+ //事业运
+ viewBinding.includedSyy.tvGqyScore.setText(String.valueOf(mTodayEntity.getFortuneAnalysis().getLoveScore()));
+// viewBinding.includedSyy.proGqy.setProgress(mTodayEntity.getFortuneAnalysis().getLoveScore());
+ viewBinding.includedSyy.tvGqyContent.setText(mTodayEntity.getFortuneAnalysis().getLoveDescription());
+ //事业运
+ viewBinding.includedSyy.tvJkyScore.setText(String.valueOf(mTodayEntity.getFortuneAnalysis().getHealthScore()));
+// viewBinding.includedSyy.proJky.setProgress(mTodayEntity.getFortuneAnalysis().getHealthScore());
+ viewBinding.includedSyy.tvJkyContent.setText(mTodayEntity.getFortuneAnalysis().getHealthDescription());
+ //事业运
+ viewBinding.includedSyy.tvCfyScore.setText(String.valueOf(mTodayEntity.getFortuneAnalysis().getWealthScore()));
+// viewBinding.includedSyy.proCfy.setProgress(mTodayEntity.getFortuneAnalysis().getWealthScore());
+ viewBinding.includedSyy.tvCfyContent.setText(mTodayEntity.getFortuneAnalysis().getWealthDescription());
+ //幸运数字
+ String [] numbers = mTodayEntity.getFortuneAnalysis().getLuckyInfo().getNumbers();
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < numbers.length; i++) {
+ sb.append(numbers[i]);
+ if (i < numbers.length - 1) {
+ sb.append("、");
+ }
+ }
+ viewBinding.includeXingyun.tvLuckNum.setText(sb.toString());
+ //幸运颜色
+ viewBinding.includeXingyun.tvLuckColor.setText(mTodayEntity.getFortuneAnalysis().getLuckyInfo().getColor());
+ //幸运方位
+ viewBinding.includeXingyun.tvLuckFw.setText(mTodayEntity.getFortuneAnalysis().getLuckyInfo().getDirection());
+ //幸运时间
+ viewBinding.includeXingyun. tvLuckTime.setText(mTodayEntity.getFortuneAnalysis().getLuckyInfo().getLuckyTimes().get(0).getTime());
+ //幸运花卉
+ viewBinding.includeXingyun.tvLuckFlower.setText(mTodayEntity.getFortuneAnalysis().getLuckyInfo().getFlower());
+ //幸运方位
+ viewBinding.includeXingyun.tvLuckFood.setText(mTodayEntity.getFortuneAnalysis().getLuckyInfo().getFood());
+ //宜 忌
+ String positive = mTodayEntity.getFortuneAnalysis().getSuggestions().getPositive();
+ String negative = mTodayEntity.getFortuneAnalysis().getSuggestions().getNegative();
+ viewBinding.includeYsjy.tvPositive.setText(positive);
+ viewBinding.includeYsjy.tvNegative.setText(negative);
+ //五行分析
+ viewBinding.includeYsjy.tvFive.setText(mTodayEntity.getFortuneAnalysis().getFiveElementsAnalysis());
+ // 重置动画标志位
+ resetAnimationFlags();
+ }
+
+ /**
+ * 通过城市逆地理编码,获取经纬度
+ * @param cityName
+ */
+ private void getLatLngByCityName(String cityName) {
+ try {
+ GeocodeSearch geocodeSearch = new GeocodeSearch(mContext);
+ geocodeSearch.setOnGeocodeSearchListener(new GeocodeSearch.OnGeocodeSearchListener() {
+ @Override
+ public void onRegeocodeSearched(com.amap.api.services.geocoder.RegeocodeResult regeocodeResult, int i) {
+ // Not used here
+ }
+
+ @Override
+ public void onGeocodeSearched(GeocodeResult geocodeResult, int rCode) {
+ if (rCode == AMapException.CODE_AMAP_SUCCESS) {
+ List addressList = geocodeResult.getGeocodeAddressList();
+ if (addressList != null && !addressList.isEmpty()) {
+ GeocodeAddress address = addressList.get(0);
+ LatLonPoint latLonPoint = address.getLatLonPoint();
+ String lat = String.valueOf(NumberDecimalFilter.roundToTwoDecimalPlaces(latLonPoint.getLatitude()));
+ String lng = String.valueOf(NumberDecimalFilter.roundToTwoDecimalPlaces(latLonPoint.getLongitude()));
+ WeatherParameter parameter = new WeatherParameter(lng + "," + lat)
+ .lang(Lang.ZH_HANS)
+ .unit(Unit.METRIC);
+ instance.weatherNow(parameter, new Callback() {
+ @Override
+ public void onSuccess(WeatherNowResponse response) {
+ mContext.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ WeatherIconUtil.loadWeatherIcon(mContext,viewBinding.includedBirthInfo.imgWeather,response.getNow().getIcon());
+ viewBinding.includedBirthInfo.tvTianqi.setText(response.getNow().getText() + " " +response.getNow().getTemp() + "℃");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onFailure(ErrorResponse errorResponse) {
+ Log.d(TAG,"调用和风 API--onFailure" + errorResponse.toString());
+ }
+
+ @Override
+ public void onException(Throwable e) {
+ Log.d(TAG,"调用和风 API--onException" + e.getMessage());
+ e.printStackTrace();
+ }
+ });
+ } else {
+ Log.d("GeoUtil", "未找到城市: " + cityName);
+ }
+ } else {
+ Log.e("GeoUtil", "查询失败,错误码: " + rCode);
+ }
+ }
+ });
+ // 创建 GeocodeQuery,第一个参数是城市名,第二个参数可指定省份(可选)
+ GeocodeQuery query = new GeocodeQuery(cityName, null);
+ geocodeSearch.getFromLocationNameAsyn(query); // 异步查询
+ } catch (AMapException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ // 重置动画标志位
+ private void resetAnimationFlags() {
+ isOverallAnimPlayed = false;
+ isCareerAnimPlayed = false;
+ isLoveAnimPlayed = false;
+ isHealthAnimPlayed = false;
+ isWealthAnimPlayed = false;
+ }
+
+
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ // 停止Toast任务
+ stopToastTask();
+ // 重置标志位以便下次可见时重新动画
+ resetAnimationFlags();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ // 清理资源
+ stopToastTask();
+ }
+
+ @Override
+ public void onNetChange(int netModel) {
+
+ }
+
+ @Override
+ public void showError(String message) {
+ // 停止Toast任务
+ stopToastTask();
+
+ hideDialogLoading();
+ ToastUtil.showShort(mContext,message);
+ }
+
+ @Override
+ public void logOut(BaseBean bean) {
+
+ }
+
+ @Override
+ public void getAllCitySuccess(List list) {
+
+ }
+
+
+
+
+ private void getCurrentCityWeather(GeoPoiResponse response) {
+
+ }
+
+ @Override
+ public void onPermissionsGranted(int requestCode, @NonNull List perms) {
+ }
+
+ @Override
+ public void onPermissionsDenied(int requestCode, @NonNull List perms) {
+
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
+ hidePermissionDialog();
+ if(requestCode == REQUEST_EVENT_CODE_10002){
+ if(grantResults[0] == 0){
+ ToastUtil.showShort(mContext,"定位获取成功");
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/guide/GuideActivity.java b/app/src/main/java/com/ckkj/water/guide/GuideActivity.java
new file mode 100644
index 0000000..d730a4a
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/guide/GuideActivity.java
@@ -0,0 +1,236 @@
+package com.ckkj.water.guide;
+
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.AlphaAnimation;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.core.content.ContextCompat;
+import androidx.viewpager.widget.PagerAdapter;
+import androidx.viewpager.widget.ViewPager;
+
+import com.bumptech.glide.Glide;
+import com.ckkj.water.AppConfig;
+import com.ckkj.water.ConstantUtil;
+import com.ckkj.water.R;
+import com.ckkj.water.base.BaseActivity;
+import com.ckkj.water.databinding.GuideActivityLayoutBinding;
+import com.ckkj.water.login.entity.LoginEntity;
+import com.ckkj.water.login.mvp.LoginContract;
+import com.ckkj.water.login.mvp.LoginImpl;
+import com.ckkj.water.login.mvp.LoginPresenter;
+import com.ckkj.water.main.activity.MainActivity;
+import com.ckkj.water.network.BaseBean;
+import com.ckkj.water.utils.dataUtil.SharePreUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class GuideActivity extends BaseActivity
+ implements LoginContract.LoginView {
+
+ private List mImgUrls = new ArrayList<>();
+ private List mPointList = new ArrayList<>();
+ private boolean mHasEnterAppFlag = false;
+ private String desc[] = {"探索你的个性特征",
+ "一键生成,智能排盘工具",
+ "贴心提供每日生活指南"};
+
+ private int[] guideDrawableIds = {
+ R.mipmap.img_guide_1,
+ R.mipmap.img_guide_2,
+ R.mipmap.img_guide_3
+ };
+
+ @Override
+ public LoginPresenter createPresenter() {
+ return new LoginPresenter(new LoginImpl());
+ }
+
+ @Override
+ public void initData(Bundle savedInstanceState) {
+ super.initData(savedInstanceState);
+
+ for (int resId : guideDrawableIds) {
+ mImgUrls.add(ContextCompat.getDrawable(mContext, resId));
+ }
+
+ mHasEnterAppFlag = SharePreUtil.getBoolean(ConstantUtil.SHARE_PRE_NAME, mContext, ConstantUtil.FIRST_ENTER_APP_FLAG);
+
+ if (!mHasEnterAppFlag) {
+ viewBinding.viewpage.setAdapter(new MyAdapter());
+ viewBinding.viewpage.setPageTransformer(true, new CrossFadePageTransformer());
+ viewBinding.viewpage.setOffscreenPageLimit(1);
+
+ viewBinding.viewpage.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ setPointSelect(position);
+ if (position == mImgUrls.size() - 1) {
+ viewBinding.tvStart.setVisibility(View.VISIBLE);
+ AlphaAnimation fadeIn = new AlphaAnimation(0f, 1f);
+ fadeIn.setDuration(400);
+ viewBinding.tvStart.startAnimation(fadeIn);
+ } else {
+ viewBinding.tvStart.setVisibility(View.INVISIBLE);
+ }
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ }
+ });
+ }
+
+ viewBinding.tvStart.setOnClickListener(v -> {
+ SharePreUtil.putBoolean(ConstantUtil.SHARE_PRE_NAME, mContext, ConstantUtil.FIRST_ENTER_APP_FLAG, true);
+ Intent intent = new Intent(mContext, MainActivity.class);
+ startActivity(intent);
+ finish();
+ });
+ }
+
+
+ private void setPointSelect(int position) {
+ for (int i = 0; i < mPointList.size(); i++) {
+ mPointList.get(i).setImageResource(
+ i == position ? R.drawable.indicator_dot_selected : R.drawable.indicator_dot_normal);
+ }
+ }
+
+ private int dp2px(float dp) {
+ float density = getResources().getDisplayMetrics().density;
+ return Math.round(dp * density);
+ }
+
+ @Override
+ protected void initWidget() {
+ super.initWidget();
+ }
+
+ @Override
+ public void getPhoneCodeSuccess(BaseBean bean) {
+ }
+
+ @Override
+ public void loginBySmsCodeSuccess(LoginEntity entity) {
+ }
+
+ class MyAdapter extends PagerAdapter {
+ @Override
+ public int getCount() {
+ return mImgUrls.size();
+ }
+
+ @Override
+ public boolean isViewFromObject(@NonNull View view, @NonNull Object obj) {
+ return view == obj;
+ }
+
+ @NonNull
+ @Override
+ public Object instantiateItem(@NonNull ViewGroup container, int position) {
+ FrameLayout page = new FrameLayout(mContext);
+ page.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ ));
+
+ ImageView imageView = new ImageView(mContext);
+ imageView.setAdjustViewBounds(true);
+ imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
+
+ FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ Gravity.TOP | Gravity.CENTER_HORIZONTAL
+ );
+ imageView.setLayoutParams(lp);
+
+ Glide.with(mContext).load(mImgUrls.get(position)).into(imageView);
+
+ page.addView(imageView);
+ container.addView(page);
+ return page;
+ }
+
+ @Override
+ public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
+ container.removeView((View) object);
+ }
+ }
+
+ /**
+ * Cross-fade page transition: outgoing page fades out (alpha 1→0),
+ * incoming page fades in (alpha 0→1) simultaneously.
+ */
+ public class CrossFadePageTransformer implements ViewPager.PageTransformer {
+ private static final float MIN_ALPHA = 0.0f;
+
+ @Override
+ public void transformPage(@NonNull View page, float position) {
+ if (position < -1f) {
+ page.setAlpha(MIN_ALPHA);
+ } else if (position <= 0f) {
+ page.setAlpha(1f + position);
+ } else if (position <= 1f) {
+ page.setAlpha(1f - position);
+ } else {
+ page.setAlpha(MIN_ALPHA);
+ }
+ }
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.P)
+ @Override
+ public int getLayoutResId(Bundle savedInstanceState) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ hideStatusBar();
+ }
+ hideBottomUIMenu();
+ return R.layout.guide_activity_layout;
+ }
+
+ @Override
+ public void showError(String message) {
+ }
+
+ @Override
+ public void logOut(BaseBean bean) {
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.P)
+ protected void hideStatusBar() {
+ WindowManager.LayoutParams lp = getWindow().getAttributes();
+ lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+ getWindow().setAttributes(lp);
+ }
+
+ protected void hideBottomUIMenu() {
+ getWindow().getDecorView().setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+ if (Build.VERSION.SDK_INT > 11 && Build.VERSION.SDK_INT < 19) {
+ View v = this.getWindow().getDecorView();
+ v.setSystemUiVisibility(View.GONE);
+ } else if (Build.VERSION.SDK_INT >= 19) {
+ View decorView = getWindow().getDecorView();
+ int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+ decorView.setSystemUiVisibility(uiOptions);
+ }
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/guide/LaunchActivity.java b/app/src/main/java/com/ckkj/water/guide/LaunchActivity.java
new file mode 100644
index 0000000..b5d7cc5
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/guide/LaunchActivity.java
@@ -0,0 +1,156 @@
+package com.ckkj.water.guide;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.View;
+
+import com.ckkj.water.login.LoginActivity;
+import com.gyf.immersionbar.ImmersionBar;
+import com.ckkj.water.ConstantUtil;
+import com.ckkj.water.MyApplication;
+import com.ckkj.water.R;
+import com.ckkj.water.base.BaseActivity;
+import com.ckkj.water.base.entity.MineUserInfoBean;
+import com.ckkj.water.databinding.GuideLayoutBinding;
+import com.ckkj.water.main.MainContract;
+import com.ckkj.water.main.MainImpl;
+import com.ckkj.water.main.MainPresenter;
+import com.ckkj.water.main.activity.MainActivity;
+import com.ckkj.water.main.entity.CheckVersionEntity;
+import com.ckkj.water.mine.WebviewUserAgreement;
+import com.ckkj.water.network.BaseBean;
+import com.ckkj.water.utils.dataUtil.SharePreUtil;
+import com.ckkj.water.utils.manager.ActivityManager;
+
+import me.jessyan.autosize.internal.CancelAdapt;
+import me.jessyan.autosize.internal.CustomAdapt;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/7
+ * @description TODO: 启动页面
+ */
+
+public class LaunchActivity extends BaseActivity
+ implements MainContract.MainView, CancelAdapt {
+ private boolean mIsFirstEnterApp = false;
+
+ @Override
+ public void initData(Bundle savedInstanceState) {
+ super.initData(savedInstanceState);
+ viewBinding.includeTitle.imgBack.setVisibility(View.GONE);
+ viewBinding.includeTitle.tvTitle.setVisibility(View.GONE);
+ ImmersionBar.with(this)
+ .statusBarColor(R.color.white)
+ .statusBarDarkFont(true)
+ .titleBar(viewBinding.rlTitle) // 替换为你的标题栏View
+ .keyboardEnable(true)
+ .navigationBarDarkIcon(true)
+ .init();
+ mIsFirstEnterApp = SharePreUtil.getBooleanDefTrue(ConstantUtil.SHARE_PRE_NAME, mContext, ConstantUtil.FIRST_ENTER_APP_AGREEMENT_TIPS);
+ //首次进入,弹框提示隐私政策
+ if (mIsFirstEnterApp) {
+ //首次进入
+ showDialogtext("温馨提示", mContext.getResources().getString(R.string.tips_yinsizhengce_dialog),
+ "同意", "拒绝", "用户隐私协议", new OnDialogClick() {
+
+ @Override
+ public void onCancelClick() {
+ finish();
+ }
+
+ @Override
+ public void onConfirmClisk() {
+ //第一次进入App隐私弹窗是否显示
+ SharePreUtil.putBoolean(ConstantUtil.SHARE_PRE_NAME, mContext, ConstantUtil.FIRST_ENTER_APP_AGREEMENT_TIPS, false);
+ //用户是否同意了隐私政策
+ SharePreUtil.putBoolean(ConstantUtil.SHARE_PRE_NAME, mContext, ConstantUtil.PRIVACY_POLICY_AGREED, true);
+ MyApplication app = (MyApplication) getApplicationContext();
+ app.initializeUmengSDK();
+ goTo(mContext, GuideActivity.class, null);
+ finish();
+ }
+
+ @Override
+ public void onConfirmClisk(String content) {
+ }
+
+ @Override
+ public void onView1Click() {
+ Bundle bundle = new Bundle();
+ bundle.putInt("from_requse_code", WebviewUserAgreement.PRIVACY_CODE);
+ goToWihoutNoPresent(mContext, WebviewUserAgreement.class, bundle);
+ }
+ }, false, true);
+ } else {
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ Intent intent = new Intent(mContext, MainActivity.class);
+ startActivity(intent);
+ finish();
+ }
+ }, 2000);
+
+ }
+
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (dialog != null && dialog.isShowing()) {
+ dialog.dismiss();
+ }
+ ActivityManager.removeAllActivity();
+ finish();
+ }
+
+
+ @Override
+ public MainPresenter createPresenter() {
+ return new MainPresenter(new MainImpl());
+ }
+
+ @Override
+ public int getLayoutResId(Bundle savedInstanceState) {
+ return 0;
+ }
+
+ @Override
+ public void showError(String message) {
+
+ }
+
+ @Override
+ public void logOut(BaseBean bean) {
+
+ }
+
+
+ @Override
+ public void getAppVersionSuccess(CheckVersionEntity bean) {
+
+ }
+
+ @Override
+ public void activationVipSuccess(BaseBean bean) {
+
+ }
+
+ @Override
+ public void getVersionCheck(CheckVersionEntity entity) {
+
+ }
+
+ @Override
+ public void getYunshiDataSuccess(BaseBean bean) {
+
+ }
+
+ @Override
+ public void getUserInfoSuccess(MineUserInfoBean bean) {
+
+ }
+
+}
diff --git a/app/src/main/java/com/ckkj/water/history/HistoryFragment.java b/app/src/main/java/com/ckkj/water/history/HistoryFragment.java
new file mode 100644
index 0000000..b07f7ff
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/history/HistoryFragment.java
@@ -0,0 +1,99 @@
+package com.ckkj.water.history;
+
+import static android.view.View.INVISIBLE;
+
+import android.os.Bundle;
+import android.view.View;
+
+import androidx.fragment.app.Fragment;
+
+import com.ckkj.water.R;
+import com.ckkj.water.base.BaseFragment;
+import com.ckkj.water.databinding.HistoryFragmentLayoutBinding;
+import com.ckkj.water.history.mvp.HistoryContract;
+import com.ckkj.water.history.mvp.HistoryPresenter;
+import com.ckkj.water.network.BaseBean;
+import com.ckkj.water.plan.BindDeviceFragment;
+import com.ckkj.water.plan.WaterConfigFragment;
+import com.ckkj.water.plate.adapter.ViewPagerAdapter;
+import com.gyf.immersionbar.ImmersionBar;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 历史
+ */
+public class HistoryFragment extends BaseFragment
+ implements HistoryContract.HistoryView {
+
+ private List mFragmentList = new ArrayList<>();
+ @Override
+ public void initData(Bundle state) {
+ ImmersionBar.with(mContext)
+ .statusBarColor(R.color.white)
+ .statusBarDarkFont(true)
+ .titleBar(viewBinding.rlTitle)
+ .init();
+ viewBinding.includedTitle.imgBack.setVisibility(INVISIBLE);
+ viewBinding.includedTitle.tvTitle.setText(getString(R.string.history_title));
+ mFragmentList.add(new TabHistoryFragment());
+ mFragmentList.add(new StatisticFragment());
+ ViewPagerAdapter adapter = new ViewPagerAdapter(this, mFragmentList);
+ viewBinding.viewpage.setAdapter(adapter);
+ viewBinding.viewpage.setCurrentItem(0);
+ //viewpage2 特有的 api,禁止滑动
+ viewBinding.viewpage.setUserInputEnabled(false);
+
+ viewBinding.tvHistory.setSelected(true);
+ viewBinding.tvData.setSelected(false);
+ initListener();
+
+ }
+
+ private void initListener() {
+ setClick(viewBinding.tvHistory);
+ setClick(viewBinding.tvData);
+ }
+
+ private void setClick(View view) {
+ view.setOnClickListener(this::onClick);
+ }
+
+ public void onClick(View v){
+ switch (v.getId()){
+ case R.id.tv_history:
+ viewBinding.tvHistory.setSelected(true);
+ viewBinding.tvData.setSelected(false);
+ viewBinding.viewpage.setCurrentItem(0);
+ break;
+
+ case R.id.tv_data:
+ viewBinding.tvHistory.setSelected(false);
+ viewBinding.tvData.setSelected(true);
+ viewBinding.viewpage.setCurrentItem(1);
+ break;
+ }
+ }
+
+ @Override
+ public HistoryPresenter createPresenter() {
+ return null;
+ }
+
+
+ @Override
+ public void onNetChange(int netModel) {
+
+ }
+
+ @Override
+ public void showError(String message) {
+
+ }
+
+ @Override
+ public void logOut(BaseBean bean) {
+
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/history/StatisticFragment.java b/app/src/main/java/com/ckkj/water/history/StatisticFragment.java
new file mode 100644
index 0000000..39dee96
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/history/StatisticFragment.java
@@ -0,0 +1,42 @@
+package com.ckkj.water.history;
+
+import android.os.Bundle;
+
+import com.ckkj.water.base.BaseFragment;
+import com.ckkj.water.databinding.WaterCountFragmentLayoutBinding;
+import com.ckkj.water.network.BaseBean;
+import com.ckkj.water.plan.mvp.WaterPlanContract;
+import com.ckkj.water.plan.mvp.WaterPlanPresenter;
+
+/**
+ * @author fengxiaoyang
+ * @date 2026/5/16
+ * @description TODO: 描述该类的作用
+ */
+public class StatisticFragment extends BaseFragment
+ implements WaterPlanContract.WaterPlanView {
+ @Override
+ public void initData(Bundle state) {
+
+ }
+
+ @Override
+ public WaterPlanPresenter createPresenter() {
+ return null;
+ }
+
+ @Override
+ public void onNetChange(int netModel) {
+
+ }
+
+ @Override
+ public void showError(String message) {
+
+ }
+
+ @Override
+ public void logOut(BaseBean bean) {
+
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/history/TabHistoryFragment.java b/app/src/main/java/com/ckkj/water/history/TabHistoryFragment.java
new file mode 100644
index 0000000..9ed4e4c
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/history/TabHistoryFragment.java
@@ -0,0 +1,159 @@
+package com.ckkj.water.history;
+
+import android.os.Bundle;
+import android.view.View;
+
+import com.chad.library.adapter.base.BaseQuickAdapter;
+import com.ckkj.water.R;
+import com.ckkj.water.base.BaseFragment;
+import com.ckkj.water.custom.CustomTimeFilterPopupView;
+import com.ckkj.water.custom.CustomTypeFilterPopupView;
+import com.ckkj.water.databinding.TabHistoryFragmentLayoutBinding;
+import com.ckkj.water.history.adapter.WateringHistoryAdapter;
+import com.ckkj.water.history.entity.WateringRecord;
+import com.ckkj.water.history.mvp.HistoryContract;
+import com.ckkj.water.history.mvp.HistoryPresenter;
+import com.ckkj.water.network.BaseBean;
+import com.ckkj.water.plan.entity.WaterPlanWeekEntity;
+import com.ckkj.water.utils.show.ToastUtil;
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import com.lxj.xpopup.XPopup;
+import com.lxj.xpopup.core.BasePopupView;
+import com.lxj.xpopup.interfaces.SimpleCallback;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author fengxiaoyang
+ * @date 2026/5/14
+ * @description TODO: 描述该类的作用
+ */
+public class TabHistoryFragment extends BaseFragment
+ implements HistoryContract.HistoryView {
+
+ List recordList = new ArrayList<>();
+ private WateringHistoryAdapter mAdapter;
+ @Override
+ public void initData(Bundle state) {
+ String jsonContent = getString(R.string.watering_device_list_json);
+ Type type = new TypeToken>() {}.getType();
+ recordList = new Gson().fromJson(jsonContent, type);
+ mAdapter = new WateringHistoryAdapter(mContext,R.layout.tab_history_item_layout,recordList);
+ viewBinding.rvHisttory.setAdapter(mAdapter);
+ initListener();
+ }
+
+ private void initListener() {
+ setClick(viewBinding.linearTimeFilter);
+ setClick(viewBinding.linearTypeFilter);
+ mAdapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
+ @Override
+ public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {
+ switch (view.getId()){
+ case R.id.img_delete:
+ showDialogtext(null, false, false, "提示",
+ "确定删除该记录", "取消", "删除",
+ new OnDialogClick() {
+ @Override
+ public void onCancelClick() {
+ }
+
+ @Override
+ public void onConfirmClick() {
+ mAdapter.remove(position);
+ }
+ });
+ break;
+ }
+ }
+ });
+ }
+
+ private void setClick(View view) {
+ view.setOnClickListener(this::onClick);
+ }
+
+ public void onClick(View v){
+ switch (v.getId()){
+ case R.id.linear_time_filter:
+ CustomTimeFilterPopupView popupView = (CustomTimeFilterPopupView) new XPopup.Builder(getContext())
+ .isViewMode(true)
+ .isTouchThrough(false)
+ .isDestroyOnDismiss(true)
+ .dismissOnTouchOutside(true)
+ .atView(v)
+ .setPopupCallback(new SimpleCallback() {
+ @Override
+ public void onShow(BasePopupView popupView) {
+ viewBinding.tvTimeFilter.setTextColor(mContext.getColor(R.color.color_zhu));
+ viewBinding.imgTimeFilter.setImageResource(R.mipmap.icon_green_triangle_up);
+ }
+ @Override
+ public void onDismiss(BasePopupView popupView) {
+ viewBinding.tvTimeFilter.setTextColor(mContext.getColor(R.color.color_222222));
+ viewBinding.imgTimeFilter.setImageResource(R.mipmap.icon_triangle_down);
+ }
+ })
+ .asCustom(new CustomTimeFilterPopupView(getContext(), new CustomTimeFilterPopupView.OnClickListener() {
+ @Override
+ public void callBack(String startTime, String endTime) {
+ ToastUtil.showShort(mContext,startTime + "---" + endTime);
+ }
+ }));
+ popupView.show();
+ break;
+ case R.id.linear_type_filter:
+
+ CustomTypeFilterPopupView typePopupView = (CustomTypeFilterPopupView) new XPopup.Builder(getContext())
+ .isViewMode(true)
+ .isTouchThrough(false)
+ .isDestroyOnDismiss(true)
+ .dismissOnTouchOutside(true)
+ .atView(v)
+ .setPopupCallback(new SimpleCallback() {
+ @Override
+ public void onShow(BasePopupView popupView) {
+ viewBinding.tvTypeFilter.setTextColor(mContext.getColor(R.color.color_zhu));
+ viewBinding.imgTypeFilter.setImageResource(R.mipmap.icon_green_triangle_up);
+ }
+ @Override
+ public void onDismiss(BasePopupView popupView) {
+ viewBinding.tvTypeFilter.setTextColor(mContext.getColor(R.color.color_222222));
+ viewBinding.imgTypeFilter.setImageResource(R.mipmap.icon_triangle_down);
+ }
+ })
+ .asCustom(new CustomTypeFilterPopupView(getContext(), new CustomTypeFilterPopupView.OnClickListener() {
+ @Override
+ public void callBack(boolean mSelManual, boolean mSelAuto) {
+
+ }
+ }));
+ typePopupView.show();
+
+ break;
+ }
+ }
+
+ @Override
+ public HistoryPresenter createPresenter() {
+ return null;
+ }
+
+ @Override
+ public void onNetChange(int netModel) {
+
+ }
+
+ @Override
+ public void showError(String message) {
+
+ }
+
+ @Override
+ public void logOut(BaseBean bean) {
+
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/history/adapter/WateringHistoryAdapter.java b/app/src/main/java/com/ckkj/water/history/adapter/WateringHistoryAdapter.java
new file mode 100644
index 0000000..72bd9c9
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/history/adapter/WateringHistoryAdapter.java
@@ -0,0 +1,38 @@
+package com.ckkj.water.history.adapter;
+
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+
+import com.chad.library.adapter.base.BaseQuickAdapter;
+import com.chad.library.adapter.base.BaseViewHolder;
+import com.ckkj.water.R;
+import com.ckkj.water.history.entity.WateringRecord;
+
+import java.util.List;
+
+/**
+ * @author fengxiaoyang
+ * @date 2026/5/15
+ * @description TODO: 描述该类的作用
+ */
+public class WateringHistoryAdapter extends BaseQuickAdapter {
+ private Context mContext;
+ public WateringHistoryAdapter(Context context,int layoutResId, @Nullable List data) {
+ super(layoutResId, data);
+ this.mContext = context;
+ }
+
+ @Override
+ protected void convert(BaseViewHolder helper, WateringRecord item) {
+ helper.setText(R.id.tv_device_name,item.getDeviceName());
+ helper.setText(R.id.tv_device_serial,item.getDeviceCode());
+ helper.setText(R.id.tv_plan_time,mContext.getString(R.string.history_watering_adapter_time)
+ + ":" + item.getStartTime() + "-" + item.getEndTime());
+ helper.setText(R.id.tv_plan_duration,mContext.getString(R.string.device_work_times_title)
+ + ":" + item.getDuration());
+ helper.setText(R.id.tv_plan_type,mContext.getString(R.string.tab_filter_type)
+ + ":" + item.getTriggerType());
+ helper.addOnClickListener(R.id.img_delete);
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/history/entity/WateringRecord.java b/app/src/main/java/com/ckkj/water/history/entity/WateringRecord.java
new file mode 100644
index 0000000..fdcec76
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/history/entity/WateringRecord.java
@@ -0,0 +1,32 @@
+package com.ckkj.water.history.entity;
+
+import java.io.Serializable;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author fengxiaoyang
+ * @date 2026/5/15
+ * @description TODO: 描述该类的作用
+ */
+@NoArgsConstructor
+@Data
+public class WateringRecord implements Serializable {
+
+ /**
+ * deviceName : 智能浇水设备A1
+ * deviceCode : YA226641000107
+ * startTime : 2026-10-10 16:00
+ * endTime : 2026-10-10 16:30
+ * duration : 30min
+ * triggerType : 排程
+ */
+
+ private String deviceName;
+ private String deviceCode;
+ private String startTime;
+ private String endTime;
+ private String duration;
+ private String triggerType;
+}
diff --git a/app/src/main/java/com/ckkj/water/history/mvp/HistoryContract.java b/app/src/main/java/com/ckkj/water/history/mvp/HistoryContract.java
new file mode 100644
index 0000000..e4e5126
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/history/mvp/HistoryContract.java
@@ -0,0 +1,16 @@
+package com.ckkj.water.history.mvp;
+
+import com.ckkj.water.base.IView;
+
+public class HistoryContract {
+ public interface CallBack {
+ void success(T msg);
+ void fail(String msg);
+ }
+ public interface DataModel{
+
+ }
+
+ public interface HistoryView extends IView {
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/history/mvp/HistoryImpl.java b/app/src/main/java/com/ckkj/water/history/mvp/HistoryImpl.java
new file mode 100644
index 0000000..7b017a5
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/history/mvp/HistoryImpl.java
@@ -0,0 +1,5 @@
+package com.ckkj.water.history.mvp;
+
+public class HistoryImpl implements HistoryContract.DataModel{
+
+}
diff --git a/app/src/main/java/com/ckkj/water/history/mvp/HistoryPresenter.java b/app/src/main/java/com/ckkj/water/history/mvp/HistoryPresenter.java
new file mode 100644
index 0000000..d22704c
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/history/mvp/HistoryPresenter.java
@@ -0,0 +1,11 @@
+package com.ckkj.water.history.mvp;
+
+import com.ckkj.water.base.BasePresenter;
+
+public class HistoryPresenter extends BasePresenter {
+ private HistoryImpl impl;
+
+ public HistoryPresenter(HistoryImpl impl) {
+ this.impl = impl;
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/index/IndexFragment.java b/app/src/main/java/com/ckkj/water/index/IndexFragment.java
new file mode 100644
index 0000000..9e76ae6
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/index/IndexFragment.java
@@ -0,0 +1,151 @@
+package com.ckkj.water.index;
+
+import android.Manifest;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.ckkj.water.R;
+import com.ckkj.water.base.BaseFragment;
+import com.ckkj.water.databinding.IndexFragmentLayoutBinding;
+import com.ckkj.water.device.AddDeviceActivity;
+import com.ckkj.water.device.DeviceControlActivity;
+import com.ckkj.water.index.adapter.DeviceAdapter;
+import com.ckkj.water.index.entity.IndexDeviceEntity;
+import com.ckkj.water.index.mvp.IndexContract;
+import com.ckkj.water.index.mvp.IndexImpl;
+import com.ckkj.water.index.mvp.IndexPresenter;
+import com.ckkj.water.network.BaseBean;
+import com.ckkj.water.utils.show.ToastUtil;
+import com.maning.mlkitscanner.scan.MNScanManager;
+import com.maning.mlkitscanner.scan.callback.act.MNScanCallback;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import pub.devrel.easypermissions.EasyPermissions;
+
+public class IndexFragment extends BaseFragment
+ implements IndexContract.IndexView , EasyPermissions.PermissionCallbacks{
+
+ private DeviceAdapter deviceAdapter;
+ private List devList = new ArrayList<>();
+
+ @Override
+ protected void initArgs(Bundle bundle) {
+ }
+
+ @Override
+ public void initData(@Nullable Bundle savedInstanceState) {
+ IndexDeviceEntity entity = new IndexDeviceEntity("智能浇水设备A1","YA226641000101","工作中",100);
+ IndexDeviceEntity entity2 = new IndexDeviceEntity("智能浇水设备B1","YA22664100000","工作中",80);
+ IndexDeviceEntity entity3 = new IndexDeviceEntity("智能浇水设备C5","YA234534000101","工作中",70);
+ IndexDeviceEntity entity4 = new IndexDeviceEntity("智能浇水设备D6","YA2266678543101","工作中",50);
+ devList.add(entity);
+ devList.add(entity2);
+ devList.add(entity3);
+ devList.add(entity4);
+ }
+
+ @Override
+ public IndexPresenter createPresenter() {
+ return new IndexPresenter(new IndexImpl());
+ }
+
+ @Override
+ protected void initView() {
+ super.initView();
+ setupRecyclerView();
+ initListener();
+ }
+
+ private void initListener() {
+ setClick(viewBinding.imgScan);
+ setClick(viewBinding.imgAdd);
+ }
+
+ private void setClick(View view) {
+ view.setOnClickListener(this::onClick);
+ }
+
+ public void onClick(View v){
+ switch (v.getId()){
+
+ case R.id.img_scan:
+ //检测有无相机权限
+ String readPermission = Manifest.permission.CAMERA;
+ if(!hasRecordPermission(readPermission)){
+ getPermissionRequst(readPermission,"权限申请",
+ "智能浇水需要您的授权访问相机,并使用扫一扫",REQUEST_EVENT_CODE_10001);
+ }else{
+ scanQrcode();
+ }
+ break;
+
+ case R.id.img_add:
+ goTo(mContext, AddDeviceActivity.class,null);
+ break;
+ }
+ }
+
+ private void scanQrcode() {
+ //默认扫描
+ MNScanManager.startScan(mContext, new MNScanCallback() {
+ @Override
+ public void onActivityResult(int resultCode, Intent data) {
+ switch (resultCode) {
+ case MNScanManager.RESULT_SUCCESS:
+ ArrayList results = data.getStringArrayListExtra(MNScanManager.INTENT_KEY_RESULT_SUCCESS);
+ if(results != null){
+ ToastUtil.showShort(mContext,results.get(0).toString());
+ }
+ break;
+ case MNScanManager.RESULT_FAIL:
+ String resultError = data.getStringExtra(MNScanManager.INTENT_KEY_RESULT_ERROR);
+ break;
+ case MNScanManager.RESULT_CANCLE:
+ ToastUtil.showShort(mContext,"取消扫码");
+ break;
+ }
+ }
+ });
+ }
+
+ private void setupRecyclerView() {
+ deviceAdapter = new DeviceAdapter(mContext,R.layout.index_device_list_item,devList);
+ viewBinding.rvDevices.setAdapter(deviceAdapter);
+ deviceAdapter.setOnItemClickListener((adapter, view, position) -> {
+ goTo(mContext, DeviceControlActivity.class,null);
+ });
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ }
+
+ @Override
+ public void onNetChange(int netModel) {
+ }
+
+ @Override
+ public void showError(String message) {
+ }
+
+ @Override
+ public void logOut(BaseBean bean) {
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
+ hidePermissionDialog();
+ if (requestCode == REQUEST_EVENT_CODE_10001) {
+ scanQrcode();
+ }
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/index/adapter/DeviceAdapter.java b/app/src/main/java/com/ckkj/water/index/adapter/DeviceAdapter.java
new file mode 100644
index 0000000..896d6d4
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/index/adapter/DeviceAdapter.java
@@ -0,0 +1,76 @@
+package com.ckkj.water.index.adapter;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.chad.library.adapter.base.BaseQuickAdapter;
+import com.chad.library.adapter.base.BaseViewHolder;
+import com.ckkj.water.R;
+import com.ckkj.water.index.entity.IndexDeviceEntity;
+
+import java.util.List;
+
+public class DeviceAdapter extends BaseQuickAdapter {
+
+ public DeviceAdapter(Context context, int layoutResId, @Nullable List data) {
+ super(layoutResId, data);
+ this.mContext = context;
+ this.mData = data;
+ }
+
+ @Override
+ protected void convert(BaseViewHolder holder, IndexDeviceEntity item) {
+ holder.setText(R.id.tv_device_name,item.getDeviceNames());
+ holder.setText(R.id.tv_device_serial,item.getDeviceSerials());
+ holder.setText(R.id.tv_device_status,item.getDeviceStatuses());
+ holder.setText(R.id.tv_battery_level,item.getBatteryLevels() + "");
+ }
+
+
+// public DeviceAdapter() {
+// super();
+// }
+//
+// @Override
+// protected void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull String data) {
+// holder.tvDeviceName.setText(deviceNames[position]);
+// holder.tvDeviceSerial.setText(deviceSerials[position]);
+// holder.tvDeviceStatus.setText(deviceStatuses[position]);
+// holder.tvBatteryLevel.setText(batteryLevels[position] + "%");
+// }
+//
+// @NonNull
+// @Override
+// public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+// View view = LayoutInflater.from(parent.getContext())
+// .inflate(R.layout.index_device_list_item, parent, false);
+// return new ViewHolder(view);
+// }
+//
+// @Override
+// protected void convert(ViewHolder helper, String item) {
+//
+// }
+//
+// static class ViewHolder extends RecyclerView.ViewHolder {
+// TextView tvDeviceName;
+// TextView tvDeviceSerial;
+// TextView tvDeviceStatus;
+// TextView tvBatteryLevel;
+//
+// public ViewHolder(@NonNull View itemView) {
+// super(itemView);
+// tvDeviceName = itemView.findViewById(R.id.tv_device_name);
+// tvDeviceSerial = itemView.findViewById(R.id.tv_device_serial);
+// tvDeviceStatus = itemView.findViewById(R.id.tv_device_status);
+// tvBatteryLevel = itemView.findViewById(R.id.tv_battery_level);
+// }
+// }
+}
diff --git a/app/src/main/java/com/ckkj/water/index/entity/IndexDeviceEntity.java b/app/src/main/java/com/ckkj/water/index/entity/IndexDeviceEntity.java
new file mode 100644
index 0000000..4c0f463
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/index/entity/IndexDeviceEntity.java
@@ -0,0 +1,49 @@
+package com.ckkj.water.index.entity;
+
+import java.io.Serializable;
+
+public class IndexDeviceEntity implements Serializable {
+ private String deviceNames;
+ private String deviceSerials;
+ private String deviceStatuses;
+ private int batteryLevels;
+
+ public IndexDeviceEntity(String deviceNames, String deviceSerials, String deviceStatuses, int batteryLevels) {
+ this.deviceNames = deviceNames;
+ this.deviceSerials = deviceSerials;
+ this.deviceStatuses = deviceStatuses;
+ this.batteryLevels = batteryLevels;
+ }
+
+ public String getDeviceNames() {
+ return deviceNames;
+ }
+
+ public void setDeviceNames(String deviceNames) {
+ this.deviceNames = deviceNames;
+ }
+
+ public String getDeviceSerials() {
+ return deviceSerials;
+ }
+
+ public void setDeviceSerials(String deviceSerials) {
+ this.deviceSerials = deviceSerials;
+ }
+
+ public String getDeviceStatuses() {
+ return deviceStatuses;
+ }
+
+ public void setDeviceStatuses(String deviceStatuses) {
+ this.deviceStatuses = deviceStatuses;
+ }
+
+ public int getBatteryLevels() {
+ return batteryLevels;
+ }
+
+ public void setBatteryLevels(int batteryLevels) {
+ this.batteryLevels = batteryLevels;
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/index/mvp/IndexContract.java b/app/src/main/java/com/ckkj/water/index/mvp/IndexContract.java
new file mode 100644
index 0000000..1d83585
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/index/mvp/IndexContract.java
@@ -0,0 +1,25 @@
+package com.ckkj.water.index.mvp;
+
+import com.ckkj.water.base.IView;
+import com.ckkj.water.login.entity.LoginEntity;
+import com.ckkj.water.network.BaseBean;
+
+import org.json.JSONObject;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/26
+ * @description TODO: 描述该类的作用
+ */
+
+public class IndexContract {
+ public interface CallBack {
+ void success(T msg);
+ void fail(String msg);
+ }
+ public interface DataModel{
+ }
+
+ public interface IndexView extends IView{
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/index/mvp/IndexImpl.java b/app/src/main/java/com/ckkj/water/index/mvp/IndexImpl.java
new file mode 100644
index 0000000..b64cd9f
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/index/mvp/IndexImpl.java
@@ -0,0 +1,5 @@
+package com.ckkj.water.index.mvp;
+
+public class IndexImpl implements IndexContract.DataModel{
+
+}
diff --git a/app/src/main/java/com/ckkj/water/index/mvp/IndexPresenter.java b/app/src/main/java/com/ckkj/water/index/mvp/IndexPresenter.java
new file mode 100644
index 0000000..145d057
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/index/mvp/IndexPresenter.java
@@ -0,0 +1,15 @@
+package com.ckkj.water.index.mvp;
+
+import com.ckkj.water.base.BasePresenter;
+import com.ckkj.water.login.mvp.LoginImpl;
+import com.ckkj.water.network.BaseBean;
+
+public class IndexPresenter extends BasePresenter{
+
+ private IndexImpl mImpl;
+
+ public IndexPresenter(IndexImpl mImpl) {
+ this.mImpl = mImpl;
+ }
+
+}
diff --git a/app/src/main/java/com/ckkj/water/login/ForgetPasswordActivity.java b/app/src/main/java/com/ckkj/water/login/ForgetPasswordActivity.java
new file mode 100644
index 0000000..1e57de0
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/login/ForgetPasswordActivity.java
@@ -0,0 +1,125 @@
+package com.ckkj.water.login;
+
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.text.TextUtils;
+import android.view.View;
+
+import com.ckkj.water.R;
+import com.ckkj.water.base.BaseActivity;
+import com.ckkj.water.databinding.RegisterLayoutBinding;
+import com.ckkj.water.login.entity.LoginEntity;
+import com.ckkj.water.login.mvp.LoginContract;
+import com.ckkj.water.login.mvp.LoginImpl;
+import com.ckkj.water.login.mvp.LoginPresenter;
+import com.ckkj.water.network.BaseBean;
+import com.ckkj.water.utils.number.PhoneNumberUtils;
+import com.ckkj.water.utils.show.ToastUtil;
+import com.gyf.immersionbar.ImmersionBar;
+
+/**
+ * 重置密码
+ */
+public class ForgetPasswordActivity extends BaseActivity
+ implements LoginContract.LoginView {
+ @Override
+ public LoginPresenter createPresenter() {
+ return new LoginPresenter(new LoginImpl());
+ }
+
+ @Override
+ public int getLayoutResId(Bundle savedInstanceState) {
+ return 0;
+ }
+
+ @Override
+ public void initData(Bundle savedInstanceState) {
+ super.initData(savedInstanceState);
+ ImmersionBar.with(this)
+ .statusBarColor(R.color.color_F9F9F9) // 设置状态栏颜色
+ .statusBarDarkFont(true) // 设置状态栏字体为黑色
+ .titleBar(viewBinding.rlTitle)
+ .init();
+ viewBinding.includedTitle.tvTitle.setText(R.string.reset_psd_title);
+ viewBinding.tvRegisterResetpsd.setText(R.string.reset_psd_title);
+ initListener();
+ }
+
+ private void initListener() {
+ setClick(viewBinding.includedTitle.imgBack);
+ setClick(viewBinding.tvGetVerifiCode);
+ setClick(viewBinding.tvCommit);
+ }
+
+ private void setClick(View view) {
+ view.setOnClickListener(this::onClick);
+ }
+
+ public void onClick(View v){
+ switch (v.getId()){
+ case R.id.img_back:
+ finish();
+ break;
+
+ case R.id.tv_get_verifi_code:
+ if (TextUtils.isEmpty(viewBinding.etAccount.getText().toString())) {
+ ToastUtil.showShort(mContext, getString(R.string.login_error_phone_empty));
+ return;
+ }
+ if (!PhoneNumberUtils.checkPhoneNum(viewBinding.etAccount.getText().toString().trim()) &&
+ !PhoneNumberUtils.checkEmail(viewBinding.etAccount.getText().toString().trim())) {
+ ToastUtil.showShort(mContext, getString(R.string.login_error_phone_invalid));
+ return;
+ }
+ cdTimer.start();
+ break;
+
+ case R.id.tv_commit:
+ goTo(mContext, RegisterSuccessActivity.class,null);
+ break;
+ }
+ }
+
+
+ private CountDownTimer cdTimer = new CountDownTimer(6000, 1000) {
+ @Override
+ public void onTick(long millisUntilFinished) {
+ viewBinding.tvGetVerifiCode.setText("(" + millisUntilFinished / 1000 + ") " + getString(R.string.login_resend));
+ viewBinding.tvGetVerifiCode.setSelected(true);
+ viewBinding.tvGetVerifiCode.setClickable(false);
+ viewBinding.tvGetVerifiCode.setTextColor(mContext.getColor(R.color.color_c8c8c8));
+ viewBinding.tvGetVerifiCode.setBackground(mContext.getResources().getDrawable(R.drawable.shape_gray_50));
+ }
+
+ @Override
+ public void onFinish() {
+ viewBinding.tvGetVerifiCode.setText(getString(R.string.login_resend));
+ viewBinding.tvGetVerifiCode.setSelected(false);
+ viewBinding.tvGetVerifiCode.setClickable(true);
+ viewBinding.tvGetVerifiCode.setTextColor(mContext.getColor(R.color.white));
+ viewBinding.tvGetVerifiCode.setBackground(mContext.getResources().getDrawable(R.drawable.shape_green_50));
+ }
+ };
+
+
+ @Override
+ public void getPhoneCodeSuccess(BaseBean bean) {
+
+ }
+
+ @Override
+ public void loginBySmsCodeSuccess(LoginEntity entity) {
+
+ }
+
+ @Override
+ public void showError(String message) {
+ ToastUtil.showShort(mContext,message);
+ }
+
+ @Override
+ public void logOut(BaseBean bean) {
+
+ }
+
+}
diff --git a/app/src/main/java/com/ckkj/water/login/LoginActivity.java b/app/src/main/java/com/ckkj/water/login/LoginActivity.java
new file mode 100644
index 0000000..222d9b8
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/login/LoginActivity.java
@@ -0,0 +1,313 @@
+package com.ckkj.water.login;
+
+
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
+import android.graphics.Typeface;
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.os.Handler;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.text.method.LinkMovementMethod;
+import android.text.style.ClickableSpan;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.core.content.ContextCompat;
+
+import com.ckkj.water.custom.view.PhotoViewActivity;
+import com.ckkj.water.utils.DownFileUtil;
+import com.google.gson.Gson;
+import com.gyf.immersionbar.ImmersionBar;
+import com.ckkj.water.AppConfig;
+import com.ckkj.water.ConstantUtil;
+import com.ckkj.water.R;
+import com.ckkj.water.base.BaseActivity;
+import com.ckkj.water.databinding.LoginLayoutBinding;
+import com.ckkj.water.login.entity.LoginEntity;
+import com.ckkj.water.login.mvp.LoginContract;
+import com.ckkj.water.login.mvp.LoginImpl;
+import com.ckkj.water.login.mvp.LoginPresenter;
+import com.ckkj.water.main.activity.MainActivity;
+import com.ckkj.water.mine.WebviewUserAgreement;
+import com.ckkj.water.network.BaseBean;
+import com.ckkj.water.utils.click.ClickUtil;
+import com.ckkj.water.utils.dataUtil.SharePreUtil;
+import com.ckkj.water.utils.manager.MingDengUserInfoManager;
+import com.ckkj.water.utils.number.PhoneNumberUtils;
+import com.ckkj.water.utils.show.ToastUtil;
+import com.lxj.xpopup.XPopup;
+import com.lxj.xpopup.interfaces.OnSelectListener;
+
+
+public class LoginActivity extends BaseActivity
+ implements LoginContract.LoginView {
+
+ private boolean mYinsiConfirm = false;
+ //当前登录方式 验证码100 密码 200
+ private static int LOGIN_TYPE_STATUS_CODE = 100;
+
+ @Override
+ public LoginPresenter createPresenter() {
+ return new LoginPresenter(new LoginImpl());
+ }
+
+ @Override
+ public void initData(Bundle savedInstanceState) {
+ super.initData(savedInstanceState);
+
+ ImmersionBar.with(this)
+ .statusBarColor(R.color.white)
+ .statusBarDarkFont(true)
+ .init();
+
+ setupAgreementText();
+
+ initListener();
+ }
+
+ private void initListener() {
+ setClick(viewBinding.tabCode);
+ setClick(viewBinding.tabPsd);
+ setClick(viewBinding.tvGetVerifiCode);
+ setClick(viewBinding.tvLogin);
+ setClick(viewBinding.tvGoRegister);
+ setClick(viewBinding.tvLanguage);
+ setClick(viewBinding.tvResetPsd);
+ }
+
+ private void setClick(View view) {
+ view.setOnClickListener(this::onClick);
+ }
+
+ private void setupAgreementText() {
+ String prefix = getString(R.string.login_agreement_prefix);
+ String privacy = getString(R.string.login_privacy_policy);
+ String and = getString(R.string.login_and);
+ String userAgreement = getString(R.string.login_user_agreement);
+ String content = prefix + privacy + and + userAgreement;
+
+ SpannableString spannableString = new SpannableString(content);
+
+ int privacyStart = prefix.length();
+ int privacyEnd = privacyStart + privacy.length();
+ ClickableSpan privacyPolicySpan = new ClickableSpan() {
+ @Override
+ public void onClick(@NonNull View widget) {
+ Bundle bundle = new Bundle();
+ bundle.putInt("from_requse_code", WebviewUserAgreement.PRIVACY_CODE);
+ goToWihoutNoPresent(mContext, WebviewUserAgreement.class, bundle);
+ }
+
+ @Override
+ public void updateDrawState(@NonNull TextPaint ds) {
+ super.updateDrawState(ds);
+ ds.setColor(ContextCompat.getColor(mContext, R.color.color_zhu));
+ ds.setUnderlineText(false);
+ }
+ };
+ spannableString.setSpan(privacyPolicySpan, privacyStart, privacyEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ int userStart = privacyEnd + and.length();
+ int userEnd = userStart + userAgreement.length();
+ ClickableSpan userAgreementSpan = new ClickableSpan() {
+ @Override
+ public void onClick(@NonNull View widget) {
+ Bundle bundle = new Bundle();
+ bundle.putInt("from_requse_code", WebviewUserAgreement.AGREEMENT_CODE);
+ goToWihoutNoPresent(mContext, WebviewUserAgreement.class, bundle);
+ }
+
+ @Override
+ public void updateDrawState(@NonNull TextPaint ds) {
+ super.updateDrawState(ds);
+ ds.setColor(ContextCompat.getColor(mContext, R.color.color_zhu));
+ ds.setUnderlineText(false);
+ }
+ };
+ spannableString.setSpan(userAgreementSpan, userStart, userEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+// viewBinding.tvAgreement.setText(spannableString);
+// viewBinding.tvAgreement.setMovementMethod(LinkMovementMethod.getInstance());
+ }
+
+ public void onClick(View v) {
+ switch (v.getId()) {
+
+ case R.id.tv_go_register:
+ goTo(mContext, RegisterActivity.class,null);
+ break;
+
+ case R.id.tab_code://验证码登录
+ LOGIN_TYPE_STATUS_CODE = 100;
+ viewBinding.tvLoginByCode.setTextColor(mContext.getResources().getColor(R.color.color_222222));
+ viewBinding.tvLoginByCode.setTypeface(Typeface.DEFAULT_BOLD);
+ viewBinding.viewLineCode.setVisibility(VISIBLE);
+
+ viewBinding.tvLoginByPsd.setTextColor(mContext.getResources().getColor(R.color.color_999999));
+ viewBinding.tvLoginByPsd.setTypeface(Typeface.DEFAULT);
+ viewBinding.viewLinePsd.setVisibility(GONE);
+
+ viewBinding.linearVerfiCode.setVisibility(VISIBLE);
+ viewBinding.linearPsd.setVisibility(GONE);
+ viewBinding.tvResetPsd.setVisibility(GONE);
+ break;
+
+ case R.id.tab_psd://密码登录
+ LOGIN_TYPE_STATUS_CODE = 200;
+ viewBinding.tvLoginByPsd.setTextColor(mContext.getResources().getColor(R.color.color_222222));
+ viewBinding.tvLoginByPsd.setTypeface(Typeface.DEFAULT_BOLD);
+ viewBinding.viewLinePsd.setVisibility(VISIBLE);
+
+ viewBinding.tvLoginByCode.setTextColor(mContext.getResources().getColor(R.color.color_999999));
+ viewBinding.tvLoginByCode.setTypeface(Typeface.DEFAULT);
+ viewBinding.viewLineCode.setVisibility(GONE);
+
+ viewBinding.linearVerfiCode.setVisibility(GONE);
+ viewBinding.linearPsd.setVisibility(VISIBLE);
+ viewBinding.tvResetPsd.setVisibility(VISIBLE);
+ break;
+
+ case R.id.tv_get_verifi_code:
+ if (TextUtils.isEmpty(viewBinding.etAccount.getText().toString())) {
+ ToastUtil.showShort(mContext, getString(R.string.login_error_phone_empty));
+ return;
+ }
+ if (!PhoneNumberUtils.checkPhoneNum(viewBinding.etAccount.getText().toString().trim()) &&
+ !PhoneNumberUtils.checkEmail(viewBinding.etAccount.getText().toString().trim())) {
+ ToastUtil.showShort(mContext, getString(R.string.login_error_phone_invalid));
+ return;
+ }
+ cdTimer.start();
+// showLoadingDialog();
+// mPresenter.getPhoneCode(
+// viewBinding.edPhoneInput.getText().toString().trim(),
+// AppConfig.SMS_CODE_TYPE_LOGIN);
+ break;
+
+ case R.id.tv_language:
+ new XPopup.Builder(mContext).asBottomList(
+ "选择语言",
+ new String[]{"中文", "English"},
+ new int[]{R.mipmap.ic_launcher, R.mipmap.ic_launcher},
+ 0,
+ new OnSelectListener() {
+ @Override
+ public void onSelect(int position, String text) {
+ ToastUtil.showShort(mContext,text);
+ }
+ },
+ R.layout.select_language_layout,
+ R.layout.select_language_item
+ ).show();
+ break;
+// case R.id.checkbox_read:
+// if (mYinsiConfirm) {
+// mYinsiConfirm = false;
+// viewBinding.checkboxRead.setChecked(false);
+// } else {
+// mYinsiConfirm = true;
+// viewBinding.checkboxRead.setChecked(true);
+// }
+// break;
+// case R.id.tv_login_confirm:
+// if (TextUtils.isEmpty(viewBinding.edPhoneInput.getText().toString())) {
+// ToastUtil.showShort(mContext, getString(R.string.login_error_phone_empty));
+// return;
+// }
+// if (!PhoneNumberUtils.checkPhoneNum(viewBinding.edPhoneInput.getText().toString().trim())) {
+// ToastUtil.showShort(mContext, getString(R.string.login_error_phone_invalid));
+// return;
+// }
+// if (TextUtils.isEmpty(viewBinding.edCodeInput.getText().toString())) {
+// ToastUtil.showShort(mContext, getString(R.string.login_error_code_empty));
+// return;
+// }
+// if (viewBinding.edCodeInput.getText().toString().trim().length() < 6) {
+// ToastUtil.showShort(mContext, getString(R.string.login_error_code_invalid));
+// return;
+// }
+// if (!mYinsiConfirm) {
+// ToastUtil.showShort(mContext, getString(R.string.login_error_agreement));
+// return;
+// }
+// showLoadingDialog();
+// mPresenter.loginBySmsCode(
+// viewBinding.edPhoneInput.getText().toString().trim(),
+// viewBinding.edCodeInput.getText().toString().trim());
+// new Handler().postDelayed(() -> hideDialogLoading(), 1000);
+// break;
+
+
+ }
+ }
+
+ private CountDownTimer cdTimer = new CountDownTimer(6000, 1000) {
+ @Override
+ public void onTick(long millisUntilFinished) {
+ viewBinding.tvGetVerifiCode.setText("(" + millisUntilFinished / 1000 + ") " + getString(R.string.login_resend));
+ viewBinding.tvGetVerifiCode.setSelected(true);
+ viewBinding.tvGetVerifiCode.setClickable(false);
+ viewBinding.tvGetVerifiCode.setTextColor(mContext.getColor(R.color.color_c8c8c8));
+ viewBinding.tvGetVerifiCode.setBackground(mContext.getResources().getDrawable(R.drawable.shape_gray_50));
+ }
+
+ @Override
+ public void onFinish() {
+ viewBinding.tvGetVerifiCode.setText(getString(R.string.login_resend));
+ viewBinding.tvGetVerifiCode.setSelected(false);
+ viewBinding.tvGetVerifiCode.setClickable(true);
+ viewBinding.tvGetVerifiCode.setTextColor(mContext.getColor(R.color.white));
+ viewBinding.tvGetVerifiCode.setBackground(mContext.getResources().getDrawable(R.drawable.shape_green_50));
+ }
+ };
+
+ @Override
+ public void getPhoneCodeSuccess(BaseBean bean) {
+ hideDialogLoading();
+ ToastUtil.showShort(mContext, getString(R.string.login_code_sent));
+ cdTimer.start();
+ }
+
+ @Override
+ public void loginBySmsCodeSuccess(LoginEntity entity) {
+ hideDialogLoading();
+ ToastUtil.showShort(mContext, getString(R.string.login_success));
+ SharePreUtil.putBoolean(ConstantUtil.SHARE_PRE_NAME, mContext, ConstantUtil.USER_LOGIN, true);
+ SharePreUtil.putString(ConstantUtil.SHARE_PRE_NAME, mContext, ConstantUtil.USER_TOKEN, entity.getAccess_token());
+ SharePreUtil.putString(ConstantUtil.SHARE_PRE_NAME, mContext, ConstantUtil.USER_NICKNAME, entity.getUser_info().getNickname());
+ SharePreUtil.putString(ConstantUtil.SHARE_PRE_NAME, mContext, ConstantUtil.USER_ID, String.valueOf(entity.getUser_info().getUserId()));
+ MingDengUserInfoManager.getInstance().setEntity(entity);
+ MingDengUserInfoManager.getInstance().setUserInfo(entity.getUser_info());
+ Gson gson = new Gson();
+ String userInfoJson = gson.toJson(entity);
+ SharePreUtil.putString(ConstantUtil.SHARE_PRE_NAME, mContext, ConstantUtil.USER_INFO_JSON_CONTENT, userInfoJson);
+ AppConfig.USER_LOGIN = true;
+ AppConfig.USER_TOKEN = entity.getAccess_token();
+ AppConfig.USER_ID = String.valueOf(entity.getUser_info().getUserId());
+ new Handler().postDelayed(() -> {
+ goTo(mContext, MainActivity.class, null);
+ finish();
+ }, 200);
+ }
+
+ @Override
+ public int getLayoutResId(Bundle savedInstanceState) {
+ return 0;
+ }
+
+ @Override
+ public void showError(String message) {
+ hideDialogLoading();
+ ToastUtil.showShort(mContext, message);
+ }
+
+ @Override
+ public void logOut(BaseBean bean) {
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/login/RegisterActivity.java b/app/src/main/java/com/ckkj/water/login/RegisterActivity.java
new file mode 100644
index 0000000..15b022b
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/login/RegisterActivity.java
@@ -0,0 +1,123 @@
+package com.ckkj.water.login;
+
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.text.TextUtils;
+import android.view.View;
+
+import com.ckkj.water.R;
+import com.ckkj.water.base.BaseActivity;
+import com.ckkj.water.databinding.RegisterLayoutBinding;
+import com.ckkj.water.login.entity.LoginEntity;
+import com.ckkj.water.login.mvp.LoginContract;
+import com.ckkj.water.login.mvp.LoginImpl;
+import com.ckkj.water.login.mvp.LoginPresenter;
+import com.ckkj.water.network.BaseBean;
+import com.ckkj.water.utils.number.PhoneNumberUtils;
+import com.ckkj.water.utils.show.ToastUtil;
+import com.gyf.immersionbar.ImmersionBar;
+
+import me.jessyan.autosize.internal.CustomAdapt;
+
+public class RegisterActivity extends BaseActivity
+ implements LoginContract.LoginView {
+ @Override
+ public LoginPresenter createPresenter() {
+ return new LoginPresenter(new LoginImpl());
+ }
+
+ @Override
+ public int getLayoutResId(Bundle savedInstanceState) {
+ return 0;
+ }
+
+ @Override
+ public void initData(Bundle savedInstanceState) {
+ super.initData(savedInstanceState);
+ ImmersionBar.with(this)
+ .statusBarColor(R.color.color_F9F9F9) // 设置状态栏颜色
+ .statusBarDarkFont(true) // 设置状态栏字体为黑色
+ .titleBar(viewBinding.rlTitle)
+ .init();
+ viewBinding.includedTitle.tvTitle.setText(R.string.register_title);
+ initListener();
+ }
+
+ private void initListener() {
+ setClick(viewBinding.includedTitle.imgBack);
+ setClick(viewBinding.tvGetVerifiCode);
+ setClick(viewBinding.tvCommit);
+ }
+
+ private void setClick(View view) {
+ view.setOnClickListener(this::onClick);
+ }
+
+ public void onClick(View v){
+ switch (v.getId()){
+ case R.id.img_back:
+ finish();
+ break;
+
+ case R.id.tv_get_verifi_code:
+ if (TextUtils.isEmpty(viewBinding.etAccount.getText().toString())) {
+ ToastUtil.showShort(mContext, getString(R.string.login_error_phone_empty));
+ return;
+ }
+ if (!PhoneNumberUtils.checkPhoneNum(viewBinding.etAccount.getText().toString().trim()) &&
+ !PhoneNumberUtils.checkEmail(viewBinding.etAccount.getText().toString().trim())) {
+ ToastUtil.showShort(mContext, getString(R.string.login_error_phone_invalid));
+ return;
+ }
+ cdTimer.start();
+ break;
+
+ case R.id.tv_commit:
+ goTo(mContext, RegisterSuccessActivity.class,null);
+ break;
+ }
+ }
+
+
+ private CountDownTimer cdTimer = new CountDownTimer(6000, 1000) {
+ @Override
+ public void onTick(long millisUntilFinished) {
+ viewBinding.tvGetVerifiCode.setText("(" + millisUntilFinished / 1000 + ") " + getString(R.string.login_resend));
+ viewBinding.tvGetVerifiCode.setSelected(true);
+ viewBinding.tvGetVerifiCode.setClickable(false);
+ viewBinding.tvGetVerifiCode.setTextColor(mContext.getColor(R.color.color_c8c8c8));
+ viewBinding.tvGetVerifiCode.setBackground(mContext.getResources().getDrawable(R.drawable.shape_gray_50));
+ }
+
+ @Override
+ public void onFinish() {
+ viewBinding.tvGetVerifiCode.setText(getString(R.string.login_resend));
+ viewBinding.tvGetVerifiCode.setSelected(false);
+ viewBinding.tvGetVerifiCode.setClickable(true);
+ viewBinding.tvGetVerifiCode.setTextColor(mContext.getColor(R.color.white));
+ viewBinding.tvGetVerifiCode.setBackground(mContext.getResources().getDrawable(R.drawable.shape_green_50));
+ }
+ };
+
+
+ @Override
+ public void getPhoneCodeSuccess(BaseBean bean) {
+
+ }
+
+ @Override
+ public void loginBySmsCodeSuccess(LoginEntity entity) {
+
+ }
+
+ @Override
+ public void showError(String message) {
+ ToastUtil.showShort(mContext,message);
+ }
+
+ @Override
+ public void logOut(BaseBean bean) {
+
+ }
+
+}
diff --git a/app/src/main/java/com/ckkj/water/login/RegisterSuccessActivity.java b/app/src/main/java/com/ckkj/water/login/RegisterSuccessActivity.java
new file mode 100644
index 0000000..99afabe
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/login/RegisterSuccessActivity.java
@@ -0,0 +1,101 @@
+package com.ckkj.water.login;
+
+import static android.view.View.GONE;
+
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.view.View;
+
+import com.ckkj.water.R;
+import com.ckkj.water.base.BaseActivity;
+import com.ckkj.water.databinding.RegisterSuccessLayoutBinding;
+import com.ckkj.water.login.entity.LoginEntity;
+import com.ckkj.water.login.mvp.LoginContract;
+import com.ckkj.water.login.mvp.LoginImpl;
+import com.ckkj.water.login.mvp.LoginPresenter;
+import com.ckkj.water.network.BaseBean;
+import com.ckkj.water.permission.PermissionActivity;
+import com.ckkj.water.utils.show.ToastUtil;
+import com.gyf.immersionbar.ImmersionBar;
+
+/**
+ * 注册成功页面
+ */
+public class RegisterSuccessActivity extends BaseActivity
+ implements LoginContract.LoginView {
+
+ private CountDownTimer cdTimer;
+
+ @Override
+ public LoginPresenter createPresenter() {
+ return new LoginPresenter(new LoginImpl());
+ }
+
+ @Override
+ public void initData(Bundle savedInstanceState) {
+ super.initData(savedInstanceState);
+ ImmersionBar.with(this)
+ .statusBarColor(R.color.color_F9F9F9) // 设置状态栏颜色
+ .statusBarDarkFont(true) // 设置状态栏字体为黑色
+ .titleBar(viewBinding.rlTitle)
+ .init();
+ viewBinding.includedTitle.imgBack.setVisibility(GONE);
+ viewBinding.includedTitle.tvTitle.setText(R.string.register_success_title);
+ startCountdown();
+ viewBinding.tvCommit.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ goTo(mContext, PermissionActivity.class,null);
+ }
+ });
+ }
+
+ private void startCountdown() {
+ cdTimer = new CountDownTimer(3000, 1000) {
+ @Override
+ public void onTick(long millisUntilFinished) {
+ long seconds = millisUntilFinished / 1000;
+ viewBinding.tvCommit.setText(getString(R.string.enter_app_countdown, seconds));
+ }
+
+ @Override
+ public void onFinish() {
+ ToastUtil.showShort(mContext, "进入App");
+ }
+ };
+ cdTimer.start();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (cdTimer != null) {
+ cdTimer.cancel();
+ }
+ }
+
+ @Override
+ public int getLayoutResId(Bundle savedInstanceState) {
+ return 0;
+ }
+
+ @Override
+ public void getPhoneCodeSuccess(BaseBean bean) {
+
+ }
+
+ @Override
+ public void loginBySmsCodeSuccess(LoginEntity entity) {
+
+ }
+
+ @Override
+ public void showError(String message) {
+
+ }
+
+ @Override
+ public void logOut(BaseBean bean) {
+
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/login/entity/LoginEntity.java b/app/src/main/java/com/ckkj/water/login/entity/LoginEntity.java
new file mode 100644
index 0000000..df07457
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/login/entity/LoginEntity.java
@@ -0,0 +1,41 @@
+package com.ckkj.water.login.entity;
+
+import com.ckkj.water.base.entity.UserInfoEntity;
+
+import java.io.Serializable;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/28
+ * @description TODO: 描述该类的作用
+ */
+
+public class LoginEntity implements Serializable {
+ private String access_token;
+ private UserInfoEntity user_info;
+ private long expires_in;
+
+ public String getAccess_token() {
+ return access_token;
+ }
+
+ public void setAccess_token(String access_token) {
+ this.access_token = access_token;
+ }
+
+ public UserInfoEntity getUser_info() {
+ return user_info;
+ }
+
+ public void setUser_info(UserInfoEntity user_info) {
+ this.user_info = user_info;
+ }
+
+ public long getExpires_in() {
+ return expires_in;
+ }
+
+ public void setExpires_in(long expires_in) {
+ this.expires_in = expires_in;
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/login/mvp/LoginContract.java b/app/src/main/java/com/ckkj/water/login/mvp/LoginContract.java
new file mode 100644
index 0000000..eab162f
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/login/mvp/LoginContract.java
@@ -0,0 +1,33 @@
+package com.ckkj.water.login.mvp;
+
+import com.ckkj.water.base.IView;
+import com.ckkj.water.login.entity.LoginEntity;
+import com.ckkj.water.network.BaseBean;
+
+import org.json.JSONObject;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/26
+ * @description TODO: 描述该类的作用
+ */
+
+public class LoginContract {
+ public interface CallBack {
+ void success(T msg);
+ void fail(String msg);
+ }
+ public interface DataModel{
+ //获取手机验证码
+ void getPhoneCode(JSONObject json, CallBack callBack);
+ //手机验证码登录
+ void loginBySmsCode(JSONObject json, CallBack callBack);
+ }
+
+ public interface LoginView extends IView{
+ //获取验证码
+ void getPhoneCodeSuccess(BaseBean bean);
+ //验证码登录成功
+ void loginBySmsCodeSuccess(LoginEntity entity);
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/login/mvp/LoginImpl.java b/app/src/main/java/com/ckkj/water/login/mvp/LoginImpl.java
new file mode 100644
index 0000000..a5654d6
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/login/mvp/LoginImpl.java
@@ -0,0 +1,100 @@
+package com.ckkj.water.login.mvp;
+
+import com.ckkj.water.login.entity.LoginEntity;
+import com.ckkj.water.network.BaseBean;
+import com.ckkj.water.network.LoginStateManager;
+import com.ckkj.water.network.NetWorkManager;
+
+import org.json.JSONObject;
+
+import io.reactivex.Observer;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.annotations.NonNull;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.schedulers.Schedulers;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/26
+ * @description TODO: 登录网络请求
+ */
+
+public class LoginImpl implements LoginContract.DataModel {
+ /**
+ * 获取验证码
+ *
+ * @param json
+ * @param callBack
+ */
+ @Override
+ public void getPhoneCode(JSONObject json, LoginContract.CallBack callBack) {
+ try {
+ NetWorkManager.getRequest().sendPhoneCode(NetWorkManager.getToken(), NetWorkManager.toRequestBody(json))
+ .subscribeOn(Schedulers.io())
+ .unsubscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new Observer() {
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {
+ }
+
+ @Override
+ public void onNext(@NonNull BaseBean bean) {
+ if (bean.getCode() == 200) {
+ callBack.success(bean);
+ } else {
+ callBack.fail(bean.getMessage());
+ }
+ }
+
+ @Override
+ public void onError(@NonNull Throwable e) {
+ callBack.fail(e.getMessage());
+ }
+
+ @Override
+ public void onComplete() {
+ }
+ });
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void loginBySmsCode(JSONObject json, LoginContract.CallBack callBack) {
+ try {
+ NetWorkManager.getRequest().loginBySmsCode(NetWorkManager.getToken(), NetWorkManager.toRequestBody(json))
+ .subscribeOn(Schedulers.io())
+ .unsubscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new Observer>() {
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {
+ }
+
+ @Override
+ public void onNext(@NonNull BaseBean bean) {
+ if (bean.getCode() == 200) {
+ callBack.success(bean.getData());
+ // 登录成功时调用
+ LoginStateManager.reset();
+ } else {
+ callBack.fail(bean.getMessage());
+ }
+ }
+
+ @Override
+ public void onError(@NonNull Throwable e) {
+ callBack.fail(e.getMessage());
+ }
+
+ @Override
+ public void onComplete() {
+ }
+ });
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/login/mvp/LoginPresenter.java b/app/src/main/java/com/ckkj/water/login/mvp/LoginPresenter.java
new file mode 100644
index 0000000..b5d9cef
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/login/mvp/LoginPresenter.java
@@ -0,0 +1,116 @@
+package com.ckkj.water.login.mvp;
+
+import com.ckkj.water.AppConfig;
+import com.ckkj.water.ConstantUtil;
+import com.ckkj.water.EventMessage;
+import com.ckkj.water.MyApplication;
+import com.ckkj.water.base.BasePresenter;
+import com.ckkj.water.base.entity.MineUserInfoBean;
+import com.ckkj.water.login.entity.LoginEntity;
+import com.ckkj.water.mine.mvp.MineImpl;
+import com.ckkj.water.network.BaseBean;
+import com.ckkj.water.utils.dataUtil.SharePreUtil;
+import com.ckkj.water.utils.manager.MingDengUserInfoManager;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import de.greenrobot.event.EventBus;
+
+/**
+ * @author fengxiaoyang
+ * @date 2025/7/26
+ * @description TODO: 描述该类的作用
+ */
+
+public class LoginPresenter extends BasePresenter {
+ private LoginImpl mImpl;
+
+ public LoginPresenter(LoginImpl mImpl) {
+ this.mImpl = mImpl;
+ }
+
+ public void getPhoneCode(String phone, String codeType) {
+ try {
+ JSONObject json = new JSONObject();
+ json.put("mobile",phone);
+ json.put("type",codeType);
+ mImpl.getPhoneCode(json, new LoginContract.CallBack() {
+ @Override
+ public void success(BaseBean bean) {
+ if(LoginPresenter.this.getView() != null){
+ LoginPresenter.this.getView().getPhoneCodeSuccess(bean);
+ }
+ }
+
+ @Override
+ public void fail(String msg) {
+ if(LoginPresenter.this.getView() != null){
+ LoginPresenter.this.getView().showError(msg);
+ }
+ }
+ });
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 通过手机验证码登录
+ * @param phone
+ * @param code
+ */
+ public void loginBySmsCode(String phone, String code) {
+ try {
+ JSONObject json = new JSONObject();
+ json.put("mobile",phone);
+ json.put("code",code);
+ mImpl.loginBySmsCode(json, new LoginContract.CallBack() {
+ @Override
+ public void success(LoginEntity bean) {
+ if(LoginPresenter.this.getView() != null){
+ LoginPresenter.this.getView().loginBySmsCodeSuccess(bean);
+ EventBus.getDefault().post(new EventMessage(EventMessage.USER_LOGIN_SUCCESS));
+ //获取个人信息
+// getUserInfo();
+ }
+ }
+
+ @Override
+ public void fail(String msg) {
+ if(LoginPresenter.this.getView() != null){
+ LoginPresenter.this.getView().showError(msg);
+ }
+ }
+ });
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ /**
+ * 查询个人信息
+ */
+ public void getUserInfo() {
+ Map map = new HashMap<>();
+// new MineImpl().getUserInfo(map, new LoginContract.CallBack() {
+// @Override
+// public void success(MineUserInfoBean bean) {
+// if(bean != null){
+// MingDengUserInfoManager.getInstance().setUserInfo(bean.getUserInfo());
+// SharePreUtil.putBoolean(ConstantUtil.SHARE_PRE_NAME, MyApplication.getContext(),
+// ConstantUtil.USER_HAS_SET_BIRTH,bean.isHasEightCharacters());
+// AppConfig.USER_SET_BIRTH_INFO = bean.isHasEightCharacters();
+// }
+// }
+//
+// @Override
+// public void fail(String msg) {
+// }
+// });
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/main/MainContract.java b/app/src/main/java/com/ckkj/water/main/MainContract.java
new file mode 100644
index 0000000..224969f
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/main/MainContract.java
@@ -0,0 +1,37 @@
+package com.ckkj.water.main;
+
+import com.ckkj.water.base.IView;
+import com.ckkj.water.base.entity.MineUserInfoBean;
+import com.ckkj.water.main.entity.CheckVersionEntity;
+import com.ckkj.water.network.BaseBean;
+
+import org.json.JSONObject;
+
+import java.util.Map;
+
+public interface MainContract {
+ interface DataModel{
+ //新版本检测
+ void checkAppVersionCode(JSONObject json, CallBack callBack);
+ //运势
+ void getYunshiData(Map map, CallBack callBack);
+ }
+
+ interface CallBack {
+ void success(T msg);
+
+ void fail(String msg);
+ }
+
+ interface MainView extends IView {
+ //版本检测更新
+ void getAppVersionSuccess(CheckVersionEntity bean);
+ //输入邀请码,激活临时会员
+ void activationVipSuccess(BaseBean bean);
+ //校验版本号
+ void getVersionCheck(CheckVersionEntity bean);
+ void getYunshiDataSuccess(BaseBean bean);
+ //获取个人信息成功
+ void getUserInfoSuccess(MineUserInfoBean bean);
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/main/MainImpl.java b/app/src/main/java/com/ckkj/water/main/MainImpl.java
new file mode 100644
index 0000000..56eede1
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/main/MainImpl.java
@@ -0,0 +1,98 @@
+package com.ckkj.water.main;
+
+
+import com.ckkj.water.main.entity.CheckVersionEntity;
+import com.ckkj.water.network.BaseBean;
+import com.ckkj.water.network.NetWorkManager;
+
+import org.json.JSONObject;
+
+import java.util.Map;
+
+import io.reactivex.Observer;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.annotations.NonNull;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.schedulers.Schedulers;
+
+public class MainImpl implements MainContract.DataModel{
+
+
+ /**
+ * 检测新版本
+ * @param jsonObject
+ * @param callBack
+ */
+ @Override
+ public void checkAppVersionCode(JSONObject jsonObject, MainContract.CallBack callBack) {
+ try {
+ NetWorkManager.getRequest().checkVersionCode(NetWorkManager.getToken(),NetWorkManager.toRequestBody(jsonObject))
+ .subscribeOn(Schedulers.io())
+ .unsubscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new Observer>() {
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {
+
+ }
+
+ @Override
+ public void onNext(@NonNull BaseBean bean) {
+ if(bean.getCode() == 200){
+ callBack.success(bean.getData());
+ }else{
+ callBack.fail(bean.getMessage());
+ }
+ }
+
+ @Override
+ public void onError(@NonNull Throwable e) {
+ callBack.fail(e.getMessage());
+ }
+
+ @Override
+ public void onComplete() {
+ }
+ });
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void getYunshiData(Map map, MainContract.CallBack callBack) {
+// try {
+// NetWorkManager.getRequest().indexTalkToAi(NetWorkManager.getToken(),map)
+// .subscribeOn(Schedulers.io())
+// .unsubscribeOn(Schedulers.io())
+// .observeOn(AndroidSchedulers.mainThread())
+// .subscribe(new Observer() {
+// @Override
+// public void onSubscribe(@NonNull Disposable d) {
+//
+// }
+//
+// @Override
+// public void onNext(@NonNull BaseBean bean) {
+// if(bean.getCode() == 200){
+// callBack.success(bean);
+// }else{
+// callBack.fail(bean.getMessage());
+// }
+// }
+//
+// @Override
+// public void onError(@NonNull Throwable e) {
+// callBack.fail(e.getMessage());
+// }
+//
+// @Override
+// public void onComplete() {
+// }
+// });
+// } catch (Exception e) {
+// e.printStackTrace();
+// }
+ }
+
+}
diff --git a/app/src/main/java/com/ckkj/water/main/MainPresenter.java b/app/src/main/java/com/ckkj/water/main/MainPresenter.java
new file mode 100644
index 0000000..fb9d791
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/main/MainPresenter.java
@@ -0,0 +1,67 @@
+package com.ckkj.water.main;
+
+import com.ckkj.water.base.BasePresenter;
+import com.ckkj.water.base.entity.MineUserInfoBean;
+import com.ckkj.water.login.mvp.LoginContract;
+import com.ckkj.water.main.entity.CheckVersionEntity;
+import com.ckkj.water.mine.mvp.MineImpl;
+import com.ckkj.water.network.BaseBean;
+
+import org.json.JSONObject;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class MainPresenter extends BasePresenter {
+ private MainImpl mainImpl;
+
+ public MainPresenter(MainImpl mainImpl) {
+ this.mainImpl = mainImpl;
+ }
+
+
+ /**
+ * 获取用户信息
+ */
+ public void checkNewVersion() {
+ JSONObject json = new JSONObject();
+ mainImpl.checkAppVersionCode(json, new MainContract.CallBack() {
+ @Override
+ public void success(CheckVersionEntity bean) {
+ if( MainPresenter.this.getView() != null){
+ MainPresenter.this.getView().getAppVersionSuccess(bean);
+ }
+ }
+
+ @Override
+ public void fail(String msg) {
+ if( MainPresenter.this.getView() != null){
+ MainPresenter.this.getView().showError(msg);
+ }
+ }
+ });
+ }
+
+
+ /**
+ * 查询个人信息
+ */
+ public void getUserInfo() {
+ Map map = new HashMap<>();
+// new MineImpl().getUserInfo(map, new LoginContract.CallBack() {
+// @Override
+// public void success(MineUserInfoBean bean) {
+// if( MainPresenter.this.getView() != null){
+// MainPresenter.this.getView().getUserInfoSuccess(bean);
+// }
+// }
+//
+// @Override
+// public void fail(String msg) {
+// if( MainPresenter.this.getView() != null){
+// MainPresenter.this.getView().showError(msg);
+// }
+// }
+// });
+ }
+}
diff --git a/app/src/main/java/com/ckkj/water/main/activity/MainActivity.java b/app/src/main/java/com/ckkj/water/main/activity/MainActivity.java
new file mode 100644
index 0000000..58e4a4a
--- /dev/null
+++ b/app/src/main/java/com/ckkj/water/main/activity/MainActivity.java
@@ -0,0 +1,466 @@
+package com.ckkj.water.main.activity;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.ImageView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.viewpager2.widget.ViewPager2;
+
+import com.ckkj.water.history.HistoryFragment;
+import com.ckkj.water.index.IndexFragment;
+import com.ckkj.water.mine.MineCenterFragment;
+import com.ckkj.water.plan.WaterPlanFragment;
+import com.gyf.immersionbar.ImmersionBar;
+import com.ckkj.water.AppConfig;
+import com.ckkj.water.ConstantUtil;
+import com.ckkj.water.EventMessage;
+import com.ckkj.water.R;
+import com.ckkj.water.adapter.ViewPageAdapter;
+import com.ckkj.water.base.BaseActivity;
+import com.ckkj.water.base.entity.MineUserInfoBean;
+import com.ckkj.water.chat.ChatFragment;
+import com.ckkj.water.databinding.MainActivityLayoutBinding;
+import com.ckkj.water.dialog.DialogAppUpdate;
+import com.ckkj.water.fate.view.FateFragment;
+import com.ckkj.water.main.MainContract;
+import com.ckkj.water.main.MainImpl;
+import com.ckkj.water.main.MainPresenter;
+import com.ckkj.water.main.entity.CheckVersionEntity;
+import com.ckkj.water.mine.MineFragment;
+import com.ckkj.water.network.BaseBean;
+import com.ckkj.water.plate.PlateFragment;
+import com.ckkj.water.utils.AppInfoUtil;
+import com.ckkj.water.utils.dataUtil.SharePreUtil;
+import com.ckkj.water.utils.manager.ActivityManager;
+import com.ckkj.water.utils.manager.MingDengUserInfoManager;
+import com.ckkj.water.utils.show.ToastUtil;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import de.greenrobot.event.Subscribe;
+import de.greenrobot.event.ThreadMode;
+import pub.devrel.easypermissions.EasyPermissions;
+
+/**
+ * 主页面,BottomNavigationView + Viewpage2展示内容
+ */
+public class MainActivity extends BaseActivity implements
+ MainContract.MainView,EasyPermissions.PermissionCallbacks{
+ private List mFragmentList = new ArrayList<>();
+ private ViewPageAdapter mVpAdapter;
+ //是否是强制更新
+ private boolean isForceUpdate;
+
+ // 添加标记,用于区分是否是首次创建
+ private boolean isFirstCreate = true;
+
+ @Override
+ public MainPresenter createPresenter() {
+ return new MainPresenter(new MainImpl());
+ }
+
+ @Override
+ protected void initWidget() {
+ super.initWidget();
+ }
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ImmersionBar.with(this)
+ .transparentStatusBar() // 透明状态栏
+ .statusBarDarkFont(false) // 白色状态栏图标
+ .navigationBarColor(R.color.white)
+ .keyboardEnable(false)
+ .init();
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ // 设置新的Intent
+ setIntent(intent);
+
+ // 处理新的Intent
+// handleNewIntent(intent);
+ }
+
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+// if(AppConfig.USER_LOGIN){
+// mPresenter.getUserInfo();
+// }
+ }
+
+
+
+ @Override
+ public void initData(Bundle savedInstanceState) {
+ super.initData(savedInstanceState);
+ mPresenter.checkNewVersion();
+ // 清空Fragment列表,避免状态恢复问题
+ mFragmentList.clear();
+ mFragmentList.add(new IndexFragment());
+ mFragmentList.add(new WaterPlanFragment());
+ mFragmentList.add(new HistoryFragment());
+ mFragmentList.add(new MineCenterFragment());
+
+ // 重新设置适配器
+ if (mVpAdapter != null) {
+ mVpAdapter = null;
+ }
+ mVpAdapter = new ViewPageAdapter(mContext, mFragmentList);
+ viewBinding.navViewpage2.setAdapter(mVpAdapter);
+ viewBinding.navViewpage2.setOffscreenPageLimit(1);
+
+ // 设置ViewPager2的状态保存策略
+ viewBinding.navViewpage2.setSaveEnabled(false);
+ viewBinding.navViewpage2.setUserInputEnabled(false);
+
+ // 检查是否需要跳转到IndexFragment(仅在首次创建时)
+ if (isFirstCreate) {
+ checkAndNavigateToIndexFragment();
+ isFirstCreate = false;
+ }
+
+ // ViewPager 滑动监听,切换 BottomNavigationView 选中项和状态栏
+ viewBinding.navViewpage2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
+ @Override
+ public void onPageSelected(int position) {
+ super.onPageSelected(position);
+ switch (position) {
+ case 0:
+ viewBinding.navView.getMenu().getItem(position).setChecked(true);
+ viewBinding.navView.setSelectedItemId(R.id.navigation_index);
+ // IndexFragment 需要透明状态栏
+ ImmersionBar.with(MainActivity.this)
+ .transparentStatusBar()
+ .statusBarDarkFont(false)
+ .init();
+ break;
+ case 1:
+ viewBinding.navView.getMenu().getItem(position).setChecked(true);
+ viewBinding.navView.setSelectedItemId(R.id.navigation_plan);
+ // 其他页面恢复白色状态栏
+ ImmersionBar.with(MainActivity.this)
+ .statusBarColor(R.color.white)
+ .statusBarDarkFont(true)
+ .init();
+ break;
+ case 2:
+ viewBinding.navView.getMenu().getItem(position).setChecked(true);
+ viewBinding.navView.setSelectedItemId(R.id.navigation_history);
+ ImmersionBar.with(MainActivity.this)
+ .statusBarColor(R.color.white)
+ .statusBarDarkFont(true)
+ .init();
+ break;
+ case 3:
+ viewBinding.navView.getMenu().getItem(position).setChecked(true);
+ viewBinding.navView.setSelectedItemId(R.id.navigation_mine);
+ ImmersionBar.with(MainActivity.this)
+ .statusBarColor(R.color.white)
+ .statusBarDarkFont(true)
+ .init();
+ break;
+ }
+ }
+ });
+
+ // BottomNavigationView 点击监听,切换 ViewPager2 页面
+ viewBinding.navView.setOnNavigationItemSelectedListener(item -> {
+ switch (item.getItemId()) {
+
+ case R.id.navigation_index:
+ viewBinding.navViewpage2.setCurrentItem(0);
+ ImmersionBar.with(MainActivity.this)
+ .transparentStatusBar()
+ .statusBarDarkFont(false)
+ .init();
+ return true;
+
+ case R.id.navigation_plan:
+ viewBinding.navViewpage2.setCurrentItem(1);
+
+ return true;
+
+ case R.id.navigation_history:
+ viewBinding.navViewpage2.setCurrentItem(2);
+ ImmersionBar.with(MainActivity.this)
+ .statusBarColor(R.color.white)
+ .statusBarDarkFont(true)
+ .init();
+ return true;
+
+ case R.id.navigation_mine:
+ viewBinding.navViewpage2.setCurrentItem(3);
+ ImmersionBar.with(MainActivity.this)
+ .statusBarColor(R.color.white)
+ .statusBarDarkFont(true)
+ .init();
+ return true;
+ }
+ return false;
+ });
+ viewBinding.navView.setItemIconTintList(null);
+ }
+
+ /**
+ * 处理新的Intent
+ */
+ private void handleNewIntent(Intent intent) {
+ if (intent != null) {
+ Bundle extras = intent.getExtras();
+ if (extras != null && extras.getBoolean("goto_index_fragment", false)) {
+ // 延迟执行,确保Fragment已经初始化完成
+ viewBinding.navViewpage2.post(() -> {
+ // 跳转到IndexFragment(位置为1)
+ viewBinding.navViewpage2.setCurrentItem(1);
+ // 设置底部导航栏选中状态
+ viewBinding.navView.setSelectedItemId(R.id.navigation_index);
+
+ // 将参数传递给IndexFragment
+ passBundleToIndexFragment(extras);
+ });
+ }
+ }
+ }
+
+ /**
+ * 检查并跳转到IndexFragment(仅在首次创建时)
+ */
+ private void checkAndNavigateToIndexFragment() {
+ Bundle extras = getIntent().getExtras();
+ if (extras != null && extras.getBoolean("goto_index_fragment", false)) {
+ // 延迟执行,确保Fragment已经初始化完成
+ viewBinding.navViewpage2.post(() -> {
+ // 跳转到IndexFragment(位置为1)
+ viewBinding.navViewpage2.setCurrentItem(1);
+ // 设置底部导航栏选中状态
+ viewBinding.navView.setSelectedItemId(R.id.navigation_index);
+
+ // 将参数传递给IndexFragment
+ passBundleToIndexFragment(extras);
+ });
+ }
+ }
+
+ /**
+ * 将Bundle参数传递给IndexFragment
+ */
+ private void passBundleToIndexFragment(Bundle bundle) {
+ // 获取IndexFragment实例
+ Fragment indexFragment = mFragmentList.get(1);
+ if (indexFragment instanceof ChatFragment) {
+ // 设置参数到Fragment
+ indexFragment.setArguments(bundle);
+
+ // 如果Fragment已经创建,可以调用其方法来处理参数
+ if (indexFragment.isAdded()) {
+ ((ChatFragment) indexFragment).handleBirthInfoBundle(bundle);
+ }
+ }
+ }
+
+
+ @Override
+ public void getYunshiDataSuccess(BaseBean bean) {
+ hideDialogLoading();
+ }
+
+ /**
+ * 获取个人信息
+ * @param bean
+ */
+ @Override
+ public void getUserInfoSuccess(MineUserInfoBean bean) {
+ if(bean != null){
+ MingDengUserInfoManager.getInstance().setUserInfo(bean.getUserInfo());
+ SharePreUtil.putBoolean(ConstantUtil.SHARE_PRE_NAME,mContext,ConstantUtil.USER_HAS_SET_BIRTH,bean.isHasEightCharacters());
+ AppConfig.USER_SET_BIRTH_INFO = bean.isHasEightCharacters();
+ }
+ }
+
+
+ @Subscribe(threadMode = ThreadMode.MainThread)
+ public void onGetMessage(EventMessage message) {
+ switch (message.msg){
+ case EventMessage.DATA_REFRESH_EVENT:
+
+ break;
+
+ case EventMessage.SWITCH_TO_FATE_FRAGMENT://切换到 fateFragment
+ break;
+ }
+ }
+
+
+ @Override
+ public int getLayoutResId(Bundle savedInstanceState) {
+ return 0;
+ }
+
+ @Override
+ public void showError(String message) {
+
+ }
+
+ @Override
+ public void logOut(BaseBean bean) {
+
+ }
+
+
+ private void animateIcon(ImageView icon) {
+ Animation explodeAnimation = AnimationUtils.loadAnimation(this, R.anim.index_tab_anim);
+ icon.startAnimation(explodeAnimation);
+ }
+
+
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull @NotNull String[] permissions, @NonNull @NotNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
+ }
+
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ }
+
+ private long exitTime = 0;
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ exitApp();
+ return false;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ private void exitApp() {
+ if ((System.currentTimeMillis() - exitTime) > 2000) {
+ ToastUtil.showShort(getApplicationContext(), "再按一次退出");
+ exitTime = System.currentTimeMillis();
+ } else {
+ //结束所有activity
+ ActivityManager.removeAllActivity();
+ finish();
+ }
+ }
+
+ /**
+ * 检测新版本
+ * @param bean
+ */
+ private DialogAppUpdate mUpdateDialog;
+
+
+ @Override
+ public void getAppVersionSuccess(CheckVersionEntity bean) {
+ int versionCode = (int) AppInfoUtil.getAppVersionCode(mContext);
+ if(bean == null){
+ return;
+ }
+ if(bean.getVersionCoercion().equals("0")){//不强制
+ isForceUpdate = false;
+ }else{
+ isForceUpdate = true;
+ }
+ //前期强制更新
+// isForceUpdate = true;
+ if(bean.getVersionNumber() > versionCode){
+ //App更新dialog
+ mUpdateDialog = new DialogAppUpdate(mContext,R.layout.dialog_app_update,isForceUpdate,bean);
+ mUpdateDialog.show();
+ mUpdateDialog.setOnDialogListener(new DialogAppUpdate.DialogListener() {
+ @Override
+ public void callBackGetPermisson(String permission) {
+ if (!hasRecordPermission(permission)) {
+ // 没有权限,只在ACTION_DOWN时去申请
+ getPermissionRequst(permission,"文件存储权限申请",
+ "星语明灯需要您的授权,获取存储权限来更新您的应用",PICK_IMAGE_REQUEST_300);
+ }else{
+
+
+ }
+ }
+
+ @Override
+ public void downLoadFinish() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ ToastUtil.showShort(mContext,"下载完成,正在安装");
+ }
+ });
+ }
+ });
+ }
+ }
+
+ /**
+ * 校验版本
+ * @param entity
+ */
+ @Override
+ public void getVersionCheck(CheckVersionEntity entity) {
+ if(entity == null){
+ return;
+ }
+ }
+
+
+ @Override
+ public void activationVipSuccess(BaseBean bean) {
+
+ }
+
+
+
+
+ @Override
+ public void onPermissionsGranted(int requestCode, @NonNull @NotNull List perms) {
+ if (requestCode == ConstantUtil.WRITE_FILE_PERMISSON_CPDE) {
+ if(mUpdateDialog != null){
+ mUpdateDialog.startUpdateApp();
+ }
+ }
+ }
+
+ @Override
+ public void onPermissionsDenied(int requestCode, @NonNull @NotNull List