adapter数据来源

73 阅读14分钟
111
private void setupAgreementText() {
    setupAgreement1();
    setupAgreement2();
}

private void setupAgreement1() {
    String agreement1Text = getString(R.string.agreement1);
    
    // 处理 "Terms of Use" 点击
    int termsStart = agreement1Text.indexOf("Terms of Use");
    int termsEnd = termsStart + "Terms of Use".length();
    
    SpannableString termsSpannable = SpannableStringInstance.getInstance().generateSpannableFontText(
        this,
        agreement1Text,
        termsStart,
        termsEnd,
        false, // underline
        R.color.link_pressed_color, // primaryColor
        R.color.white, // secondaryColor
        new SpannableStringInstance.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(AgreementActivity.this, "Terms of Use", Toast.LENGTH_SHORT).show();
            }
        }
    );
    
    // 处理 "Privacy Policy" 点击
    int privacyStart = agreement1Text.indexOf("Privacy Policy");
    int privacyEnd = privacyStart + "Privacy Policy".length();
    
    // 由于需要两个点击区域,需要手动组合
    SpannableStringBuilder agreement1Builder = new SpannableStringBuilder(agreement1Text);
    
    // 添加 Terms of Use 的点击事件
    agreement1Builder.setSpan(new SpannableStringInstance.TouchableSpan(
        getResources().getColor(R.color.link_pressed_color),
        getResources().getColor(R.color.white),
        false
    ) {
        @Override
        public void onClick(View view) {
            Toast.makeText(AgreementActivity.this, "Terms of Use", Toast.LENGTH_SHORT).show();
        }
    }, termsStart, termsEnd, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
    
    // 添加 Privacy Policy 的点击事件
    agreement1Builder.setSpan(new SpannableStringInstance.TouchableSpan(
        getResources().getColor(R.color.link_pressed_color),
        getResources().getColor(R.color.white),
        false
    ) {
        @Override
        public void onClick(View view) {
            Toast.makeText(AgreementActivity.this, "Privacy Policy", Toast.LENGTH_SHORT).show();
        }
    }, privacyStart, privacyEnd, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
    
    viewBinding.agreement1.setText(agreement1Builder);
    viewBinding.agreement1.setMovementMethod(SpannableStringInstance.getInstance().getLinkMovementMethod());
}

private void setupAgreement2() {
    String agreement2Text = getString(R.string.agreement2);
    int start = agreement2Text.indexOf("User Experience Improvement");
    int end = start + "User Experience Improvement".length();
    
    SpannableString agreement2Spannable = SpannableStringInstance.getInstance().generateSpannableFontText(
        this,
        agreement2Text,
        start,
        end,
        false, // underline
        R.color.link_pressed_color, // primaryColor
        R.color.white, // secondaryColor
        new SpannableStringInstance.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(AgreementActivity.this, "User Experience Improvement", Toast.LENGTH_SHORT).show();
            }
        }
    );
    
    viewBinding.agreement2.setText(agreement2Spannable);
    viewBinding.agreement2.setMovementMethod(SpannableStringInstance.getInstance().getLinkMovementMethod());
}





三、系统相关者和需求

3.1 系统相关者

整个系统包括 Mercusys App/Tether App、路由器设备端、网络服务提供商(ISP),他们之间的通信关系如图所示:

3.2 系统相关者需求

3.2.1 双端有差异的产品需求

3.2.1.1 Hotspot备份功能支持差异

Tether App版本1.3支持Hotspot的backup和load balance功能,版本1.2不支持,Hotspot为单独模式;Mercusys App完全支持Hotspot功能,需要根据设备接口确定是否支持Hotspot备份

3.2.1.2 连接卡片交互展示差异

Tether App支持连接卡片的折叠/展开功能,记住用户操作偏好,默认全部折叠;Mercusys App注重信息密度展示,同时显示所有连接详情,需要根据界面设计规范确定交互方式

3.2.1.3 首次连接引导范围差异

Tether App版本1.2首次连接引导仅针对Ethernet和USB Internet;Mercusys App和Tether App版本1.3支持完整连接方式引导,需要根据设备支持的连接类型确定引导范围

3.2.1.4 Slider Control物理控制差异

Tether App支持Slider Control物理拨片控制Operation Mode切换,可选择开启或关闭该功能;Mercusys App可能不支持物理拨片控制,需要根据硬件接口确定是否支持物理控制

3.2.1.5 Load Balance权重配置差异

Tether App和Mercusys App在Traffic Allocation Weight的界面实现上存在细微差异,需要根据对应的权重配置接口来调整滑块交互和数值显示

3.2.1.6 连接类型检测支持差异

Tether App支持Auto Detect自动检测连接类型功能,部分Mercusys设备可能不支持,需要根据设备接口确定是否支持自动检测以及检测类型范围

3.2.1.7 USB设备类型识别差异

Tether App支持3/4G Modem和Phone/Tablet两种USB设备类型自动识别,Mercusys App的支持程度可能不同,需要根据USB接口能力确定设备识别范围

3.2.1.8 IPv6 LAN配置功能差异

Tether App支持完整的IPv6 LAN设置包括SLAAC+Stateless DHCP配置,Mercusys App的IPv6支持可能有差异,需要根据IPv6接口确定支持的配置项

3.2.2 技术需求

3.2.2.1 Internet Mode管理模块统一化

统一Backup模式和Load Balance模式的底层实现逻辑,包含连接状态管理、优先级调度、流量权重分配等核心算法,确保两个App的一致性

3.2.2.2 多连接方式适配层抽象

建立统一的连接方式适配层,封装Ethernet、USB Internet、Hotspot的底层差异,为上层应用提供一致的接口调用方式

3.2.2.3 设备能力动态检测机制

实现设备能力的动态检测和上报机制,让App能够根据设备实际支持的功能动态调整界面展示和功能可用性

3.2.2.4 降低App接入复杂度

App侧尽可能减少需要实现的底层网络配置接口,将复杂的网络管理逻辑下沉到设备端,降低App开发和维护成本


package com.tplink.deco.homepage.adapter;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.ImageSpan;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.tplink.deco.R;
import com.tplink.deco.homepage.viewmodel.ClientViewModel;
import com.tplink.design.list.TPTwoLineItemView;
import com.tplink.design.list.TPTwoLineItemViewHolder;
import com.tplink.design.menu.TPPopupMenu;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;

/**
 * 设备列表适配器
 * 职责:纯UI组件,负责显示设备列表和处理用户交互
 * 不再直接操作数据,所有数据操作都委托给Fragment
 * 移除了模拟数据生成,改为直接使用设备自带的网络数据
 */
public class ClientListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    
    private List<ClientViewModel.DeviceInfo> deviceList = new ArrayList<>();
    private Context context;
    private boolean isOnlineAdapter;

    // 设备操作监听接口
    public interface OnDeviceActionListener {
        void onBlockDevice(ClientViewModel.DeviceInfo device, int position);
        void onDeleteDevice(ClientViewModel.DeviceInfo device, int position);
    }

    private OnDeviceActionListener listener;

    public ClientListAdapter(Context context, boolean isOnlineAdapter) {
        this.context = context;
        this.isOnlineAdapter = isOnlineAdapter;
        System.out.println("ClientListAdapter创建,isOnline: " + isOnlineAdapter);
    }

    public void setOnDeviceActionListener(OnDeviceActionListener listener) {
        this.listener = listener;
    }

    /**
     * 更新设备列表
     * 这个方法会被Fragment调用,传入来自ViewModel的LiveData数据
     */
    public void updateDeviceList(List<ClientViewModel.DeviceInfo> devices) {
        System.out.println("ClientListAdapter.updateDeviceList调用,新设备数量: " + 
            (devices != null ? devices.size() : 0) + ", isOnline: " + isOnlineAdapter);
            
        this.deviceList.clear();
        if (devices != null) {
            this.deviceList.addAll(devices);
        }
        notifyDataSetChanged();
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return TPTwoLineItemViewHolder.create(parent);
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        if (position >= deviceList.size()) return;
        
        TPTwoLineItemView lineItem = (TPTwoLineItemView) holder.itemView;
        ClientViewModel.DeviceInfo device = deviceList.get(position);
        
        // 设置设备类型图标
        if (device.getDeviceType() == 1) { // 电脑
            lineItem.setStartIcon(R.mipmap.laptop3);
        } else { // 手机
            lineItem.setStartIcon(R.mipmap.game3);
        }

        // 设置图标尺寸
        ViewGroup.LayoutParams layoutParams = lineItem.getStartIcon().getLayoutParams();
        int iconSize = (int) context.getResources().getDimension(com.tplink.design.R.dimen.tpds_all_dp_40);
        layoutParams.width = iconSize;
        layoutParams.height = iconSize;
        lineItem.getStartIcon().setLayoutParams(layoutParams);

        lineItem.setTitleText(device.getDeviceName());
        lineItem.showDivider(true);

        // 根据设备状态设置内容和交互
        if (device.isOnline()) {
            setupOnlineDevice(lineItem, device, position);
        } else {
            setupOfflineDevice(lineItem, device, position);
        }
    }

    /**
     * 设置在线设备的显示和交互
     * 使用设备自带的网络数据,不再生成模拟数据
     */
    private void setupOnlineDevice(TPTwoLineItemView lineItem, ClientViewModel.DeviceInfo device, int position) {
        // 直接使用设备自带的网络数据
        int signalStrength = device.getSignalStrength();
        int downloadSpeed = device.getDownloadSpeed();
        int uploadSpeed = device.getUploadSpeed();
        
        System.out.println("设备 " + device.getDeviceName() + " 网络数据: " +
            "信号=" + signalStrength + ", 下载=" + downloadSpeed + "KB/s, 上传=" + uploadSpeed + "KB/s");
        
        lineItem.setEndIcon(createSignalIcon(signalStrength));
        setOnlineContent(uploadSpeed, downloadSpeed, lineItem);
        
        // 设置长按菜单 - 在线设备只能拉黑
        lineItem.setOnLongClickListener(v -> {
            TPPopupMenu popupMenu = new TPPopupMenu(context, v, R.menu.menu_block)
                    .setIconEnable(true);
            popupMenu.setOnMenuItemClickListener(item -> {
                if (item.getItemId() == R.id.block) {
                    showBlockConfirmDialog(device, position);
                    return true;
                }
                return false;
            });
            popupMenu.show();
            return true;
        });
    }

    /**
     * 设置离线设备的显示和交互
     * 使用设备自带的最后在线时间
     */
    private void setupOfflineDevice(TPTwoLineItemView lineItem, ClientViewModel.DeviceInfo device, int position) {
        lineItem.setEndIcon(R.drawable.next);
        
        // 使用设备自带的最后在线时间
        long lastOnlineTime = device.getLastOnlineTime();
        setOfflineContent(lastOnlineTime, lineItem);
        
        System.out.println("离线设备 " + device.getDeviceName() + " 最后在线: " + new Date(lastOnlineTime));
        
        // 设置长按菜单 - 离线设备可以拉黑或删除
        lineItem.setOnLongClickListener(v -> {
            TPPopupMenu popupMenu = new TPPopupMenu(context, v, R.menu.menu_delete)
                    .setIconEnable(true);
            popupMenu.setOnMenuItemClickListener(item -> {
                if (item.getItemId() == R.id.block) {
                    showBlockConfirmDialog(device, position);
                    return true;
                } else if (item.getItemId() == R.id.delete) {
                    showDeleteConfirmDialog(device, position);
                    return true;
                }
                return false;
            });
            popupMenu.show();
            return true;
        });
    }

    /**
     * 显示拉黑确认对话框
     */
    private void showBlockConfirmDialog(ClientViewModel.DeviceInfo device, int position) {
        new MaterialAlertDialogBuilder(context)
                .setTitle("拉黑设备")
                .setMessage("确定要将 \"" + device.getDeviceName() + "\" 加入黑名单吗?\n" +
                    "设备MAC地址: " + device.getMacAddress())
                .setPositiveButton("确定", (dialog, which) -> {
                    if (listener != null) {
                        System.out.println("适配器:准备拉黑设备 " + device.getDeviceName());
                        listener.onBlockDevice(device, position);
                    }
                })
                .setNegativeButton("取消", null)
                .show();
    }

    /**
     * 显示删除确认对话框
     */
    private void showDeleteConfirmDialog(ClientViewModel.DeviceInfo device, int position) {
        new MaterialAlertDialogBuilder(context)
                .setTitle("删除设备")
                .setMessage("确定要永久删除 \"" + device.getDeviceName() + "\" 吗?\n" +
                    "删除后设备将从所有列表中移除。")
                .setPositiveButton("确定", (dialog, which) -> {
                    if (listener != null) {
                        listener.onDeleteDevice(device, position);
                    }
                })
                .setNegativeButton("取消", null)
                .show();
    }

    @Override
    public int getItemCount() {
        return deviceList.size();
    }

    /**
     * 设置在线设备的内容显示(上传下载速度)
     * 直接使用传入的速度参数,不再生成模拟数据
     */
    private void setOnlineContent(int uploadRate, int downloadRate, TPTwoLineItemView lineItem) {
        TextView textView = lineItem.getContent();
        android.graphics.Paint.FontMetrics fm = textView.getPaint().getFontMetrics();
        float textHeight = fm.descent - fm.ascent;
        
        String downloadText = " " + downloadRate + "KB/s";
        String spaceText = "  ";
        String uploadText = " " + uploadRate + "KB/s";
        SpannableString spannable = new SpannableString(downloadText + spaceText + uploadText);

        Drawable uploadIconDrawable = ContextCompat.getDrawable(context, R.drawable.upload);
        Drawable downloadIconDrawable = ContextCompat.getDrawable(context, R.drawable.download);

        if (uploadIconDrawable != null && downloadIconDrawable != null) {
            float iconAspectRatio = (float) uploadIconDrawable.getIntrinsicWidth() / uploadIconDrawable.getIntrinsicHeight();
            int scaleWidth = (int) (textHeight * iconAspectRatio);
            
            downloadIconDrawable.setBounds(0, 0, scaleWidth, (int) textHeight);
            uploadIconDrawable.setBounds(0, 0, scaleWidth, (int) textHeight);

            ImageSpan downloadIconSpan = new ImageSpan(downloadIconDrawable, ImageSpan.ALIGN_BOTTOM);
            ImageSpan uploadIconSpan = new ImageSpan(uploadIconDrawable, ImageSpan.ALIGN_BOTTOM);

            spannable.setSpan(downloadIconSpan, 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            spannable.setSpan(uploadIconSpan,
                    downloadText.length() + spaceText.length(),
                    downloadText.length() + spaceText.length() + 1,
                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        }

        textView.setText(spannable);
        lineItem.getContent().setVisibility(View.VISIBLE);
    }

    /**
     * 设置离线设备的内容显示(最后在线时间)
     * 使用传入的时间戳,不再生成固定时间
     */
    @SuppressLint("SimpleDateFormat")
    private void setOfflineContent(long lastOnlineTime, TPTwoLineItemView lineItem) {
        try {
            SimpleDateFormat timeFormat = new SimpleDateFormat(context.getString(R.string.clients_time_format_pattern));
            String timeString = timeFormat.format(new Date(lastOnlineTime));
            String contentText = context.getString(R.string.clients_hint_current_online_time, timeString);
            lineItem.setContentText(contentText);
        } catch (Exception e) {
            // 如果格式化失败,显示相对时间
            long timeDiff = System.currentTimeMillis() - lastOnlineTime;
            String relativeTime;
            if (timeDiff < 60 * 60 * 1000) { // 小于1小时
                relativeTime = "1小时前";
            } else if (timeDiff < 24 * 60 * 60 * 1000) { // 小于24小时
                long hours = timeDiff / (60 * 60 * 1000);
                relativeTime = hours + "小时前";
            } else { // 超过24小时
                long days = timeDiff / (24 * 60 * 60 * 1000);
                relativeTime = days + "天前";
            }
            lineItem.setContentText("上次在线: " + relativeTime);
        }
    }

    /**
     * 创建信号强度图标
     * 使用传入的信号强度,不再使用数组索引
     */
    private LayerDrawable createSignalIcon(int strength) {
        Drawable signalIcon;
        switch (strength) {
            case 1:
                signalIcon = ContextCompat.getDrawable(context, R.drawable.wifi1);
                break;
            case 2:
                signalIcon = ContextCompat.getDrawable(context, R.drawable.wifi2);
                break;
            case 3:
                signalIcon = ContextCompat.getDrawable(context, R.drawable.wifi3);
                break;
            case 4:
            default:
                signalIcon = ContextCompat.getDrawable(context, R.drawable.wifi4);
                break;
        }

        Drawable nextIcon = ContextCompat.getDrawable(context, R.drawable.next);
        if (signalIcon == null || nextIcon == null) {
            return null;
        }

        Drawable[] layers = {signalIcon, nextIcon};
        LayerDrawable layerDrawable = new LayerDrawable(layers);

        int iconSize = (int) context.getResources().getDimension(com.tplink.design.R.dimen.tpds_all_dp_20);
        int spacing = (int) context.getResources().getDimension(com.tplink.design.R.dimen.tpds_all_dp_20);

        layerDrawable.setLayerSize(0, iconSize, iconSize);
        layerDrawable.setLayerGravity(0, Gravity.CENTER_HORIZONTAL);
        layerDrawable.setLayerGravity(1, Gravity.CENTER_HORIZONTAL);
        layerDrawable.setLayerInset(0, -spacing, 0, 0, 0);
        layerDrawable.setLayerInset(1, 2 * spacing, 0, 0, 0);

        return layerDrawable;
    }
}
package com.tplink.deco.homepage.viewmodel;

import androidx.lifecycle.LiveData;
import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
import com.tplink.deco.homepage.repository.DeviceRepository;
import java.util.List;

/**
 * 客户端设备ViewModel
 * 职责:作为UI和Repository之间的桥梁,提供UI需要的数据和操作方法
 * 所有ViewModel实例都使用同一个Repository,确保数据共享
 */
public class ClientViewModel extends ViewModel {
    
    private DeviceRepository repository;
    
    // 兼容原有代码的数据模型类
    public static class DeviceInfo {
        private String deviceName;
        private String macAddress;
        private int deviceType;
        private boolean isOnline;
        private boolean isGuestNetwork;
        
        // 网络相关字段
        private int signalStrength;
        private int downloadSpeed;
        private int uploadSpeed;
        private long lastOnlineTime;
        
        public DeviceInfo(String deviceName, String macAddress, int deviceType, 
                         boolean isOnline, boolean isGuestNetwork,
                         int signalStrength, int downloadSpeed, int uploadSpeed, long lastOnlineTime) {
            this.deviceName = deviceName;
            this.macAddress = macAddress;
            this.deviceType = deviceType;
            this.isOnline = isOnline;
            this.isGuestNetwork = isGuestNetwork;
            this.signalStrength = signalStrength;
            this.downloadSpeed = downloadSpeed;
            this.uploadSpeed = uploadSpeed;
            this.lastOnlineTime = lastOnlineTime;
        }
        
        // 从Repository的DeviceInfo转换
        public DeviceInfo(DeviceRepository.DeviceInfo repoDevice) {
            this.deviceName = repoDevice.getDeviceName();
            this.macAddress = repoDevice.getMacAddress();
            this.deviceType = repoDevice.getDeviceType();
            this.isOnline = repoDevice.isOnline();
            this.isGuestNetwork = repoDevice.isGuestNetwork();
            this.signalStrength = repoDevice.getSignalStrength();
            this.downloadSpeed = repoDevice.getDownloadSpeed();
            this.uploadSpeed = repoDevice.getUploadSpeed();
            this.lastOnlineTime = repoDevice.getLastOnlineTime();
        }
        
        // Getters
        public String getDeviceName() { return deviceName; }
        public String getMacAddress() { return macAddress; }
        public int getDeviceType() { return deviceType; }
        public boolean isOnline() { return isOnline; }
        public boolean isGuestNetwork() { return isGuestNetwork; }
        public int getSignalStrength() { return signalStrength; }
        public int getDownloadSpeed() { return downloadSpeed; }
        public int getUploadSpeed() { return uploadSpeed; }
        public long getLastOnlineTime() { return lastOnlineTime; }
    }
    
    public static class BlacklistItem {
        private String displayName;
        private String macAddress;
        private boolean isMacOnly;
        
        // 从Repository的BlacklistItem转换
        public BlacklistItem(DeviceRepository.BlacklistItem repoItem) {
            this.displayName = repoItem.getDisplayName();
            this.macAddress = repoItem.getMacAddress();
            this.isMacOnly = repoItem.isMacOnly();
        }
        
        public String getDisplayName() { return displayName; }
        public String getMacAddress() { return macAddress; }
        public boolean isMacOnly() { return isMacOnly; }
        public String getIdentifier() { return isMacOnly ? macAddress : displayName; }
    }

    public ClientViewModel() {
        // 获取单例Repository实例
        repository = DeviceRepository.getInstance();
        System.out.println("ClientViewModel创建,Repository实例: " + repository.hashCode());
    }

    // ========== 设备操作方法 ==========
    
    /**
     * 拉黑设备 - 核心方法
     * 这个方法会被Fragment调用,然后数据会自动同步到BlacklistActivity
     */
    public boolean blockDevice(String deviceName) {
        System.out.println("ClientViewModel.blockDevice调用: " + deviceName);
        return repository.blockDevice(deviceName);
    }
    
    /**
     * 通过MAC地址拉黑
     */
    public boolean blockDeviceByMac(String macAddress) {
        return repository.blockDeviceByMac(macAddress);
    }
    
    /**
     * 解除拉黑
     */
    public boolean unblockDevice(String identifier) {
        return repository.unblockDevice(identifier);
    }
    
    /**
     * 删除设备
     */
    public boolean deleteDevice(String deviceName) {
        return repository.deleteDevice(deviceName);
    }
    
    /**
     * 删除黑名单项目
     */
    public boolean deleteBlacklistDevice(String identifier) {
        return repository.deleteBlacklistItem(identifier);
    }

    // ========== LiveData 获取方法 ==========
    // 这些方法返回的LiveData都来自同一个Repository,确保数据共享

    /**
     * 获取黑名单LiveData
     * BlacklistActivity会观察这个LiveData
     */
    public LiveData<List<BlacklistItem>> getBlacklistItems() {
        return Transformations.map(repository.getBlacklistItems(), repoItems -> {
            List<BlacklistItem> viewModelItems = new java.util.ArrayList<>();
            if (repoItems != null) {
                for (DeviceRepository.BlacklistItem repoItem : repoItems) {
                    viewModelItems.add(new BlacklistItem(repoItem));
                }
            }
            System.out.println("黑名单LiveData转换,数量: " + viewModelItems.size());
            return viewModelItems;
        });
    }

    /**
     * 获取主网络在线设备
     */
    public LiveData<List<DeviceInfo>> getMainOnlineDevices() {
        return Transformations.map(repository.getMainOnlineDevices(), repoDevices -> {
            List<DeviceInfo> viewModelDevices = new java.util.ArrayList<>();
            if (repoDevices != null) {
                for (DeviceRepository.DeviceInfo repoDevice : repoDevices) {
                    viewModelDevices.add(new DeviceInfo(repoDevice));
                }
            }
            return viewModelDevices;
        });
    }

    /**
     * 获取主网络离线设备
     */
    public LiveData<List<DeviceInfo>> getMainOfflineDevices() {
        return Transformations.map(repository.getMainOfflineDevices(), repoDevices -> {
            List<DeviceInfo> viewModelDevices = new java.util.ArrayList<>();
            if (repoDevices != null) {
                for (DeviceRepository.DeviceInfo repoDevice : repoDevices) {
                    viewModelDevices.add(new DeviceInfo(repoDevice));
                }
            }
            return viewModelDevices;
        });
    }

    /**
     * 获取访客网络设备
     */
    public LiveData<List<DeviceInfo>> getGuestDevices() {
        return Transformations.map(repository.getGuestDevices(), repoDevices -> {
            List<DeviceInfo> viewModelDevices = new java.util.ArrayList<>();
            if (repoDevices != null) {
                for (DeviceRepository.DeviceInfo repoDevice : repoDevices) {
                    viewModelDevices.add(new DeviceInfo(repoDevice));
                }
            }
            return viewModelDevices;
        });
    }

    /**
     * 获取刷新状态
     */
    public LiveData<Boolean> getRefreshingState() {
        return repository.getRefreshingState();
    }

    // ========== 兼容性方法 - 返回数量 ==========
    // 这些方法是为了兼容原有代码结构

    public LiveData<Integer> getMainOnlineCount() {
        return Transformations.map(getMainOnlineDevices(), devices -> 
            devices != null ? devices.size() : 0);
    }

    public LiveData<Integer> getMainOfflineCount() {
        return Transformations.map(getMainOfflineDevices(), devices -> 
            devices != null ? devices.size() : 0);
    }

    public LiveData<Integer> getGuestCount() {
        return Transformations.map(getGuestDevices(), devices -> 
            devices != null ? devices.size() : 0);
    }

    // ========== 当前数量获取方法 ==========

    public int getCurrentMainOnlineCount() {
        return repository.getCurrentMainOnlineCount();
    }

    public int getCurrentMainOfflineCount() {
        return repository.getCurrentMainOfflineCount();
    }

    public int getCurrentGuestCount() {
        return repository.getCurrentGuestCount();
    }

    public int getCurrentBlacklistCount() {
        return repository.getCurrentBlacklistCount();
    }

    // ========== 其他方法 ==========

    /**
     * 刷新所有数据
     */
    public void refreshData() {
        repository.refreshData();
    }

    // ========== 兼容性方法(废弃但保留) ==========
    // 这些方法保留是为了不破坏现有代码,但实际上不再需要手动更新计数

    @Deprecated
    public void updateMainOnlineCount(int count) {
        // 不再需要手动更新,Repository会自动处理
        System.out.println("updateMainOnlineCount调用被忽略,由Repository自动管理");
    }

    @Deprecated
    public void updateMainOfflineCount(int count) {
        // 不再需要手动更新,Repository会自动处理
        System.out.println("updateMainOfflineCount调用被忽略,由Repository自动管理");
    }

    @Deprecated
    public void updateGuestCount(int count) {
        // 不再需要手动更新,Repository会自动处理
        System.out.println("updateGuestCount调用被忽略,由Repository自动管理");
    }

    // 兼容方法:blockMainDevice 和 blockGuestDevice
    public boolean blockMainDevice(String deviceName) {
        return blockDevice(deviceName);
    }

    public boolean blockGuestDevice(String deviceName) {
        return blockDevice(deviceName);
    }

    public boolean deleteGuestDevice(String deviceName) {
        return deleteDevice(deviceName);
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        System.out.println("ClientViewModel被清理");
    }
}
package com.tplink.deco.homepage.repository;

import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
import java.util.Random;

/**
 * 设备数据仓库 - 单例模式
 * 负责管理所有设备数据,是唯一的数据源
 * 确保Fragment和Activity之间的数据共享
 */
public class DeviceRepository {
    private static volatile DeviceRepository INSTANCE;
    
    // 设备数据模型
    public static class DeviceInfo {
        private String deviceName;
        private String macAddress;
        private int deviceType; // 0-手机, 1-电脑
        private boolean isOnline;
        private boolean isGuestNetwork;
        
        // 网络相关字段
        private int signalStrength;    // 1-4
        private int downloadSpeed;     // KB/s
        private int uploadSpeed;       // KB/s
        private long lastOnlineTime;   // 最后在线时间
        
        public DeviceInfo(String deviceName, String macAddress, int deviceType, 
                         boolean isOnline, boolean isGuestNetwork,
                         int signalStrength, int downloadSpeed, int uploadSpeed, long lastOnlineTime) {
            this.deviceName = deviceName;
            this.macAddress = macAddress;
            this.deviceType = deviceType;
            this.isOnline = isOnline;
            this.isGuestNetwork = isGuestNetwork;
            this.signalStrength = signalStrength;
            this.downloadSpeed = downloadSpeed;
            this.uploadSpeed = uploadSpeed;
            this.lastOnlineTime = lastOnlineTime;
        }
        
        // Getters
        public String getDeviceName() { return deviceName; }
        public String getMacAddress() { return macAddress; }
        public int getDeviceType() { return deviceType; }
        public boolean isOnline() { return isOnline; }
        public boolean isGuestNetwork() { return isGuestNetwork; }
        public int getSignalStrength() { return signalStrength; }
        public int getDownloadSpeed() { return downloadSpeed; }
        public int getUploadSpeed() { return uploadSpeed; }
        public long getLastOnlineTime() { return lastOnlineTime; }
        
        // Setters
        public void setOnline(boolean online) { isOnline = online; }
        public void setDeviceType(int deviceType) { this.deviceType = deviceType; }
        public void setSignalStrength(int signalStrength) { this.signalStrength = signalStrength; }
        public void setDownloadSpeed(int downloadSpeed) { this.downloadSpeed = downloadSpeed; }
        public void setUploadSpeed(int uploadSpeed) { this.uploadSpeed = uploadSpeed; }
        
        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (obj == null || getClass() != obj.getClass()) return false;
            DeviceInfo that = (DeviceInfo) obj;
            return deviceName.equals(that.deviceName) && macAddress.equals(that.macAddress);
        }
        
        @Override
        public int hashCode() {
            return deviceName.hashCode() + macAddress.hashCode();
        }
    }

    // 黑名单项目模型
    public static class BlacklistItem {
        private String displayName;
        private String macAddress;
        private boolean isMacOnly;

        public BlacklistItem(String deviceName, String macAddress) {
            this.displayName = deviceName;
            this.macAddress = macAddress;
            this.isMacOnly = false;
        }

        public BlacklistItem(String macAddress) {
            this.displayName = macAddress;
            this.macAddress = macAddress;
            this.isMacOnly = true;
        }

        public String getDisplayName() { return displayName; }
        public String getMacAddress() { return macAddress; }
        public boolean isMacOnly() { return isMacOnly; }
        public String getIdentifier() { return isMacOnly ? macAddress : displayName; }
        
        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (obj == null || getClass() != obj.getClass()) return false;
            BlacklistItem that = (BlacklistItem) obj;
            return macAddress.equals(that.macAddress);
        }
    }

    // 数据存储
    private List<DeviceInfo> allDevices = new ArrayList<>();
    private List<BlacklistItem> blacklistItems = new ArrayList<>();
    
    // LiveData - 供外部观察
    private MutableLiveData<List<DeviceInfo>> allDevicesLiveData = new MutableLiveData<>();
    private MutableLiveData<List<BlacklistItem>> blacklistLiveData = new MutableLiveData<>();
    private MutableLiveData<Boolean> isRefreshing = new MutableLiveData<>(false);
    
    // 随机数生成器用于模拟数据
    private Random random = new Random();

    private DeviceRepository() {
        initializeData();
    }

    // 单例获取方法
    public static DeviceRepository getInstance() {
        if (INSTANCE == null) {
            synchronized (DeviceRepository.class) {
                if (INSTANCE == null) {
                    INSTANCE = new DeviceRepository();
                }
            }
        }
        return INSTANCE;
    }

    // 初始化设备数据
    private void initializeData() {
        allDevices.clear();
        blacklistItems.clear();
        
        long currentTime = System.currentTimeMillis();
        
        // 主网络在线设备 - 包含完整网络信息
        allDevices.add(new DeviceInfo("Melanie's iPhone", generateMacAddress("iPhone"), 0, true, false,
                generateSignalStrength(), generateDownloadSpeed(), generateUploadSpeed(), currentTime));
                
        allDevices.add(new DeviceInfo("Melanie's iMac", generateMacAddress("iMac"), 1, true, false,
                generateSignalStrength(), generateDownloadSpeed(), generateUploadSpeed(), currentTime));
                
        allDevices.add(new DeviceInfo("Melanie's iPad", generateMacAddress("iPad"), 0, true, false,
                generateSignalStrength(), generateDownloadSpeed(), generateUploadSpeed(), currentTime));
                
        allDevices.add(new DeviceInfo("Office PC", generateMacAddress("PC"), 1, true, false,
                generateSignalStrength(), generateDownloadSpeed(), generateUploadSpeed(), currentTime));
        
        // 主网络离线设备 - 只有最后在线时间,网络数据为0
        allDevices.add(new DeviceInfo("John's MacBook", generateMacAddress("MacBook"), 1, false, false,
                0, 0, 0, currentTime - 24 * 60 * 60 * 1000)); // 24小时前
                
        allDevices.add(new DeviceInfo("Smart TV", generateMacAddress("TV"), 1, false, false,
                0, 0, 0, currentTime - 48 * 60 * 60 * 1000)); // 48小时前
                
        allDevices.add(new DeviceInfo("Gaming Console", generateMacAddress("Console"), 1, false, false,
                0, 0, 0, currentTime - 12 * 60 * 60 * 1000)); // 12小时前
        
        // 访客网络设备 - 包含网络信息
        allDevices.add(new DeviceInfo("Guest Phone 1", generateMacAddress("GuestPhone1"), 0, true, true,
                generateSignalStrength(), generateDownloadSpeed(), generateUploadSpeed(), currentTime));
                
        allDevices.add(new DeviceInfo("Guest Laptop", generateMacAddress("GuestLaptop"), 1, true, true,
                generateSignalStrength(), generateDownloadSpeed(), generateUploadSpeed(), currentTime));
        
        // 更新LiveData
        updateLiveData();
        
        System.out.println("设备数据初始化完成,总设备数:" + allDevices.size());
    }

    // 数据生成辅助方法
    private int generateSignalStrength() {
        return random.nextInt(4) + 1; // 1-4
    }

    private int generateDownloadSpeed() {
        return random.nextInt(80) + 20; // 20-100 KB/s
    }

    private int generateUploadSpeed() {
        return random.nextInt(40) + 10; // 10-50 KB/s
    }

    // 生成MAC地址
    private String generateMacAddress(String deviceIdentifier) {
        int hash = Math.abs(deviceIdentifier.hashCode());
        return String.format("%02X-%02X-%02X-%02X-%02X-%02X", 
                (hash >> 20) & 0xFF, (hash >> 16) & 0xFF, 
                (hash >> 12) & 0xFF, (hash >> 8) & 0xFF, 
                (hash >> 4) & 0xFF, hash & 0xFF);
    }

    // 更新LiveData
    private void updateLiveData() {
        allDevicesLiveData.setValue(new ArrayList<>(allDevices));
        blacklistLiveData.setValue(new ArrayList<>(blacklistItems));
    }

    // ========== 核心功能方法 ==========
    
    /**
     * 拉黑设备 - 核心方法
     * 从设备列表移除设备并添加到黑名单
     */
    public boolean blockDevice(String deviceName) {
        System.out.println("准备拉黑设备: " + deviceName);
        
        // 查找要拉黑的设备
        DeviceInfo deviceToBlock = null;
        for (DeviceInfo device : allDevices) {
            if (device.getDeviceName().equals(deviceName)) {
                deviceToBlock = device;
                break;
            }
        }
        
        if (deviceToBlock != null) {
            // 检查是否已在黑名单中
            boolean alreadyBlocked = false;
            for (BlacklistItem item : blacklistItems) {
                if (item.getIdentifier().equals(deviceName)) {
                    alreadyBlocked = true;
                    break;
                }
            }
            
            if (!alreadyBlocked) {
                // 从设备列表移除
                allDevices.remove(deviceToBlock);
                
                // 添加到黑名单
                BlacklistItem newBlacklistItem = new BlacklistItem(
                    deviceToBlock.getDeviceName(), 
                    deviceToBlock.getMacAddress()
                );
                blacklistItems.add(newBlacklistItem);
                
                // 更新LiveData,通知所有观察者
                updateLiveData();
                
                System.out.println("设备拉黑成功: " + deviceName + 
                    ", 剩余设备数: " + allDevices.size() + 
                    ", 黑名单数: " + blacklistItems.size());
                
                return true;
            } else {
                System.out.println("设备已在黑名单中: " + deviceName);
            }
        } else {
            System.out.println("未找到设备: " + deviceName);
        }
        
        return false;
    }

    /**
     * 通过MAC地址拉黑
     */
    public boolean blockDeviceByMac(String macAddress) {
        // 检查MAC地址格式
        if (!isValidMacAddress(macAddress)) {
            return false;
        }
        
        // 检查是否已存在
        for (BlacklistItem item : blacklistItems) {
            if (item.getMacAddress().equals(macAddress)) {
                return false; // 已存在
            }
        }
        
        // 添加到黑名单
        blacklistItems.add(new BlacklistItem(macAddress));
        updateLiveData();
        
        System.out.println("MAC地址拉黑成功: " + macAddress);
        return true;
    }

    /**
     * 解除拉黑
     */
    public boolean unblockDevice(String identifier) {
        BlacklistItem itemToRemove = null;
        
        for (BlacklistItem item : blacklistItems) {
            if (item.getIdentifier().equals(identifier)) {
                itemToRemove = item;
                break;
            }
        }
        
        if (itemToRemove != null) {
            blacklistItems.remove(itemToRemove);
            
            // 如果是设备名称(非纯MAC),重新添加到设备列表
            if (!itemToRemove.isMacOnly()) {
                long currentTime = System.currentTimeMillis();
                DeviceInfo restoredDevice = new DeviceInfo(
                    itemToRemove.getDisplayName(),
                    itemToRemove.getMacAddress(),
                    0, // 默认手机类型
                    false, // 离线状态
                    false, // 主网络
                    0, 0, 0, currentTime - 24 * 60 * 60 * 1000 // 默认24小时前离线
                );
                allDevices.add(restoredDevice);
            }
            
            updateLiveData();
            
            System.out.println("设备解除拉黑: " + identifier);
            return true;
        }
        
        return false;
    }

    /**
     * 删除设备(永久删除)
     */
    public boolean deleteDevice(String deviceName) {
        Iterator<DeviceInfo> iterator = allDevices.iterator();
        boolean removed = false;
        
        while (iterator.hasNext()) {
            DeviceInfo device = iterator.next();
            if (device.getDeviceName().equals(deviceName)) {
                iterator.remove();
                removed = true;
                break;
            }
        }
        
        if (removed) {
            updateLiveData();
            System.out.println("设备删除成功: " + deviceName);
        }
        
        return removed;
    }

    /**
     * 删除黑名单项目
     */
    public boolean deleteBlacklistItem(String identifier) {
        Iterator<BlacklistItem> iterator = blacklistItems.iterator();
        boolean removed = false;
        
        while (iterator.hasNext()) {
            BlacklistItem item = iterator.next();
            if (item.getIdentifier().equals(identifier)) {
                iterator.remove();
                removed = true;
                break;
            }
        }
        
        if (removed) {
            updateLiveData();
            System.out.println("黑名单项目删除: " + identifier);
        }
        
        return removed;
    }

    // ========== LiveData 获取方法 ==========
    
    public LiveData<List<BlacklistItem>> getBlacklistItems() {
        return blacklistLiveData;
    }

    public LiveData<List<DeviceInfo>> getAllDevices() {
        return allDevicesLiveData;
    }

    public LiveData<List<DeviceInfo>> getMainOnlineDevices() {
        return Transformations.map(allDevicesLiveData, devices -> {
            List<DeviceInfo> result = new ArrayList<>();
            if (devices != null) {
                for (DeviceInfo device : devices) {
                    if (!device.isGuestNetwork() && device.isOnline()) {
                        result.add(device);
                    }
                }
            }
            return result;
        });
    }

    public LiveData<List<DeviceInfo>> getMainOfflineDevices() {
        return Transformations.map(allDevicesLiveData, devices -> {
            List<DeviceInfo> result = new ArrayList<>();
            if (devices != null) {
                for (DeviceInfo device : devices) {
                    if (!device.isGuestNetwork() && !device.isOnline()) {
                        result.add(device);
                    }
                }
            }
            return result;
        });
    }

    public LiveData<List<DeviceInfo>> getGuestDevices() {
        return Transformations.map(allDevicesLiveData, devices -> {
            List<DeviceInfo> result = new ArrayList<>();
            if (devices != null) {
                for (DeviceInfo device : devices) {
                    if (device.isGuestNetwork()) {
                        result.add(device);
                    }
                }
            }
            return result;
        });
    }

    public LiveData<Boolean> getRefreshingState() {
        return isRefreshing;
    }

    // ========== 辅助方法 ==========
    
    /**
     * 刷新数据
     */
    public void refreshData() {
        isRefreshing.setValue(true);
        
        // 模拟网络请求
        new android.os.Handler().postDelayed(() -> {
            // 刷新时重新生成网络数据
            for (DeviceInfo device : allDevices) {
                if (device.isOnline()) {
                    device.setSignalStrength(generateSignalStrength());
                    device.setDownloadSpeed(generateDownloadSpeed());
                    device.setUploadSpeed(generateUploadSpeed());
                }
            }
            updateLiveData();
            isRefreshing.setValue(false);
        }, 1500);
    }

    /**
     * 验证MAC地址格式
     */
    private boolean isValidMacAddress(String mac) {
        return mac != null && mac.matches("^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$");
    }

    /**
     * 获取当前统计数据
     */
    public int getCurrentMainOnlineCount() {
        int count = 0;
        for (DeviceInfo device : allDevices) {
            if (!device.isGuestNetwork() && device.isOnline()) {
                count++;
            }
        }
        return count;
    }

    public int getCurrentMainOfflineCount() {
        int count = 0;
        for (DeviceInfo device : allDevices) {
            if (!device.isGuestNetwork() && !device.isOnline()) {
                count++;
            }
        }
        return count;
    }

    public int getCurrentGuestCount() {
        int count = 0;
        for (DeviceInfo device : allDevices) {
            if (device.isGuestNetwork()) {
                count++;
            }
        }
        return count;
    }

    public int getCurrentBlacklistCount() {
        return blacklistItems.size();
    }
}