TP-Link Deco App Development I

78 阅读22分钟

TP-Link Deco App Development

Deco App 完整项目结构设计

一、项目根目录结构

DecoApp/
├── app/
│   ├── src/
│   │   ├── main/
│   │   │   ├── java/com/yourcompany/deco/    # Java/Kotlin源代码
│   │   │   ├── res/                          # 资源文件
│   │   │   └── AndroidManifest.xml           # 应用配置清单
│   │   └── test/                             # 单元测试
│   └── build.gradle                          # 模块构建配置
├── gradle/                                   # Gradle配置
└── build.gradle                              # 项目构建配置

二、Java源代码目录详细结构

java/com/yourcompany/deco/
├── DecoApplication.java          # 全局Application类
├── base/                         # 基类文件夹
├── data/                         # 数据层(Model)
├── ui/                          # 界面层(View + ViewModel)
├── utils/                       # 工具类
└── widget/                      # 自定义控件
1. base/ 文件夹

作用:存放所有基类,减少代码重复

base/
├── BaseActivity.java         # Activity基类:统一处理loading、错误提示等
├── BaseViewModel.java        # ViewModel基类:统一处理LiveData
├── BaseDialog.java          # Dialog基类:统一样式和动画
└── BaseRepository.java      # Repository基类:统一错误处理
2. data/ 文件夹

作用:MVVM的Model层,处理所有数据相关操作

data/
├── api/                     # 网络接口定义
│   ├── ApiService.java      # 所有API接口定义(登录、获取设备等)
│   └── ApiConstants.java    # API常量(BaseUrl、错误码等)
│
├── model/                   # 数据模型
│   ├── request/            # 请求数据模型
│   │   ├── LoginRequest.java       # 登录请求参数
│   │   └── RegisterRequest.java    # 注册请求参数
│   │
│   ├── response/           # 响应数据模型
│   │   ├── LoginResponse.java      # 登录响应数据
│   │   ├── DeviceResponse.java     # 设备信息响应
│   │   └── BaseResponse.java       # 响应基类
│   │
│   └── entity/             # 实体类
│       ├── User.java              # 用户信息
│       ├── Device.java            # 设备信息
│       └── Region.java            # 地区信息
│
├── network/                # 网络配置
│   ├── NetworkManager.java        # OkHttp配置和初始化
│   ├── HeaderInterceptor.java     # 请求头拦截器
│   └── LoggingInterceptor.java    # 日志拦截器
│
├── repository/             # 数据仓库
│   ├── AuthRepository.java        # 认证相关数据处理
│   └── DeviceRepository.java      # 设备相关数据处理
│
└── local/                  # 本地存储
    ├── PreferenceHelper.java      # SharedPreferences管理
    └── DatabaseHelper.java        # 数据库管理(如需要)
3. ui/ 文件夹

作用:MVVM的View和ViewModel层,所有界面相关代码

ui/
├── splash/                 # 启动页模块
│   ├── SplashActivity.java        # 显示logo,判断登录状态
│   └── SplashViewModel.java       # 处理启动逻辑
│
├── agreement/              # 用户协议模块
│   ├── AgreementActivity.java     # 显示协议内容,处理同意/拒绝
│   └── AgreementViewModel.java    # 保存用户协议状态
│
├── auth/                   # 认证相关模块
│   ├── choice/            # 登录/注册选择
│   │   └── AuthChoiceActivity.java    # 显示登录/注册按钮
│   │
│   ├── login/             # 登录模块
│   │   ├── LoginActivity.java         # 登录界面,包含地区选择
│   │   └── LoginViewModel.java        # 处理登录逻辑,保存token
│   │
│   ├── register/          # 注册模块
│   │   ├── RegisterActivity.java      # 注册界面,邮箱输入、密码设置
│   │   └── RegisterViewModel.java     # 处理注册逻辑
│   │
│   └── verification/      # 邮箱验证模块
│       ├── EmailVerificationActivity.java   # 验证提示界面
│       └── EmailVerificationViewModel.java  # 处理验证状态
│
├── main/                  # 主页模块
│   ├── MainActivity.java          # 主界面框架,底部导航
│   ├── MainViewModel.java         # 主页数据处理
│   │
│   ├── home/              # 首页(Network标签)
│   │   ├── HomeFragment.java     # 显示网络状态、设备列表
│   │   ├── HomeViewModel.java    # 获取设备数据
│   │   └── DeviceAdapter.java    # 设备列表适配器
│   │
│   ├── family/            # Family标签
│   │   ├── FamilyFragment.java
│   │   └── FamilyViewModel.java
│   │
│   ├── smart/             # Smart标签
│   │   ├── SmartFragment.java
│   │   └── SmartViewModel.java
│   │
│   ├── discover/          # Discover标签
│   │   ├── DiscoverFragment.java
│   │   └── DiscoverViewModel.java
│   │
│   └── more/              # More标签
│       ├── MoreFragment.java
│       └── MoreViewModel.java
│
└── dialog/                # 对话框
    ├── RegionDialog.java          # 地区选择对话框
    ├── ProblemHelpDialog.java     # 问题帮助对话框
    └── LoadingDialog.java         # 加载中对话框
4. utils/ 文件夹

作用:通用工具类

utils/
├── NetworkUtils.java      # 网络状态检查
├── ValidationUtils.java   # 输入验证(邮箱、密码格式)
├── DeviceUtils.java       # 获取设备信息(UUID、型号等)
├── ToastUtils.java        # Toast统一管理
├── LogUtils.java          # 日志工具
└── Constants.java         # 全局常量
5. widget/ 文件夹

作用:自定义View控件

widget/
├── PasswordEditText.java   # 密码输入框(显示/隐藏功能)
├── LoadingButton.java      # 带加载状态的按钮
└── CircleImageView.java    # 圆形图片(用户头像等)

三、资源文件目录结构

res/
├── layout/                 # 布局文件
│   ├── activity_splash.xml
│   ├── activity_agreement.xml
│   ├── activity_auth_choice.xml
│   ├── activity_login.xml
│   ├── activity_register.xml
│   ├── activity_email_verification.xml
│   ├── activity_main.xml
│   ├── fragment_home.xml
│   ├── dialog_region_select.xml
│   ├── dialog_problem_help.xml
│   ├── item_device.xml            # 设备列表项
│   └── item_region.xml            # 地区列表项
│
├── drawable/               # 图片资源
│   ├── ic_logo.png               # Deco logo
│   ├── ic_earth.xml              # 地球图标
│   ├── ic_check.xml              # 勾选图标
│   └── bg_button.xml             # 按钮背景
│
├── values/                 # 数值资源
│   ├── strings.xml               # 文字
│   ├── colors.xml                # 颜色
│   ├── styles.xml                # 样式主题
│   └── dimens.xml                # 尺寸
│
├── values-zh/              # 中文资源
│   └── strings.xml
│
└── anim/                   # 动画资源
    ├── slide_in_bottom.xml
    └── fade_in.xml

四、每个关键文件的具体内容说明

1. DecoApplication.java
作用:应用程序入口,初始化全局配置
内容:
- 初始化OkHttp
- 初始化日志系统
- 初始化SharedPreferences
- 设置全局异常捕获
2. LoginActivity.java
作用:用户登录界面
内容:
- 用户名输入框
- 密码输入框(带显示/隐藏)
- 地区选择(点击地球图标)
- 记住密码复选框
- 忘记密码链接
- 登录按钮点击处理
- 与LoginViewModel交互
3. LoginViewModel.java
作用:处理登录业务逻辑
内容:
- 调用AuthRepository进行登录
- 保存用户信息到本地
- 处理登录结果
- 管理加载状态
- 错误处理
4. ApiService.java
作用:定义所有网络接口
内容:
- 登录接口:/api/v2/account/captchaLogin
- 注册接口
- 获取设备列表接口
- 其他业务接口
5. MainActivity.java
作用:应用主界面框架
内容:
- ViewPager2或FragmentContainerView
- BottomNavigationView底部导航
- Fragment切换逻辑
- 处理返回键
6. HomeFragment.java
作用:首页展示网络和设备信息
内容:
- RecyclerView展示设备列表
- SwipeRefreshLayout下拉刷新
- 网络状态卡片
- 安全防护信息卡片
- 速度测试入口

1. AndroidManifest.xml 配置

<application
    android:name=".DecoApplication"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme">
    
    <!-- 启动页 -->
    <activity 
        android:name=".ui.splash.SplashActivity"
        android:theme="@style/SplashTheme"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    
    <!-- 欢迎页 -->
    <activity 
        android:name=".ui.splash.WelcomeActivity"
        android:theme="@style/WelcomeTheme" />
    
    <!-- 协议页 -->
    <activity 
        android:name=".ui.agreement.AgreementActivity" />
    
</application>

2. SplashActivity.java (启动图标页)

package com.yourcompany.deco.ui.splash;

import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import androidx.appcompat.app.AppCompatActivity;
import com.yourcompany.deco.R;

public class SplashActivity extends AppCompatActivity {
    
    private static final int SPLASH_DELAY = 1000; // 1秒
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash);
        
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                Intent intent = new Intent(SplashActivity.this, WelcomeActivity.class);
                startActivity(intent);
                overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
                finish();
            }
        }, SPLASH_DELAY);
    }
}

3. activity_splash.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white">
    
    <ImageView
        android:id="@+id/iv_logo"
        android:layout_width="180dp"
        android:layout_height="60dp"
        android:layout_centerInParent="true"
        android:src="@drawable/deco_logo"
        android:scaleType="centerInside" />
    
</RelativeLayout>

4. WelcomeActivity.java (欢迎页)

package com.yourcompany.deco.ui.splash;

import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import androidx.appcompat.app.AppCompatActivity;
import com.yourcompany.deco.R;
import com.yourcompany.deco.ui.agreement.AgreementActivity;

public class WelcomeActivity extends AppCompatActivity {
    
    private static final int WELCOME_DELAY = 2000; // 2秒
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_welcome);
        
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                Intent intent = new Intent(WelcomeActivity.this, AgreementActivity.class);
                startActivity(intent);
                overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
                finish();
            }
        }, WELCOME_DELAY);
    }
}

5. activity_welcome.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <!-- 背景图 -->
    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/welcome_background"
        android:scaleType="centerCrop" />
    
    <!-- Logo和文字容器 -->
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:orientation="vertical"
        android:gravity="center_horizontal">
        
        <!-- Deco Logo -->
        <ImageView
            android:id="@+id/iv_deco_logo"
            android:layout_width="240dp"
            android:layout_height="80dp"
            android:src="@drawable/deco_logo"
            android:scaleType="centerInside" />
        
        <!-- 标语文字 -->
        <TextView
            android:id="@+id/tv_slogan"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="40dp"
            android:text="Superior Mesh Wi-Fi,\nEverywhere"
            android:textSize="24sp"
            android:textColor="#666666"
            android:gravity="center"
            android:lineSpacing="8dp" />
            
    </LinearLayout>
    
</RelativeLayout>

6. AgreementActivity.java (协议页)

@Override
protected void subscribeViewModel(@Nullable Bundle bundle) {
    // 获取视图组件
    CheckBox agreement1 = getViewBinding().agreement1;
    CheckBox agreement2 = getViewBinding().agreement2;
    TextView agreement1Text = getViewBinding().agreement1Text;
    TextView agreement2Text = getViewBinding().agreement2Text;
    Button nextButton = getViewBinding().nextButton;
    
    // 初始化按钮状态为禁用
    nextButton.setEnabled(false);
    
    // 设置第一个协议的文本
    String text1 = "I accept the Terms of Use and confirm that I have fully read and understood the Privacy Policy.";
    SpannableString spannable1 = new SpannableString(text1);
    
    // 设置"Terms of Use"为可点击
    int termsStart = text1.indexOf("Terms of Use");
    int termsEnd = termsStart + "Terms of Use".length();
    spannable1.setSpan(new ClickableSpan() {
        @Override
        public void onClick(@NonNull View widget) {
            // 跳转到Terms of Use页面
            startActivity(new Intent(AgreementActivity.this, TermsOfUseActivity.class));
        }
        @Override
        public void updateDrawState(@NonNull TextPaint ds) {
            super.updateDrawState(ds);
            ds.setColor(Color.parseColor("#00BCD4")); // 青色
            ds.setUnderlineText(false);
        }
    }, termsStart, termsEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    
    // 设置"Privacy Policy"为可点击
    int privacyStart = text1.indexOf("Privacy Policy");
    int privacyEnd = privacyStart + "Privacy Policy".length();
    spannable1.setSpan(new ClickableSpan() {
        @Override
        public void onClick(@NonNull View widget) {
            // 跳转到Privacy Policy页面
            startActivity(new Intent(AgreementActivity.this, PrivacyPolicyActivity.class));
        }
        @Override
        public void updateDrawState(@NonNull TextPaint ds) {
            super.updateDrawState(ds);
            ds.setColor(Color.parseColor("#00BCD4")); // 青色
            ds.setUnderlineText(false);
        }
    }, privacyStart, privacyEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    
    agreement1Text.setText(spannable1);
    agreement1Text.setMovementMethod(LinkMovementMethod.getInstance());
    
    // 设置第二个协议的文本
    String text2 = "I confirm to join the User Experience Improvement Program. I understand that I can opt out of the program any time.";
    SpannableString spannable2 = new SpannableString(text2);
    
    // 设置"User Experience Improvement Program"为可点击
    int programStart = text2.indexOf("User Experience Improvement Program");
    int programEnd = programStart + "User Experience Improvement Program".length();
    spannable2.setSpan(new ClickableSpan() {
        @Override
        public void onClick(@NonNull View widget) {
            // 跳转到User Experience Improvement Program页面
            startActivity(new Intent(AgreementActivity.this, UserExperienceProgramActivity.class));
        }
        @Override
        public void updateDrawState(@NonNull TextPaint ds) {
            super.updateDrawState(ds);
            ds.setColor(Color.parseColor("#00BCD4")); // 青色
            ds.setUnderlineText(false);
        }
    }, programStart, programEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    
    agreement2Text.setText(spannable2);
    agreement2Text.setMovementMethod(LinkMovementMethod.getInstance());
    
    // 只监听checkbox的选中状态变化
    CompoundButton.OnCheckedChangeListener checkListener = (buttonView, isChecked) -> {
        // 更新按钮状态:两个都选中才启用
        boolean bothChecked = agreement1.isChecked() && agreement2.isChecked();
        nextButton.setEnabled(bothChecked);
        
        // 可选:改变按钮透明度
        nextButton.setAlpha(bothChecked ? 1.0f : 0.5f);
    };
    
    agreement1.setOnCheckedChangeListener(checkListener);
    agreement2.setOnCheckedChangeListener(checkListener);
    
    // 重要:防止点击TextView触发checkbox
    agreement1Text.setHighlightColor(Color.TRANSPARENT);
    agreement2Text.setHighlightColor(Color.TRANSPARENT);
    
    // Next按钮点击事件
    nextButton.setOnClickListener(v -> {
        if (agreement1.isChecked() && agreement2.isChecked()) {
            // 继续下一步
            // startActivity(new Intent(AgreementActivity.this, NextActivity.class));
            // finish();
        }
    });
}

7. activity_agreement.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#E8F8F5">
    
    <!-- 中间装饰背景图 -->
    <ImageView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:src="@drawable/welcome_background"
        android:scaleType="centerCrop"
        android:alpha="0.3" />
    
    <!-- 内容层 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        
        <!-- 上部分:Logo和文字 -->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:orientation="vertical"
            android:gravity="center">
            
            <!-- Deco Logo -->
            <ImageView
                android:layout_width="200dp"
                android:layout_height="66dp"
                android:src="@drawable/deco_logo"
                android:scaleType="centerInside" />
            
            <!-- 标语 -->
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="30dp"
                android:text="Superior Mesh Wi-Fi,\nEverywhere"
                android:textSize="22sp"
                android:textColor="#666666"
                android:gravity="center"
                android:lineSpacing="6dp" />
        </LinearLayout>
        
        <!-- 下部分:协议和按钮 -->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:paddingLeft="40dp"
            android:paddingRight="40dp"
            android:paddingTop="30dp"
            android:paddingBottom="40dp">
            
            <!-- 第一个协议 -->
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                android:layout_marginBottom="20dp">
                
                <CheckBox
                    android:id="@+id/cb_terms"
                    android:layout_width="24dp"
                    android:layout_height="24dp"
                    android:layout_marginTop="2dp"
                    android:button="@null"
                    android:background="@drawable/custom_checkbox"
                    android:checked="true" />
                
                <TextView
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:layout_marginLeft="12dp"
                    android:text="I accept the Terms of Use and confirm that I have fully read and understood the Privacy Policy."
                    android:textSize="15sp"
                    android:textColor="#333333"
                    android:lineSpacing="3dp" />
            </LinearLayout>
            
            <!-- 第二个协议 -->
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                android:layout_marginBottom="40dp">
                
                <CheckBox
                    android:id="@+id/cb_user_experience"
                    android:layout_width="24dp"
                    android:layout_height="24dp"
                    android:layout_marginTop="2dp"
                    android:button="@null"
                    android:background="@drawable/custom_checkbox"
                    android:checked="true" />
                
                <TextView
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:layout_marginLeft="12dp"
                    android:text="I confirm to join the User Experience Improvement Program. I understand that I can opt out of the program any time."
                    android:textSize="15sp"
                    android:textColor="#333333"
                    android:lineSpacing="3dp" />
            </LinearLayout>
            
            <!-- Continue按钮 -->
            <Button
                android:id="@+id/btn_continue"
                android:layout_width="match_parent"
                android:layout_height="56dp"
                android:text="Continue"
                android:textSize="18sp"
                android:textColor="@android:color/white"
                android:background="@drawable/button_cyan"
                android:textAllCaps="false" />
            
            <!-- Disagree and Quit -->
            <TextView
                android:id="@+id/tv_disagree"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:layout_marginTop="20dp"
                android:text="Disagree and Quit"
                android:textSize="16sp"
                android:textColor="#00D4AA"
                android:clickable="true"
                android:background="?attr/selectableItemBackground"
                android:padding="8dp" />
        </LinearLayout>
    </LinearLayout>
    
</RelativeLayout>

8. 样式文件 styles.xml

<resources>
    <!-- 基础主题 -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="colorPrimary">#00D4AA</item>
        <item name="colorPrimaryDark">#00B493</item>
        <item name="colorAccent">#00D4AA</item>
    </style>
    
    <!-- 启动页主题 -->
    <style name="SplashTheme" parent="AppTheme">
        <item name="android:windowFullscreen">true</item>
        <item name="android:windowBackground">@android:color/white</item>
    </style>
    
    <!-- 欢迎页主题 -->
    <style name="WelcomeTheme" parent="AppTheme">
        <item name="android:windowFullscreen">true</item>
    </style>
</resources>

9. button_continue.xml (按钮背景)

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    
    <solid android:color="#00D4AA" />
    
    <corners android:radius="28dp" />
    
</shape>

10. checkbox_selector.xml (复选框样式)

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/ic_checkbox_checked" android:state_checked="true" />
    <item android:drawable="@drawable/ic_checkbox_unchecked" />
</selector>

11. PreferenceHelper.java (保存用户选择)

package com.yourcompany.deco.utils;

import android.content.Context;
import android.content.SharedPreferences;

public class PreferenceHelper {
    private static final String PREF_NAME = "deco_prefs";
    private static final String KEY_AGREEMENT_ACCEPTED = "agreement_accepted";
    private static final String KEY_USER_EXPERIENCE = "user_experience_program";
    
    private static PreferenceHelper instance;
    private SharedPreferences prefs;
    
    private PreferenceHelper(Context context) {
        prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
    }
    
    public static PreferenceHelper getInstance(Context context) {
        if (instance == null) {
            instance = new PreferenceHelper(context);
        }
        return instance;
    }
    
    public void setAgreementAccepted(boolean accepted) {
        prefs.edit().putBoolean(KEY_AGREEMENT_ACCEPTED, accepted).apply();
    }
    
    public boolean isAgreementAccepted() {
        return prefs.getBoolean(KEY_AGREEMENT_ACCEPTED, false);
    }
    
    public void setUserExperienceProgram(boolean joined) {
        prefs.edit().putBoolean(KEY_USER_EXPERIENCE, joined).apply();
    }
    
    public boolean isUserExperienceProgramJoined() {
        return prefs.getBoolean(KEY_USER_EXPERIENCE, false);
    }
}

1. AuthChoiceActivity.java (登录/注册选择页)

package com.yourcompany.deco.ui.auth.choice;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.yourcompany.deco.R;
import com.yourcompany.deco.ui.auth.login.LoginActivity;
import com.yourcompany.deco.ui.auth.register.RegisterActivity;

public class AuthChoiceActivity extends AppCompatActivity {
    
    private Button btnCreateAccount;
    private TextView tvHaveAccount;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_auth_choice);
        
        initViews();
        setListeners();
    }
    
    private void initViews() {
        btnCreateAccount = findViewById(R.id.btn_create_account);
        tvHaveAccount = findViewById(R.id.tv_have_account);
    }
    
    private void setListeners() {
        btnCreateAccount.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(AuthChoiceActivity.this, RegisterActivity.class));
            }
        });
        
        tvHaveAccount.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(AuthChoiceActivity.this, LoginActivity.class));
            }
        });
    }
}

2. activity_auth_choice.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/welcome_background">
    
    <!-- Logo和文字 -->
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="120dp"
        android:orientation="vertical"
        android:gravity="center_horizontal">
        
        <ImageView
            android:layout_width="200dp"
            android:layout_height="66dp"
            android:src="@drawable/deco_logo"
            android:scaleType="centerInside" />
        
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            android:text="Superior Mesh Wi-Fi,\nEverywhere"
            android:textSize="20sp"
            android:textColor="#666666"
            android:gravity="center"
            android:lineSpacing="6dp" />
    </LinearLayout>
    
    <!-- 按钮区域 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="100dp"
        android:orientation="vertical"
        android:paddingLeft="40dp"
        android:paddingRight="40dp">
        
        <Button
            android:id="@+id/btn_create_account"
            android:layout_width="match_parent"
            android:layout_height="56dp"
            android:text="Create a TP-Link ID"
            android:textSize="18sp"
            android:textColor="@android:color/white"
            android:background="@drawable/button_primary"
            android:textAllCaps="false" />
        
        <TextView
            android:id="@+id/tv_have_account"
            android:layout_width="match_parent"
            android:layout_height="56dp"
            android:layout_marginTop="16dp"
            android:text="Already have an account?"
            android:textSize="18sp"
            android:textColor="#00D4AA"
            android:gravity="center"
            android:background="@drawable/button_outline"
            android:clickable="true" />
    </LinearLayout>
    
</RelativeLayout>

3. RegisterActivity.java (注册页-邮箱输入)

package com.yourcompany.deco.ui.auth.register;

import android.content.Intent;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.yourcompany.deco.R;
import com.yourcompany.deco.ui.dialog.RegionDialog;

public class RegisterActivity extends AppCompatActivity {
    
    private ImageView ivBack;
    private TextView tvIsp;
    private EditText etEmail;
    private TextView tvRegion;
    private ImageView ivRegion;
    private Button btnNext;
    private TextView tvHaveAccount;
    
    private String selectedRegion = "United States";
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_register);
        
        initViews();
        setListeners();
    }
    
    private void initViews() {
        ivBack = findViewById(R.id.iv_back);
        tvIsp = findViewById(R.id.tv_isp);
        etEmail = findViewById(R.id.et_email);
        tvRegion = findViewById(R.id.tv_region);
        ivRegion = findViewById(R.id.iv_region);
        btnNext = findViewById(R.id.btn_next);
        tvHaveAccount = findViewById(R.id.tv_have_account);
        
        tvRegion.setText(selectedRegion);
    }
    
    private void setListeners() {
        ivBack.setOnClickListener(v -> finish());
        
        // 邮箱输入监听
        etEmail.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) {
                btnNext.setEnabled(s.length() > 0);
            }
            
            @Override
            public void afterTextChanged(Editable s) {}
        });
        
        // 地区选择
        View.OnClickListener regionClickListener = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                RegionDialog dialog = new RegionDialog(RegisterActivity.this);
                dialog.setOnRegionSelectedListener(new RegionDialog.OnRegionSelectedListener() {
                    @Override
                    public void onRegionSelected(String region) {
                        selectedRegion = region;
                        tvRegion.setText(region);
                    }
                });
                dialog.show();
            }
        };
        
        tvRegion.setOnClickListener(regionClickListener);
        ivRegion.setOnClickListener(regionClickListener);
        
        // Next按钮
        btnNext.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String email = etEmail.getText().toString();
                Intent intent = new Intent(RegisterActivity.this, SetPasswordActivity.class);
                intent.putExtra("email", email);
                intent.putExtra("region", selectedRegion);
                startActivity(intent);
            }
        });
        
        // 已有账号
        tvHaveAccount.setOnClickListener(v -> {
            startActivity(new Intent(RegisterActivity.this, LoginActivity.class));
            finish();
        });
    }
}

4. activity_register.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="@android:color/white">
    
    <!-- 顶部栏 -->
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="56dp">
        
        <ImageView
            android:id="@+id/iv_back"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_centerVertical="true"
            android:padding="12dp"
            android:src="@drawable/ic_back"
            android:background="?attr/selectableItemBackgroundBorderless" />
        
        <TextView
            android:id="@+id/tv_isp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:layout_marginRight="16dp"
            android:text="ISP"
            android:textColor="#333333"
            android:textSize="14sp"
            android:background="@drawable/bg_isp_tag"
            android:paddingLeft="12dp"
            android:paddingRight="12dp"
            android:paddingTop="4dp"
            android:paddingBottom="4dp" />
    </RelativeLayout>
    
    <!-- 内容区域 -->
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:padding="32dp">
            
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Create a TP-Link ID"
                android:textSize="28sp"
                android:textColor="#000000"
                android:textStyle="bold" />
            
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:text="This will be your TP-Link ID for managing your devices. We will send you an email to this address for verification."
                android:textSize="16sp"
                android:textColor="#666666"
                android:lineSpacing="4dp" />
            
            <!-- 邮箱输入 -->
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="40dp"
                android:text="TP-Link ID (Email)"
                android:textSize="14sp"
                android:textColor="#999999" />
            
            <EditText
                android:id="@+id/et_email"
                android:layout_width="match_parent"
                android:layout_height="48dp"
                android:layout_marginTop="8dp"
                android:hint="Your content"
                android:inputType="textEmailAddress"
                android:textSize="16sp"
                android:background="@drawable/bg_input_field"
                android:paddingLeft="16dp"
                android:paddingRight="16dp" />
            
            <!-- 地区选择 -->
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="48dp"
                android:layout_marginTop="24dp"
                android:orientation="horizontal"
                android:gravity="center_vertical"
                android:background="@drawable/bg_input_field"
                android:paddingLeft="16dp"
                android:paddingRight="16dp">
                
                <ImageView
                    android:id="@+id/iv_region"
                    android:layout_width="24dp"
                    android:layout_height="24dp"
                    android:src="@drawable/ic_earth" />
                
                <TextView
                    android:id="@+id/tv_region"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:layout_marginLeft="12dp"
                    android:text="United States"
                    android:textSize="16sp"
                    android:textColor="#00D4AA" />
            </LinearLayout>
            
            <!-- Next按钮 -->
            <Button
                android:id="@+id/btn_next"
                android:layout_width="match_parent"
                android:layout_height="56dp"
                android:layout_marginTop="80dp"
                android:text="Next"
                android:textSize="18sp"
                android:textColor="@android:color/white"
                android:background="@drawable/button_primary"
                android:enabled="false"
                android:textAllCaps="false" />
            
            <TextView
                android:id="@+id/tv_have_account"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:layout_marginTop="24dp"
                android:text="Already have an account?"
                android:textSize="16sp"
                android:textColor="#999999"
                android:clickable="true"
                android:background="?attr/selectableItemBackground"
                android:padding="8dp" />
                
        </LinearLayout>
    </ScrollView>
    
</LinearLayout>

5. SetPasswordActivity.java (设置密码页)

package com.yourcompany.deco.ui.auth.register;

import android.content.Intent;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ImageView;
import androidx.appcompat.app.AppCompatActivity;
import com.yourcompany.deco.R;

public class SetPasswordActivity extends AppCompatActivity {
    
    private ImageView ivBack;
    private EditText etPassword;
    private EditText etConfirmPassword;
    private CheckBox cbTerms;
    private CheckBox cbProgram;
    private Button btnNext;
    
    private String email;
    private String region;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_set_password);
        
        email = getIntent().getStringExtra("email");
        region = getIntent().getStringExtra("region");
        
        initViews();
        setListeners();
    }
    
    private void initViews() {
        ivBack = findViewById(R.id.iv_back);
        etPassword = findViewById(R.id.et_password);
        etConfirmPassword = findViewById(R.id.et_confirm_password);
        cbTerms = findViewById(R.id.cb_terms);
        cbProgram = findViewById(R.id.cb_program);
        btnNext = findViewById(R.id.btn_next);
    }
    
    private void setListeners() {
        ivBack.setOnClickListener(v -> finish());
        
        TextWatcher textWatcher = 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) {
                checkInputs();
            }
            
            @Override
            public void afterTextChanged(Editable s) {}
        };
        
        etPassword.addTextChangedListener(textWatcher);
        etConfirmPassword.addTextChangedListener(textWatcher);
        
        cbTerms.setOnCheckedChangeListener((buttonView, isChecked) -> checkInputs());
        
        btnNext.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 这里应该调用注册API
                Intent intent = new Intent(SetPasswordActivity.this, EmailVerificationActivity.class);
                intent.putExtra("email", email);
                startActivity(intent);
            }
        });
    }
    
    private void checkInputs() {
        String password = etPassword.getText().toString();
        String confirmPassword = etConfirmPassword.getText().toString();
        
        boolean isValid = password.length() >= 6 
            && password.equals(confirmPassword) 
            && cbTerms.isChecked();
            
        btnNext.setEnabled(isValid);
    }
}

6. activity_set_password.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="@android:color/white">
    
    <!-- 顶部栏 -->
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="56dp">
        
        <ImageView
            android:id="@+id/iv_back"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_centerVertical="true"
            android:padding="12dp"
            android:src="@drawable/ic_back"
            android:background="?attr/selectableItemBackgroundBorderless" />
    </RelativeLayout>
    
    <!-- 内容区域 -->
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:padding="32dp">
            
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Set Your Password"
                android:textSize="28sp"
                android:textColor="#000000"
                android:textStyle="bold" />
            
            <!-- 密码输入 -->
            <EditText
                android:id="@+id/et_password"
                android:layout_width="match_parent"
                android:layout_height="48dp"
                android:layout_marginTop="40dp"
                android:hint="Password"
                android:inputType="textPassword"
                android:textSize="16sp"
                android:background="@drawable/bg_input_field"
                android:paddingLeft="16dp"
                android:paddingRight="16dp" />
            
            <!-- 确认密码 -->
            <EditText
                android:id="@+id/et_confirm_password"
                android:layout_width="match_parent"
                android:layout_height="48dp"
                android:layout_marginTop="16dp"
                android:hint="Confirm Password"
                android:inputType="textPassword"
                android:textSize="16sp"
                android:background="@drawable/bg_input_field"
                android:paddingLeft="16dp"
                android:paddingRight="16dp" />
            
            <!-- 协议勾选 -->
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="40dp"
                android:orientation="vertical">
                
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal"
                    android:layout_marginBottom="16dp">
                    
                    <CheckBox
                        android:id="@+id/cb_terms"
                        android:layout_width="24dp"
                        android:layout_height="24dp"
                        android:button="@drawable/checkbox_selector" />
                    
                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="8dp"
                        android:text="I accept the Terms of Use and confirm that I have fully read and understood the Privacy Policy."
                        android:textSize="14sp"
                        android:textColor="#666666" />
                </LinearLayout>
                
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal">
                    
                    <CheckBox
                        android:id="@+id/cb_program"
                        android:layout_width="24dp"
                        android:layout_height="24dp"
                        android:button="@drawable/checkbox_selector" />
                    
                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="8dp"
                        android:text="I confirm to join the User Experience Improvement Program. I understand that I can opt out of the program any time."
                        android:textSize="14sp"
                        android:textColor="#666666" />
                </LinearLayout>
            </LinearLayout>
            
            <!-- Next按钮 -->
            <Button
                android:id="@+id/btn_next"
                android:layout_width="match_parent"
                android:layout_height="56dp"
                android:layout_marginTop="60dp"
                android:text="Next"
                android:textSize="18sp"
                android:textColor="@android:color/white"
                android:background="@drawable/button_primary"
                android:enabled="false"
                android:textAllCaps="false" />
                
        </LinearLayout>
    </ScrollView>
    
</LinearLayout>

7. EmailVerificationActivity.java (邮箱验证页)

package com.yourcompany.deco.ui.auth.register;

import android.content.Intent;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import com.yourcompany.deco.R;
import com.yourcompany.deco.ui.auth.login.LoginActivity;
import com.yourcompany.deco.ui.dialog.ProblemHelpDialog;

public class EmailVerificationActivity extends AppCompatActivity {
    
    private ImageView ivBack;
    private TextView tvEmail;
    private TextView tvResend;
    private Button btnActivated;
    
    private String email;
    private CountDownTimer countDownTimer;
    private boolean isCountingDown = false;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_email_verification);
        
        email = getIntent().getStringExtra("email");
        
        initViews();
        setListeners();
    }
    
    private void initViews() {
        ivBack = findViewById(R.id.iv_back);
        tvEmail = findViewById(R.id.tv_email);
        tvResend = findViewById(R.id.tv_resend);
        btnActivated = findViewById(R.id.btn_activated);
        
        tvEmail.setText(email);
    }
    
    private void setListeners() {
        ivBack.setOnClickListener(v -> finish());
        
        // Resend按钮
        tvResend.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!isCountingDown) {
                    startCountDown();
                    // 这里调用重发邮件API
                }
            }
        });
        
        // Activated & Log In按钮
        btnActivated.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(EmailVerificationActivity.this, LoginActivity.class);
                intent.putExtra("email", email);
                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                startActivity(intent);
                finish();
            }
        });
        
        // Having problem?链接
        TextView tvProblem = findViewById(R.id.tv_having_problem);
        tvProblem.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ProblemHelpDialog dialog = new ProblemHelpDialog(EmailVerificationActivity.this);
                dialog.setEmail(email);
                dialog.show();
            }
        });
    }
    
    private void startCountDown() {
        isCountingDown = true;
        tvResend.setEnabled(false);
        
        countDownTimer = new CountDownTimer(60000, 1000) {
            @Override
            public void onTick(long millisUntilFinished) {
                int seconds = (int) (millisUntilFinished / 1000);
                tvResend.setText("Resend (" + seconds + "s)");
                tvResend.setTextColor(getResources().getColor(R.color.text_gray));
            }
            
            @Override
            public void onFinish() {
                isCountingDown = false;
                tvResend.setEnabled(true);
                tvResend.setText("Resend");
                tvResend.setTextColor(getResources().getColor(R.color.primary_color));
            }
        }.start();
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (countDownTimer != null) {
            countDownTimer.cancel();
        }
    }
}

8. activity_email_verification.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="@android:color/white">
    
    <!-- 顶部栏 -->
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="56dp">
        
        <ImageView
            android:id="@+id/iv_back"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_centerVertical="true"
            android:padding="12dp"
            android:src="@drawable/ic_back"
            android:background="?attr/selectableItemBackgroundBorderless" />
    </RelativeLayout>
    
    <!-- 内容区域 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:gravity="center"
        android:padding="32dp">
        
        <!-- 邮件图标 -->
        <ImageView
            android:layout_width="120dp"
            android:layout_height="120dp"
            android:src="@drawable/ic_email_sent" />
        
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="32dp"
            android:text="Check Your Inbox"
            android:textSize="28sp"
            android:textColor="#000000"
            android:textStyle="bold" />
        
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:text="An email has been sent to"
            android:textSize="16sp"
            android:textColor="#666666" />
        
        <TextView
            android:id="@+id/tv_email"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:text="username@tp-link.com"
            android:textSize="18sp"
            android:textColor="#000000"
            android:textStyle="bold" />
        
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="24dp"
            android:text="Please follow the instructions in the email to activate your TP-Link ID within 1 hour."
            android:textSize="14sp"
            android:textColor="#999999"
            android:gravity="center"
            android:lineSpacing="4dp" />
        
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="40dp"
            android:orientation="horizontal"
            android:gravity="center_vertical">
            
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Didn't receive email? "
                android:textSize="14sp"
                android:textColor="#999999" />
            
            <TextView
                android:id="@+id/tv_resend"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Resend"
                android:textSize="14sp"
                android:textColor="@color/primary_color"
                android:clickable="true"
                android:background="?attr/selectableItemBackground"
                android:padding="4dp" />
        </LinearLayout>
        
        <Button
            android:id="@+id/btn_activated"
            android:layout_width="match_parent"
            android:layout_height="56dp"
            android:layout_marginTop="40dp"
            android:text="Activated &amp; Log In"
            android:textSize="18sp"
            android:textColor="@android:color/white"
            android:background="@drawable/button_primary"
            android:textAllCaps="false" />
        
        <TextView
            android:id="@+id/tv_having_problem"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:text="Having problem?"
            android:textSize="14sp"
            android:textColor="#999999"
            android:clickable="true"
            android:background="?attr/selectableItemBackground"
            android:padding="8dp" />
            
    </LinearLayout>
    
</LinearLayout>

9. LoginActivity.java (登录页)

package com.yourcompany.deco.ui.auth.login;

import android.content.Intent;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;
import com.yourcompany.deco.R;
import com.yourcompany.deco.ui.main.MainActivity;
import com.yourcompany.deco.widget.LoadingButton;

public class LoginActivity extends AppCompatActivity {
    
    private ImageView ivBack;
    private EditText etUsername;
    private EditText etPassword;
    private LoadingButton btnLogin;
    private TextView tvForgotPassword;
    private TextView tvNoAccount;
    
    private LoginViewModel loginViewModel;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        
        loginViewModel = new ViewModelProvider(this).get(LoginViewModel.class);
        
        initViews();
        setListeners();
        observeViewModel();
        
        // 如果从注册页跳转过来,自动填充邮箱
        String email = getIntent().getStringExtra("email");
        if (email != null) {
            etUsername.setText(email);
        }
    }
    
    private void initViews() {
        ivBack = findViewById(R.id.iv_back);
        etUsername = findViewById(R.id.et_username);
        etPassword = findViewById(R.id.et_password);
        btnLogin = findViewById(R.id.btn_login);
        tvForgotPassword = findViewById(R.id.tv_forgot_password);
        tvNoAccount = findViewById(R.id.tv_no_account);
    }
    
    private void setListeners() {
        ivBack.setOnClickListener(v -> finish());
        
        TextWatcher textWatcher = 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) {
                checkInputs();
            }
            
            @Override
            public void afterTextChanged(Editable s) {}
        };
        
        etUsername.addTextChangedListener(textWatcher);
        etPassword.addTextChangedListener(textWatcher);
        
        btnLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String username = etUsername.getText().toString();
                String password = etPassword.getText().toString();
                loginViewModel.login(username, password);
            }
        });
        
        tvForgotPassword.setOnClickListener(v -> {
            // 跳转到忘记密码页面
        });
        
        tvNoAccount.setOnClickListener(v -> {
            // 跳转到注册页面
        });
    }
    
    private void checkInputs() {
        String username = etUsername.getText().toString();
        String password = etPassword.getText().toString();
        
        btnLogin.setEnabled(!username.isEmpty() && !password.isEmpty());
    }
    
    private void observeViewModel() {
        // 观察加载状态
        loginViewModel.getIsLoading().observe(this, isLoading -> {
            btnLogin.setLoading(isLoading);
        });
        
        // 观察登录结果
        loginViewModel.getLoginResult().observe(this, result -> {
            if (result != null && result.getError_code() == 0) {
                // 登录成功,跳转到主页
                startActivity(new Intent(LoginActivity.this, MainActivity.class));
                finish();
            }
        });
        
        // 观察错误信息
        loginViewModel.getErrorMessage().observe(this, error -> {
            if (error != null) {
                // 显示错误提示
            }
        });
    }
}

10. activity_login.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="@android:color/white">
    
    <!-- 顶部栏 -->
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="56dp">
        
        <ImageView
            android:id="@+id/iv_back"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_centerVertical="true"
            android:padding="12dp"
            android:src="@drawable/ic_back"
            android:background="?attr/selectableItemBackgroundBorderless" />
    </RelativeLayout>
    
    <!-- 内容区域 -->
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:padding="32dp">
            
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Welcome to Deco"
                android:textSize="28sp"
                android:textColor="#000000"
                android:textStyle="bold" />
            
            <!-- 用户名输入 -->
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="48dp"
                android:layout_marginTop="40dp"
                android:orientation="horizontal"
                android:background="@drawable/bg_input_field"
                android:paddingLeft="16dp"
                android:paddingRight="16dp"
                android:gravity="center_vertical">
                
                <ImageView
                    android:layout_width="24dp"
                    android:layout_height="24dp"
                    android:src="@drawable/ic_user"
                    android:tint="#999999" />
                
                <EditText
                    android:id="@+id/et_username"
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:layout_marginLeft="12dp"
                    android:hint="Admin"
                    android:inputType="textEmailAddress"
                    android:textSize="16sp"
                    android:background="@null" />
                
                <ImageView
                    android:id="@+id/iv_clear_username"
                    android:layout_width="24dp"
                    android:layout_height="24dp"
                    android:src="@drawable/ic_clear"
                    android:visibility="gone" />
            </LinearLayout>
            
            <!-- 密码输入 -->
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="48dp"
                android:layout_marginTop="16dp"
                android:orientation="horizontal"
                android:background="@drawable/bg_input_field"
                android:paddingLeft="16dp"
                android:paddingRight="16dp"
                android:gravity="center_vertical">
                
                <ImageView
                    android:layout_width="24dp"
                    android:layout_height="24dp"
                    android:src="@drawable/ic_lock"
                    android:tint="#999999" />
                
                <EditText
                    android:id="@+id/et_password"
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:layout_marginLeft="12dp"
                    android:hint="Password"
                    android:inputType="textPassword"
                    android:textSize="16sp"
                    android:background="@null" />
            </LinearLayout>
            
            <TextView
                android:id="@+id/tv_forgot_password"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="right"
                android:layout_marginTop="16dp"
                android:text="Forgot Password"
                android:textSize="14sp"
                android:textColor="#999999"
                android:clickable="true"
                android:background="?attr/selectableItemBackground"
                android:padding="4dp" />
            
            <!-- 登录按钮 -->
            <com.yourcompany.deco.widget.LoadingButton
                android:id="@+id/btn_login"
                android:layout_width="match_parent"
                android:layout_height="56dp"
                android:layout_marginTop="60dp"
                android:text="Log In"
                android:textSize="18sp"
                android:textColor="@android:color/white"
                android:background="@drawable/button_primary"
                android:enabled="false"
                android:textAllCaps="false" />
            
            <TextView
                android:id="@+id/tv_no_account"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:layout_marginTop="24dp"
                android:text="Don't have an account?"
                android:textSize="16sp"
                android:textColor="#999999"
                android:clickable="true"
                android:background="?attr/selectableItemBackground"
                android:padding="8dp" />
                
        </LinearLayout>
    </ScrollView>
    
</LinearLayout>

11. LoadingButton.java (带加载状态的按钮)

package com.yourcompany.deco.widget;

import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.appcompat.widget.AppCompatButton;
import com.yourcompany.deco.R;

public class LoadingButton extends FrameLayout {
    
    private AppCompatButton button;
    private ProgressBar progressBar;
    private String originalText;
    
    public LoadingButton(Context context) {
        super(context);
        init(context);
    }
    
    public LoadingButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }
    
    private void init(Context context) {
        LayoutInflater.from(context).inflate(R.layout.widget_loading_button, this, true);
        button = findViewById(R.id.button);
        progressBar = findViewById(R.id.progress_bar);
    }
    
    public void setLoading(boolean loading) {
        if (loading) {
            originalText = button.getText().toString();
            button.setText("");
            progressBar.setVisibility(View.VISIBLE);
            button.setEnabled(false);
        } else {
            button.setText(originalText);
            progressBar.setVisibility(View.GONE);
            button.setEnabled(true);
        }
    }
    
    public void setText(String text) {
        button.setText(text);
        originalText = text;
    }
    
    @Override
    public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);
        button.setEnabled(enabled);
    }
    
    @Override
    public void setOnClickListener(OnClickListener listener) {
        button.setOnClickListener(listener);
    }
}

12. widget_loading_button.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    
    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    
    <ProgressBar
        android:id="@+id/progress_bar"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:layout_gravity="center"
        android:visibility="gone"
        android:indeterminateTint="@android:color/white" />
    
</FrameLayout>

13. 资源文件

colors.xml

<resources>
    <color name="primary_color">#00D4AA</color>
    <color name="text_gray">#999999</color>
</resources>

背景drawable文件

bg_input_field.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#F5F5F5" />
    <corners android:radius="8dp" />
</shape>

button_primary.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_enabled="false">
        <shape>
            <solid android:color="#CCCCCC" />
            <corners android:radius="28dp" />
        </shape>
    </item>
    <item>
        <shape>
            <solid android:color="#00D4AA" />
            <corners android:radius="28dp" />
        </shape>
    </item>
</selector>

button_outline.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <stroke android:width="2dp" android:color="#00D4AA" />
    <corners android:radius="28dp" />
</shape>

bg_isp_tag.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#FFD700" />
    <corners android:radius="4dp" />
</shape>

1. 网络层实现

NetworkManager.java (OkHttp配置)

package com.yourcompany.deco.data.network;

import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import java.util.concurrent.TimeUnit;

public class NetworkManager {
    
    private static NetworkManager instance;
    private OkHttpClient okHttpClient;
    private static final String BASE_URL_BETA = "https://n-wap-beta.tplinkcloud.com";
    private static final String BASE_URL_PROD = "https://n-wap-gw.tplinkcloud.com";
    
    private NetworkManager() {
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        
        okHttpClient = new OkHttpClient.Builder()
                .connectTimeout(30, TimeUnit.SECONDS)
                .readTimeout(30, TimeUnit.SECONDS)
                .writeTimeout(30, TimeUnit.SECONDS)
                .addInterceptor(new HeaderInterceptor())
                .addInterceptor(loggingInterceptor)
                .build();
    }
    
    public static NetworkManager getInstance() {
        if (instance == null) {
            instance = new NetworkManager();
        }
        return instance;
    }
    
    public OkHttpClient getClient() {
        return okHttpClient;
    }
    
    public String getBaseUrl() {
        // 这里可以根据配置返回测试或生产环境
        return BASE_URL_BETA;
    }
}

HeaderInterceptor.java

package com.yourcompany.deco.data.network;

import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;

public class HeaderInterceptor implements Interceptor {
    
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request original = chain.request();
        
        Request.Builder builder = original.newBuilder()
                .header("Content-Type", "application/json; charset=UTF-8")
                .header("Accept-Encoding", "gzip")
                .header("Connection", "Keep-Alive");
        
        Request request = builder.build();
        return chain.proceed(request);
    }
}

ApiService.java

package com.yourcompany.deco.data.api;

import com.yourcompany.deco.data.model.request.LoginRequest;
import com.yourcompany.deco.data.model.request.DeviceListRequest;
import com.yourcompany.deco.data.model.response.LoginResponse;
import com.yourcompany.deco.data.model.response.DeviceListResponse;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.*;
import java.util.Map;

public interface ApiService {
    
    @POST("/api/v2/account/captchaLogin")
    Call<LoginResponse> login(
            @Body LoginRequest request,
            @QueryMap Map<String, String> params
    );
    
    @POST("/")
    Call<DeviceListResponse> getDeviceList(
            @Body DeviceListRequest request,
            @QueryMap Map<String, String> params
    );
}

AuthRepository.java

package com.yourcompany.deco.data.repository;

import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.yourcompany.deco.data.api.ApiService;
import com.yourcompany.deco.data.model.request.LoginRequest;
import com.yourcompany.deco.data.model.response.LoginResponse;
import com.yourcompany.deco.data.network.NetworkManager;
import com.yourcompany.deco.utils.DeviceUtils;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import java.util.HashMap;
import java.util.Map;

public class AuthRepository {
    
    private static AuthRepository instance;
    private ApiService apiService;
    
    private AuthRepository() {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(NetworkManager.getInstance().getBaseUrl())
                .client(NetworkManager.getInstance().getClient())
                .addConverterFactory(GsonConverterFactory.create())
                .build();
                
        apiService = retrofit.create(ApiService.class);
    }
    
    public static AuthRepository getInstance() {
        if (instance == null) {
            instance = new AuthRepository();
        }
        return instance;
    }
    
    public LiveData<LoginResponse> login(String username, String password) {
        MutableLiveData<LoginResponse> result = new MutableLiveData<>();
        
        // 构建请求参数
        Map<String, String> queryParams = new HashMap<>();
        queryParams.put("appName", "TP-Link_aria_Android");
        queryParams.put("appVer", "3.8.33");
        queryParams.put("netType", "wifi");
        queryParams.put("termID", DeviceUtils.getDeviceId());
        queryParams.put("ospf", "Android 12");
        queryParams.put("brand", "TPLINK");
        queryParams.put("locale", "en_US");
        queryParams.put("model", DeviceUtils.getDeviceModel());
        queryParams.put("termName", DeviceUtils.getDeviceModel());
        queryParams.put("termMeta", "1");
        
        LoginRequest request = new LoginRequest();
        request.setAppType("TP-Link_aria_Android");
        request.setAppVersion("3.8.33");
        request.setCloudPassword(password);
        request.setCloudUserName(username);
        request.setPlatform("Android 12");
        request.setRefreshTokenNeeded(false);
        request.setTerminalMeta("1");
        request.setTerminalName(DeviceUtils.getDeviceModel());
        request.setTerminalUUID(DeviceUtils.getDeviceId());
        
        apiService.login(request, queryParams).enqueue(new Callback<LoginResponse>() {
            @Override
            public void onResponse(Call<LoginResponse> call, Response<LoginResponse> response) {
                if (response.isSuccessful()) {
                    result.postValue(response.body());
                } else {
                    result.postValue(null);
                }
            }
            
            @Override
            public void onFailure(Call<LoginResponse> call, Throwable t) {
                result.postValue(null);
            }
        });
        
        return result;
    }
}

2. 主页实现

MainActivity.java

package com.yourcompany.deco.ui.main;

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.yourcompany.deco.R;
import com.yourcompany.deco.ui.main.home.HomeFragment;
import com.yourcompany.deco.ui.main.family.FamilyFragment;
import com.yourcompany.deco.ui.main.smart.SmartFragment;
import com.yourcompany.deco.ui.main.discover.DiscoverFragment;
import com.yourcompany.deco.ui.main.more.MoreFragment;

public class MainActivity extends AppCompatActivity {
    
    private BottomNavigationView bottomNav;
    private Fragment currentFragment;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        initViews();
        
        // 默认显示Home
        if (savedInstanceState == null) {
            showFragment(new HomeFragment());
        }
    }
    
    private void initViews() {
        bottomNav = findViewById(R.id.bottom_navigation);
        
        bottomNav.setOnNavigationItemSelectedListener(item -> {
            Fragment fragment = null;
            
            switch (item.getItemId()) {
                case R.id.nav_network:
                    fragment = new HomeFragment();
                    break;
                case R.id.nav_family:
                    fragment = new FamilyFragment();
                    break;
                case R.id.nav_smart:
                    fragment = new SmartFragment();
                    break;
                case R.id.nav_discover:
                    fragment = new DiscoverFragment();
                    break;
                case R.id.nav_more:
                    fragment = new MoreFragment();
                    break;
            }
            
            if (fragment != null) {
                showFragment(fragment);
                return true;
            }
            return false;
        });
    }
    
    private void showFragment(Fragment fragment) {
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        
        if (currentFragment != null) {
            transaction.hide(currentFragment);
        }
        
        String tag = fragment.getClass().getSimpleName();
        Fragment existingFragment = getSupportFragmentManager().findFragmentByTag(tag);
        
        if (existingFragment != null) {
            transaction.show(existingFragment);
            currentFragment = existingFragment;
        } else {
            transaction.add(R.id.fragment_container, fragment, tag);
            currentFragment = fragment;
        }
        
        transaction.commit();
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    
    <FrameLayout
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />
    
    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottom_navigation"
        android:layout_width="match_parent"
        android:layout_height="56dp"
        android:background="@android:color/white"
        app:menu="@menu/bottom_nav_menu"
        app:labelVisibilityMode="labeled"
        app:itemTextColor="@drawable/bottom_nav_selector"
        app:itemIconTint="@drawable/bottom_nav_selector" />
    
</LinearLayout>