新增ChatMessage.java建立聊天消息数据模型 #36
Merged
pjao9fvxr
merged 2 commits from zhouzexin_branch into master 4 weeks ago
@ -0,0 +1,16 @@
|
||||
package net.micode.notes.model;
|
||||
|
||||
public class ChatMessage {
|
||||
public long id;
|
||||
public int senderType; // 0:用户, 1:AI
|
||||
public int msgType; // 0:普通文本, 1:提醒卡片
|
||||
public String content;
|
||||
public long createdAt;
|
||||
|
||||
public ChatMessage(int senderType, int msgType, String content) {
|
||||
this.senderType = senderType;
|
||||
this.msgType = msgType;
|
||||
this.content = content;
|
||||
this.createdAt = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,103 @@
|
||||
package net.micode.notes.ui;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
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 net.micode.notes.R;
|
||||
import net.micode.notes.data.Notes;
|
||||
import net.micode.notes.data.Notes.NoteColumns;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class AgendaAdapter extends RecyclerView.Adapter<AgendaAdapter.AgendaViewHolder> {
|
||||
|
||||
private Context mContext;
|
||||
private List<AgendaItem> mData = new ArrayList<>();
|
||||
private OnAgendaActionListener mListener;
|
||||
|
||||
// [关键修复] 定义接口
|
||||
public interface OnAgendaActionListener {
|
||||
void onToggleComplete(AgendaItem item);
|
||||
void onDelete(AgendaItem item);
|
||||
}
|
||||
|
||||
public void setOnAgendaActionListener(OnAgendaActionListener listener) {
|
||||
this.mListener = listener;
|
||||
}
|
||||
|
||||
public static class AgendaItem {
|
||||
public long id;
|
||||
public String title;
|
||||
public String timeLabel;
|
||||
public long startTime;
|
||||
public boolean isCompleted;
|
||||
public boolean isSpecificTime() {
|
||||
return timeLabel != null && timeLabel.contains(":");
|
||||
}
|
||||
}
|
||||
|
||||
public AgendaAdapter(Context context) {
|
||||
this.mContext = context;
|
||||
}
|
||||
|
||||
public void updateData(List<AgendaItem> newList) {
|
||||
Collections.sort(newList, (a, b) -> {
|
||||
if (a.isCompleted != b.isCompleted) return a.isCompleted ? 1 : -1;
|
||||
if (a.isSpecificTime() != b.isSpecificTime()) return a.isSpecificTime() ? -1 : 1;
|
||||
return Long.compare(a.startTime, b.startTime);
|
||||
});
|
||||
this.mData = newList;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public AgendaViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(mContext).inflate(R.layout.agenda_item, parent, false);
|
||||
return new AgendaViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull AgendaViewHolder holder, int position) {
|
||||
AgendaItem item = mData.get(position);
|
||||
holder.tvContent.setText(item.title);
|
||||
holder.tvTime.setText(item.timeLabel);
|
||||
|
||||
if (item.isCompleted) {
|
||||
holder.ivCheck.setImageResource(R.drawable.checkbox_checked);
|
||||
holder.tvContent.setPaintFlags(holder.tvContent.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
|
||||
holder.tvContent.setTextColor(Color.LTGRAY);
|
||||
} else {
|
||||
holder.ivCheck.setImageResource(R.drawable.checkbox_unchecked);
|
||||
holder.tvContent.setPaintFlags(holder.tvContent.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
|
||||
holder.tvContent.setTextColor(Color.parseColor("#333333"));
|
||||
}
|
||||
|
||||
holder.ivCheck.setOnClickListener(v -> { if (mListener != null) mListener.onToggleComplete(item); });
|
||||
holder.btnDelete.setOnClickListener(v -> { if (mListener != null) mListener.onDelete(item); });
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() { return mData.size(); }
|
||||
|
||||
public static class AgendaViewHolder extends RecyclerView.ViewHolder {
|
||||
ImageView ivCheck, btnDelete;
|
||||
TextView tvTime, tvContent;
|
||||
public AgendaViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
ivCheck = itemView.findViewById(R.id.iv_agenda_check);
|
||||
tvTime = itemView.findViewById(R.id.tv_agenda_time);
|
||||
tvContent = itemView.findViewById(R.id.tv_agenda_content);
|
||||
btnDelete = itemView.findViewById(R.id.btn_agenda_delete);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
package net.micode.notes.ui;
|
||||
|
||||
import android.graphics.Typeface;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import net.micode.notes.R;
|
||||
import net.micode.notes.model.ChatMessage;
|
||||
import java.util.List;
|
||||
|
||||
public class ChatAdapter extends RecyclerView.Adapter<ChatAdapter.ChatViewHolder> {
|
||||
private List<ChatMessage> mMessages;
|
||||
|
||||
public ChatAdapter(List<ChatMessage> messages) {
|
||||
this.mMessages = messages;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
ChatMessage msg = mMessages.get(position);
|
||||
if (msg.senderType == 0) return 0; // 用户消息
|
||||
if (msg.msgType == 1) return 2; // 提醒卡片
|
||||
return 1; // AI 普通回复
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ChatViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_chat_msg, parent, false);
|
||||
return new ChatViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ChatViewHolder holder, int position) {
|
||||
ChatMessage msg = mMessages.get(position);
|
||||
int viewType = getItemViewType(position);
|
||||
|
||||
// 隐藏所有,根据类型显示
|
||||
holder.leftLayout.setVisibility(View.GONE);
|
||||
holder.rightLayout.setVisibility(View.GONE);
|
||||
|
||||
if (viewType == 0) { // 用户
|
||||
holder.rightLayout.setVisibility(View.VISIBLE);
|
||||
holder.tvRight.setText(msg.content);
|
||||
} else { // AI 或 提醒
|
||||
holder.leftLayout.setVisibility(View.VISIBLE);
|
||||
holder.tvLeft.setText(msg.content);
|
||||
|
||||
if (viewType == 2) { // 提醒特殊样式
|
||||
holder.tvLeft.setBackgroundResource(R.drawable.bg_bubble_reminder);
|
||||
holder.tvLeft.setTypeface(null, Typeface.BOLD);
|
||||
holder.tvLeft.setText("📅 日程提醒:\n" + msg.content);
|
||||
} else {
|
||||
holder.tvLeft.setBackgroundResource(R.drawable.bg_bubble_ai);
|
||||
holder.tvLeft.setTypeface(null, Typeface.NORMAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() { return mMessages.size(); }
|
||||
|
||||
static class ChatViewHolder extends RecyclerView.ViewHolder {
|
||||
View leftLayout, rightLayout;
|
||||
TextView tvLeft, tvRight;
|
||||
ChatViewHolder(View v) {
|
||||
super(v);
|
||||
leftLayout = v.findViewById(R.id.ll_left_layout);
|
||||
rightLayout = v.findViewById(R.id.ll_right_layout);
|
||||
tvLeft = v.findViewById(R.id.tv_msg_left);
|
||||
tvRight = v.findViewById(R.id.tv_msg_right);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,109 @@
|
||||
package net.micode.notes.ui;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import net.micode.notes.R;
|
||||
import net.micode.notes.data.Notes;
|
||||
|
||||
public class FloatingService extends Service {
|
||||
private WindowManager windowManager;
|
||||
private View floatingView;
|
||||
private WindowManager.LayoutParams params;
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) { return null; }
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
|
||||
floatingView = LayoutInflater.from(this).inflate(R.layout.floating_window, null);
|
||||
|
||||
// 设置布局参数
|
||||
int layoutFlag;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
layoutFlag = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
|
||||
} else {
|
||||
layoutFlag = WindowManager.LayoutParams.TYPE_PHONE;
|
||||
}
|
||||
|
||||
params = new WindowManager.LayoutParams(
|
||||
WindowManager.LayoutParams.WRAP_CONTENT,
|
||||
WindowManager.LayoutParams.WRAP_CONTENT,
|
||||
layoutFlag,
|
||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, // 保证外部可点击
|
||||
PixelFormat.TRANSLUCENT
|
||||
);
|
||||
|
||||
params.gravity = Gravity.TOP | Gravity.START;
|
||||
params.x = 100;
|
||||
params.y = 100;
|
||||
|
||||
// 挂载到窗口
|
||||
windowManager.addView(floatingView, params);
|
||||
|
||||
// 设置交互逻辑
|
||||
setupInteraction();
|
||||
}
|
||||
|
||||
private void setupInteraction() {
|
||||
View ball = floatingView.findViewById(R.id.iv_floating_ball);
|
||||
|
||||
ball.setOnTouchListener(new View.OnTouchListener() {
|
||||
private int initialX, initialY;
|
||||
private float initialTouchX, initialTouchY;
|
||||
private long touchStartTime;
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
initialX = params.x;
|
||||
initialY = params.y;
|
||||
initialTouchX = event.getRawX();
|
||||
initialTouchY = event.getRawY();
|
||||
touchStartTime = System.currentTimeMillis();
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
params.x = initialX + (int) (event.getRawX() - initialTouchX);
|
||||
params.y = initialY + (int) (event.getRawY() - initialTouchY);
|
||||
windowManager.updateViewLayout(floatingView, params);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_UP:
|
||||
// 如果按下时间短且移动距离小,判定为点击
|
||||
if (System.currentTimeMillis() - touchStartTime < 200) {
|
||||
openNewNote();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void openNewNote() {
|
||||
Intent intent = new Intent(this, NoteEditActivity.class);
|
||||
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
|
||||
intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, Notes.ID_ROOT_FOLDER);
|
||||
// [核心] Service 调起 Activity 必须加此 Flag
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (floatingView != null) windowManager.removeView(floatingView);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue