重构数据模型命名

This commit is contained in:
2025-12-12 13:22:34 +08:00
parent fc9f15c70c
commit be52292203
25 changed files with 761 additions and 550 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Fri Dec 12 04:27:50 GMT 2025
#Fri Dec 12 05:20:34 GMT 2025
stageCount=1
libraryProject=
baseVersion=15.12
publishVersion=15.12.0
buildCount=2
buildCount=5
baseBetaVersion=15.12.1

View File

@@ -28,7 +28,7 @@ import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager;
import cc.winboll.studio.contacts.activities.SettingsActivity;
import cc.winboll.studio.contacts.beans.MainServiceBean;
import cc.winboll.studio.contacts.model.MainServiceBean;
import cc.winboll.studio.contacts.fragments.CallLogFragment;
import cc.winboll.studio.contacts.fragments.ContactsFragment;
import cc.winboll.studio.contacts.fragments.LogFragment;

View File

@@ -1,31 +1,37 @@
package cc.winboll.studio.contacts.activities;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/03/31 15:15:54
* @Describe 应用介绍窗口
*/
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libaes.models.APPInfo;
import cc.winboll.studio.libaes.views.AboutView;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libaes.views.AboutView;
import cc.winboll.studio.libappbase.LogUtils;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/03/31 15:15:54
* @Describe 应用介绍窗口
*/
public class AboutActivity extends AppCompatActivity implements IWinBoLLActivity {
// ====================== 常量定义区 ======================
public static final String TAG = "AboutActivity";
private static final String BRANCH_NAME = "contacts";
Context mContext;
Toolbar mToolbar;
// ====================== 成员变量区 ======================
private Context mContext;
private Toolbar mToolbar;
// ====================== 接口实现区 ======================
@Override
public Activity getActivity() {
return this;
@@ -36,58 +42,75 @@ public class AboutActivity extends AppCompatActivity implements IWinBoLLActivity
return TAG;
}
// ====================== 生命周期函数区 ======================
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LogUtils.d(TAG, "onCreate: 关于页面开始创建");
mContext = this;
setContentView(R.layout.activity_about);
mToolbar = findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
mToolbar.setSubtitle(TAG);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
AboutView aboutView = CreateAboutView();
// 在 Activity 的 onCreate 或其他生命周期方法中调用
// LinearLayout layout = new LinearLayout(this);
// layout.setOrientation(LinearLayout.VERTICAL);
// // 创建布局参数(宽度和高度)
// ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
// ViewGroup.LayoutParams.MATCH_PARENT,
// ViewGroup.LayoutParams.MATCH_PARENT
// );
// addContentView(aboutView, params);
LinearLayout layout = findViewById(R.id.aboutviewroot_ll);
// 创建布局参数(宽度和高度)
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
);
layout.addView(aboutView, params);
// 初始化工具栏
initToolbar();
// 初始化关于页面视图
initAboutView();
// 注册Activity管理
WinBoLLActivityManager.getInstance().add(this);
LogUtils.d(TAG, "onCreate: 关于页面初始化完成");
}
@Override
protected void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "onDestroy: 关于页面开始销毁");
WinBoLLActivityManager.getInstance().registeRemove(this);
LogUtils.d(TAG, "onDestroy: 关于页面销毁完成");
}
public AboutView CreateAboutView() {
String szBranchName = "contacts";
// ====================== 控件初始化函数区 ======================
private void initToolbar() {
LogUtils.d(TAG, "initToolbar: 初始化工具栏");
// Java7 适配:添加强制类型转换
mToolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
mToolbar.setSubtitle(TAG);
// 非空判断,避免空指针异常
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
}
private void initAboutView() {
LogUtils.d(TAG, "initAboutView: 初始化关于页面内容视图");
AboutView aboutView = createAboutView();
LinearLayout layout = (LinearLayout) findViewById(R.id.aboutviewroot_ll);
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
);
layout.addView(aboutView, params);
LogUtils.d(TAG, "initAboutView: AboutView已添加到布局");
}
// ====================== 业务逻辑函数区 ======================
private AboutView createAboutView() {
LogUtils.d(TAG, "createAboutView: 构建APP信息并创建AboutView");
APPInfo appInfo = new APPInfo();
appInfo.setAppName("Contacts");
appInfo.setAppIcon(cc.winboll.studio.libaes.R.drawable.ic_winboll);
appInfo.setAppDescription("这是可以根据正则表达式匹配拦截骚扰电话的手机拨号应用。");
appInfo.setAppGitName("WinBoLL");
appInfo.setAppGitOwner("Studio");
appInfo.setAppGitAPPBranch(szBranchName);
appInfo.setAppGitAPPSubProjectFolder(szBranchName);
appInfo.setAppGitAPPBranch(BRANCH_NAME);
appInfo.setAppGitAPPSubProjectFolder(BRANCH_NAME);
appInfo.setAppHomePage("https://www.winboll.cc/apks/index.php?project=Contacts");
appInfo.setAppAPKName("Contacts");
appInfo.setAppAPKFolderName("Contacts");
return new AboutView(mContext, appInfo);
}
}

View File

@@ -1,10 +1,5 @@
package cc.winboll.studio.contacts.activities;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/02/20 17:15:46
* @Describe 拨号窗口
*/
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -20,99 +15,144 @@ import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import cc.winboll.studio.contacts.MainActivity;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.libappbase.LogUtils;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/02/20 17:15:46
* @Describe 拨号窗口
*/
public class CallActivity extends AppCompatActivity {
public static final String TAG = "CallActivity";
// ====================== 常量定义区 ======================
public static final String TAG = "CallActivity";
private static final int REQUEST_CALL_PHONE = 1;
// ====================== UI控件区 ======================
private EditText phoneNumberEditText;
private TextView callStatusTextView;
private Button dialButton;
// ====================== 业务成员区 ======================
private TelephonyManager telephonyManager;
private MyPhoneStateListener phoneStateListener;
// ====================== 生命周期函数区 ======================
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
LogUtils.d(TAG, "onCreate: 拨号页面开始创建");
setContentView(R.layout.activity_call);
phoneNumberEditText = findViewById(R.id.phone_number);
Button dialButton = findViewById(R.id.dial_button);
callStatusTextView = findViewById(R.id.call_status);
dialButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String phoneNumber = phoneNumberEditText.getText().toString().trim();
if (!phoneNumber.isEmpty()) {
if (ContextCompat.checkSelfPermission(CallActivity.this, Manifest.permission.CALL_PHONE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(CallActivity.this,
new String[]{Manifest.permission.CALL_PHONE},
REQUEST_CALL_PHONE);
} else {
dialPhoneNumber(phoneNumber);
}
} else {
Toast.makeText(CallActivity.this, "请输入电话号码", Toast.LENGTH_SHORT).show();
}
}
});
// 初始化TelephonyManager和PhoneStateListener
telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
phoneStateListener = new MyPhoneStateListener();
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CALL_PHONE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
String phoneNumber = phoneNumberEditText.getText().toString().trim();
dialPhoneNumber(phoneNumber);
} else {
Toast.makeText(this, "未授予拨打电话权限", Toast.LENGTH_SHORT).show();
}
}
}
private void dialPhoneNumber(String phoneNumber) {
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(android.net.Uri.parse("tel:" + phoneNumber));
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
return;
}
startActivity(intent);
}
private class MyPhoneStateListener extends PhoneStateListener {
@Override
public void onCallStateChanged(int state, String incomingNumber) {
switch (state) {
case TelephonyManager.CALL_STATE_IDLE:
callStatusTextView.setText("电话已挂断");
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
callStatusTextView.setText("正在通话中");
break;
case TelephonyManager.CALL_STATE_RINGING:
callStatusTextView.setText("来电: " + incomingNumber);
break;
}
}
// 初始化控件
initViews();
// 初始化电话状态监听
initPhoneStateListener();
LogUtils.d(TAG, "onCreate: 拨号页面初始化完成");
}
@Override
protected void onDestroy() {
super.onDestroy();
// 取消监听
if (telephonyManager != null) {
LogUtils.d(TAG, "onDestroy: 拨号页面开始销毁");
// 取消电话状态监听,避免内存泄漏
if (telephonyManager != null && phoneStateListener != null) {
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
LogUtils.d(TAG, "onDestroy: 电话状态监听已取消");
}
LogUtils.d(TAG, "onDestroy: 拨号页面销毁完成");
}
// ====================== 权限回调函数区 ======================
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
LogUtils.d(TAG, "onRequestPermissionsResult: 权限请求回调requestCode=" + requestCode);
if (requestCode == REQUEST_CALL_PHONE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
LogUtils.d(TAG, "onRequestPermissionsResult: 拨打电话权限授予成功");
String phoneNumber = phoneNumberEditText.getText().toString().trim();
dialPhoneNumber(phoneNumber);
} else {
LogUtils.w(TAG, "onRequestPermissionsResult: 拨打电话权限被拒绝");
Toast.makeText(this, "未授予拨打电话权限", Toast.LENGTH_SHORT).show();
}
}
}
// ====================== 控件初始化函数区 ======================
private void initViews() {
LogUtils.d(TAG, "initViews: 初始化UI控件");
// Java7 适配:添加强制类型转换
phoneNumberEditText = (EditText) findViewById(R.id.phone_number);
dialButton = (Button) findViewById(R.id.dial_button);
callStatusTextView = (TextView) findViewById(R.id.call_status);
// 设置拨号按钮点击事件
dialButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String phoneNumber = phoneNumberEditText.getText().toString().trim();
LogUtils.d(TAG, "initViews: 拨号按钮点击,号码=" + phoneNumber);
if (phoneNumber.isEmpty()) {
Toast.makeText(CallActivity.this, "请输入电话号码", Toast.LENGTH_SHORT).show();
return;
}
// 权限检查
if (ContextCompat.checkSelfPermission(CallActivity.this, Manifest.permission.CALL_PHONE)
!= PackageManager.PERMISSION_GRANTED) {
LogUtils.w(TAG, "initViews: 拨打电话权限未授予,发起权限申请");
ActivityCompat.requestPermissions(CallActivity.this,
new String[]{Manifest.permission.CALL_PHONE},
REQUEST_CALL_PHONE);
} else {
dialPhoneNumber(phoneNumber);
}
}
});
}
// ====================== 电话状态监听初始化函数区 ======================
private void initPhoneStateListener() {
LogUtils.d(TAG, "initPhoneStateListener: 初始化电话状态监听");
telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
phoneStateListener = new MyPhoneStateListener();
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
}
// ====================== 核心业务函数区 ======================
private void dialPhoneNumber(String phoneNumber) {
LogUtils.d(TAG, "dialPhoneNumber: 发起拨号,号码=" + phoneNumber);
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(android.net.Uri.parse("tel:" + phoneNumber));
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
LogUtils.e(TAG, "dialPhoneNumber: 拨打电话权限缺失,拨号失败");
return;
}
startActivity(intent);
}
// ====================== 内部电话状态监听类 ======================
private class MyPhoneStateListener extends PhoneStateListener {
@Override
public void onCallStateChanged(int state, String incomingNumber) {
super.onCallStateChanged(state, incomingNumber);
switch (state) {
case TelephonyManager.CALL_STATE_IDLE:
callStatusTextView.setText("电话已挂断");
LogUtils.d(TAG, "MyPhoneStateListener: 通话状态-挂断");
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
callStatusTextView.setText("正在通话中");
LogUtils.d(TAG, "MyPhoneStateListener: 通话状态-通话中");
break;
case TelephonyManager.CALL_STATE_RINGING:
callStatusTextView.setText("来电: " + incomingNumber);
LogUtils.d(TAG, "MyPhoneStateListener: 通话状态-来电,号码=" + incomingNumber);
break;
}
}
}
}

View File

@@ -1,40 +1,80 @@
package cc.winboll.studio.contacts.activities;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/02/20 20:18:26
*/
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.libappbase.LogUtils;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/02/20 20:18:26
* @Describe 拨号盘窗口(跳转到系统拨号界面)
*/
public class DialerActivity extends AppCompatActivity {
// ====================== 常量定义区 ======================
public static final String TAG = "DialerActivity";
// ====================== UI控件区 ======================
private EditText phoneNumberEditText;
private Button dialButton;
// ====================== 生命周期函数区 ======================
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LogUtils.d(TAG, "onCreate: 拨号盘页面开始创建");
setContentView(R.layout.activity_dialer);
phoneNumberEditText = findViewById(R.id.phone_number_edit_text);
Button dialButton = findViewById(R.id.dial_button);
// 初始化UI控件与点击事件
initViews();
LogUtils.d(TAG, "onCreate: 拨号盘页面初始化完成");
}
@Override
protected void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "onDestroy: 拨号盘页面已销毁");
}
// ====================== 控件初始化函数区 ======================
private void initViews() {
LogUtils.d(TAG, "initViews: 初始化UI控件");
// Java7 适配:添加强制类型转换
phoneNumberEditText = (EditText) findViewById(R.id.phone_number_edit_text);
dialButton = (Button) findViewById(R.id.dial_button);
// 设置拨号按钮点击事件
dialButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String phoneNumber = phoneNumberEditText.getText().toString();
Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + phoneNumber));
startActivity(intent);
}
});
@Override
public void onClick(View v) {
String phoneNumber = phoneNumberEditText.getText().toString().trim();
LogUtils.d(TAG, "initViews: 拨号按钮点击,输入号码=" + phoneNumber);
// 空号码校验
if (phoneNumber.isEmpty()) {
LogUtils.w(TAG, "initViews: 拨号失败,号码为空");
Toast.makeText(DialerActivity.this, "请输入有效电话号码", Toast.LENGTH_SHORT).show();
return;
}
// 跳转到系统拨号界面
Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + phoneNumber));
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
LogUtils.d(TAG, "initViews: 成功跳转到系统拨号界面");
} else {
LogUtils.e(TAG, "initViews: 跳转失败,无可用拨号应用");
Toast.makeText(DialerActivity.this, "未找到可用拨号应用", Toast.LENGTH_SHORT).show();
}
}
});
}
}

View File

@@ -22,10 +22,10 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.contacts.adapters.PhoneConnectRuleAdapter;
import cc.winboll.studio.contacts.beans.MainServiceBean;
import cc.winboll.studio.contacts.beans.PhoneConnectRuleModel;
import cc.winboll.studio.contacts.beans.RingTongBean;
import cc.winboll.studio.contacts.beans.SettingsModel;
import cc.winboll.studio.contacts.model.MainServiceBean;
import cc.winboll.studio.contacts.model.PhoneConnectRuleModel;
import cc.winboll.studio.contacts.model.RingTongBean;
import cc.winboll.studio.contacts.model.SettingsModel;
import cc.winboll.studio.contacts.bobulltoon.TomCat;
import cc.winboll.studio.contacts.dun.Rules;
import cc.winboll.studio.contacts.services.MainService;

View File

@@ -11,84 +11,123 @@ import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.LogView;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/03/02 16:07:04
* @Describe 规则单元测试页面
*/
public class UnitTestActivity extends Activity {
// ====================== 常量定义区 ======================
public static final String TAG = "UnitTestActivity";
LogView logView;
// ====================== UI控件区 ======================
private LogView logView;
private EditText etPhone;
// ====================== 生命周期函数区 ======================
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LogUtils.d(TAG, "onCreate: 单元测试页面开始创建");
setContentView(R.layout.activity_unittest);
logView = findViewById(R.id.logview);
// 初始化控件
initViews();
LogUtils.d(TAG, "onCreate: 单元测试页面初始化完成");
}
@Override
protected void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "onDestroy: 单元测试页面开始销毁");
if (logView != null) {
// 若LogView有停止方法建议调用避免资源泄漏
// logView.stop();
LogUtils.d(TAG, "onDestroy: LogView资源已处理");
}
LogUtils.d(TAG, "onDestroy: 单元测试页面销毁完成");
}
// ====================== 控件初始化函数区 ======================
private void initViews() {
LogUtils.d(TAG, "initViews: 初始化UI控件");
// Java7 适配:添加强制类型转换
logView = (LogView) findViewById(R.id.logview);
etPhone = (EditText) findViewById(R.id.phone_et);
// 启动日志视图
logView.start();
LogUtils.d(TAG, "initViews: LogView已启动");
}
// ====================== 点击事件测试函数区 ======================
/**
* 测试单个号码匹配规则
*/
public void onTestPhone(View view) {
// 开始测试数据
EditText etPhone = findViewById(R.id.phone_et);
Rules rules = Rules.getInstance(this);
LogUtils.d(TAG, "onTestPhone: 开始测试单个号码规则匹配");
String phone = etPhone.getText().toString().trim();
LogUtils.d(TAG, String.format("Test phone : %s\n%s", phone, rules.isAllowed(phone)));
}
public void onTestMain(View view) {
LogUtils.d(TAG, "IntUtils.unittest_getIntInRange();");
IntUtils.unittest_getIntInRange();
Rules rules = Rules.getInstance(this);
// 如果没有规则就添加测试规则
if (rules.getPhoneBlacRuleBeanList().size() == 0) {
// 手机号码允许
// 中国手机号码正则表达式以1开头第二位可以是3、4、5、6、7、8、9后面跟9位数字
String regex = "^1[3-9]\\d{9}$";
rules.add(regex, true, true);
// 指定区号号码允许
regex = "^0660\\d+$";
rules.add(regex, true, true);
// 指定区号号码允许
regex = "^020\\d+$";
rules.add(regex, true, true);
// 添加默认拒接规则
regex = ".*";
rules.add(regex, false, true);
// 保存规则到文件
rules.saveRules();
if (phone.isEmpty()) {
LogUtils.w(TAG, "onTestPhone: 测试号码为空,跳过匹配");
return;
}
// 开始测试数据
String phone = "16769764848";
LogUtils.d(TAG, String.format("Test phone : %s\n%s", phone, rules.isAllowed(phone)));
Rules rules = Rules.getInstance(this);
boolean isAllowed = rules.isAllowed(phone);
LogUtils.d(TAG, String.format("onTestPhone: 测试号码: %s | 匹配结果: %s", phone, isAllowed));
}
phone = "16856582777";
LogUtils.d(TAG, String.format("Test phone : %s\n%s", phone, rules.isAllowed(phone)));
/**
* 批量测试预设号码规则匹配
*/
public void onTestMain(View view) {
LogUtils.d(TAG, "onTestMain: 开始批量测试号码规则匹配");
// 测试IntUtils工具类方法
LogUtils.d(TAG, "onTestMain: 执行 IntUtils.unittest_getIntInRange() 测试");
IntUtils.unittest_getIntInRange();
phone = "17519703124";
LogUtils.d(TAG, String.format("Test phone : %s\n%s", phone, rules.isAllowed(phone)));
// 初始化规则实例
Rules rules = Rules.getInstance(this);
// 无规则时添加测试规则集
initTestRulesIfEmpty(rules);
phone = "0205658955";
LogUtils.d(TAG, String.format("Test phone : %s\n%s", phone, rules.isAllowed(phone)));
// 预设测试号码列表
String[] testPhones = {
"16769764848", "16856582777", "17519703124",
"0205658955", "0108965253", "+8616769764848",
"4005816769764848", "95566"
};
phone = "0108965253";
LogUtils.d(TAG, String.format("Test phone : %s\n%s", phone, rules.isAllowed(phone)));
// 遍历测试号码并输出结果
for (String phone : testPhones) {
boolean isAllowed = rules.isAllowed(phone);
LogUtils.d(TAG, String.format("onTestMain: 测试号码: %s | 匹配结果: %s", phone, isAllowed));
}
LogUtils.d(TAG, "onTestMain: 批量号码规则测试完成");
}
phone = "+8616769764848";
LogUtils.d(TAG, String.format("Test phone : %s\n%s", phone, rules.isAllowed(phone)));
// ====================== 私有工具函数区 ======================
/**
* 规则集为空时初始化测试规则
*/
private void initTestRulesIfEmpty(Rules rules) {
if (rules.getPhoneBlacRuleBeanList().size() == 0) {
LogUtils.d(TAG, "initTestRulesIfEmpty: 当前无规则,添加测试规则集");
// 规则1中国手机号允许
rules.add("^1[3-9]\\d{9}$", true, true);
// 规则20660区号号码允许
rules.add("^0660\\d+$", true, true);
// 规则3020区号号码允许
rules.add("^020\\d+$", true, true);
// 规则4默认拒接所有号码
rules.add(".*", false, true);
phone = "4005816769764848";
LogUtils.d(TAG, String.format("Test phone : %s\n%s", phone, rules.isAllowed(phone)));
phone = "95566";
LogUtils.d(TAG, String.format("Test phone : %s\n%s", phone, rules.isAllowed(phone)));
// 保存规则到本地
rules.saveRules();
LogUtils.d(TAG, "initTestRulesIfEmpty: 测试规则集已保存");
} else {
LogUtils.d(TAG, "initTestRulesIfEmpty: 当前已有规则,跳过初始化");
}
}
}

View File

@@ -1,10 +1,5 @@
package cc.winboll.studio.contacts.activities;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/03/31 15:16:45
* @Describe 应用窗口基类
*/
import android.app.Activity;
import android.os.Bundle;
import android.view.MenuItem;
@@ -12,13 +7,22 @@ import androidx.appcompat.app.AppCompatActivity;
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
import cc.winboll.studio.libaes.models.AESThemeBean;
import cc.winboll.studio.libaes.utils.AESThemeUtil;
import cc.winboll.studio.libappbase.LogUtils;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/03/31 15:16:45
* @Describe 应用窗口基类,统一处理主题设置与导航返回
*/
public class WinBollActivity extends AppCompatActivity implements IWinBoLLActivity {
// ====================== 常量定义区 ======================
public static final String TAG = "WinBollActivity";
// ====================== 成员变量区 ======================
protected volatile AESThemeBean.ThemeType mThemeType;
// ====================== 接口实现区 ======================
@Override
public Activity getActivity() {
return this;
@@ -29,14 +33,24 @@ public class WinBollActivity extends AppCompatActivity implements IWinBoLLActivi
return TAG;
}
// ====================== 生命周期函数区 ======================
@Override
protected void onCreate(Bundle savedInstanceState) {
LogUtils.d(TAG, "onCreate: 基类页面开始创建");
// 优先设置主题,再执行父类初始化
mThemeType = getThemeType();
setThemeStyle();
super.onCreate(savedInstanceState);
LogUtils.d(TAG, "onCreate: 基类主题设置完成,当前主题类型=" + mThemeType);
}
// ====================== 主题相关函数区 ======================
/**
* 获取当前应用主题类型
*/
AESThemeBean.ThemeType getThemeType() {
LogUtils.d(TAG, "getThemeType: 获取应用主题类型");
// 注释的SharedPreferences逻辑保留便于后续扩展
/*SharedPreferences sharedPreferences = getSharedPreferences(
SHAREDPREFERENCES_NAME, MODE_PRIVATE);
return AESThemeBean.ThemeType.values()[((sharedPreferences.getInt(DRAWER_THEME_TYPE, AESThemeBean.ThemeType.DEFAULT.ordinal())))];
@@ -44,17 +58,27 @@ public class WinBollActivity extends AppCompatActivity implements IWinBoLLActivi
return AESThemeBean.getThemeStyleType(AESThemeUtil.getThemeTypeID(getApplicationContext()));
}
/**
* 应用当前主题样式
*/
void setThemeStyle() {
//setTheme(AESThemeBean.getThemeStyle(getThemeType()));
LogUtils.d(TAG, "setThemeStyle: 开始设置应用主题");
// 替换原注释逻辑使用AESThemeUtil获取的主题ID
setTheme(AESThemeUtil.getThemeTypeID(getApplicationContext()));
LogUtils.d(TAG, "setThemeStyle: 主题设置完成");
}
// ====================== 菜单与导航函数区 ======================
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if(item.getItemId() == android.R.id.home) {
LogUtils.d(TAG, "onOptionsItemSelected: 菜单选项点击itemId=" + item.getItemId());
// 处理导航栏返回按钮点击事件
if (item.getItemId() == android.R.id.home) {
LogUtils.d(TAG, "onOptionsItemSelected: 点击导航返回按钮,关闭当前页面");
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}

View File

@@ -1,10 +1,5 @@
package cc.winboll.studio.contacts.adapters;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/02/26 13:09:32
* @Describe CallLogAdapter
*/
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
@@ -13,125 +8,167 @@ import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.PopupMenu;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.contacts.beans.CallLogModel;
import cc.winboll.studio.contacts.model.CallLogModel;
import cc.winboll.studio.contacts.utils.ContactUtils;
import cc.winboll.studio.libaes.views.AOHPCTCSeekBar;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Locale;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/02/26 13:09:32
* @Describe 通话记录列表适配器
*/
public class CallLogAdapter extends RecyclerView.Adapter<CallLogAdapter.CallLogViewHolder> {
// ====================== 常量定义区 ======================
public static final String TAG = "CallLogAdapter";
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
// ====================== 成员变量区 ======================
private Context mContext;
private List<CallLogModel> callLogList;
ContactUtils mContactUtils;
Context mContext;
private ContactUtils mContactUtils;
// ====================== 构造函数区 ======================
public CallLogAdapter(Context context, List<CallLogModel> callLogList) {
mContext = context;
this.mContactUtils = ContactUtils.getInstance(mContext);
LogUtils.d(TAG, "CallLogAdapter: 初始化适配器,数据量=" + callLogList.size());
this.mContext = context;
this.callLogList = callLogList;
this.mContactUtils = ContactUtils.getInstance(mContext);
}
public void relaodContacts() {
this.mContactUtils.relaodContacts();
}
// ====================== 公共方法区 ======================
/**
* 重新加载联系人数据
*/
public void relaodContacts() {
LogUtils.d(TAG, "relaodContacts: 开始重新加载联系人数据");
this.mContactUtils.relaodContacts();
notifyDataSetChanged();
LogUtils.d(TAG, "relaodContacts: 联系人数据加载完成,列表已刷新");
}
// ====================== RecyclerView 重写方法区 ======================
@NonNull
@Override
public CallLogViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LogUtils.d(TAG, "onCreateViewHolder: 创建列表项ViewHolder");
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_call_log, parent, false);
return new CallLogViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull CallLogViewHolder holder, int position) {
LogUtils.d(TAG, "onBindViewHolder: 绑定列表项数据position=" + position);
final CallLogModel callLog = callLogList.get(position);
holder.phoneNumber.setText(callLog.getPhoneNumber() + "" + mContactUtils.getContactsName(callLog.getPhoneNumber()));
holder.phoneNumber.setOnLongClickListener(new View.OnLongClickListener() {
// 绑定通话号码与联系人名称
String contactName = mContactUtils.getContactsName(callLog.getPhoneNumber());
String phoneText = callLog.getPhoneNumber() + "" + (contactName == null ? "" : contactName);
holder.phoneNumber.setText(phoneText);
// 号码长按弹出菜单事件
holder.phoneNumber.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View p1) {
// 弹出复制菜单
PopupMenu menu = new PopupMenu(mContext, holder.phoneNumber);
//加载菜单资源
menu.getMenuInflater().inflate(R.menu.toolbar_calllog_phonenumber, menu.getMenu());
//设置点击事件的响应
menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
int nItemId = menuItem.getItemId();
if (nItemId == R.id.item_calllog_phonenumber_copy) {
// Gets a handle to the clipboard service.
ClipboardManager clipboard = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
// Creates a new text clip to put on the clipboard
ClipData clip = ClipData.newPlainText("simple text", callLog.getPhoneNumber());
// Set the clipboard's primary clip.
clipboard.setPrimaryClip(clip);
Toast.makeText(mContext, "Copy to clipboard.", Toast.LENGTH_SHORT).show();
} else if (nItemId == R.id.item_calllog_phonenumber_add_contact) {
//ToastUtils.show(callLog.getPhoneNumber());
ContactUtils.jumpToAddContact(mContext, callLog.getPhoneNumber());
}
return true;
}
});
//一定要调用show()来显示弹出式菜单
menu.show();
showPhonePopupMenu(holder.phoneNumber, callLog);
return true;
}
});
holder.callStatus.setText(callLog.getCallStatus());
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
holder.callDate.setText(dateFormat.format(callLog.getCallDate()));
// 初始化拉动后拨号控件
holder.dialAOHPCTCSeekBar.setThumb(holder.itemView.getContext().getDrawable(R.drawable.ic_call));
holder.dialAOHPCTCSeekBar.setBlurRightDP(80);
holder.dialAOHPCTCSeekBar.setThumbOffset(0);
holder.dialAOHPCTCSeekBar.setOnOHPCListener(
new AOHPCTCSeekBar.OnOHPCListener(){
@Override
public void onOHPCommit() {
String phoneNumber = callLog.getPhoneNumber().replaceAll("\\s", "");
ToastUtils.show(phoneNumber);
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(android.net.Uri.parse("tel:" + phoneNumber));
// 添加 FLAG_ACTIVITY_NEW_TASK 标志
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
holder.itemView.getContext().startActivity(intent);
}
});
// 绑定通话状态与时间
holder.callStatus.setText(callLog.getCallStatus());
holder.callDate.setText(DATE_FORMAT.format(callLog.getCallDate()));
// 初始化滑动拨号SeekBar
initDialSeekBar(holder.dialAOHPCTCSeekBar, callLog);
}
@Override
public int getItemCount() {
return callLogList.size();
return callLogList == null ? 0 : callLogList.size();
}
// ====================== 私有工具方法区 ======================
/**
* 显示号码操作弹窗菜单
*/
private void showPhonePopupMenu(View anchorView, final CallLogModel callLog) {
LogUtils.d(TAG, "showPhonePopupMenu: 弹出号码操作菜单");
PopupMenu menu = new PopupMenu(mContext, anchorView);
menu.getMenuInflater().inflate(R.menu.toolbar_calllog_phonenumber, menu.getMenu());
menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
int itemId = menuItem.getItemId();
if (itemId == R.id.item_calllog_phonenumber_copy) {
// 复制号码到剪贴板
ClipboardManager clipboard = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("call_log_phone", callLog.getPhoneNumber());
clipboard.setPrimaryClip(clip);
Toast.makeText(mContext, "Copy to clipboard.", Toast.LENGTH_SHORT).show();
LogUtils.d(TAG, "showPhonePopupMenu: 号码" + callLog.getPhoneNumber() + "已复制到剪贴板");
} else if (itemId == R.id.item_calllog_phonenumber_add_contact) {
// 跳转到添加联系人页面
ContactUtils.jumpToAddContact(mContext, callLog.getPhoneNumber());
LogUtils.d(TAG, "showPhonePopupMenu: 跳转添加联系人页面,号码=" + callLog.getPhoneNumber());
}
return true;
}
});
menu.show();
}
/**
* 初始化滑动拨号SeekBar
*/
private void initDialSeekBar(AOHPCTCSeekBar seekBar, final CallLogModel callLog) {
LogUtils.d(TAG, "initDialSeekBar: 初始化滑动拨号控件");
seekBar.setThumb(seekBar.getContext().getDrawable(R.drawable.ic_call));
seekBar.setBlurRightDP(80);
seekBar.setThumbOffset(0);
seekBar.setOnOHPCListener(new AOHPCTCSeekBar.OnOHPCListener() {
@Override
public void onOHPCommit() {
String phoneNumber = callLog.getPhoneNumber().replaceAll("\\s", "");
LogUtils.d(TAG, "initDialSeekBar: 滑动拨号触发,号码=" + phoneNumber);
ToastUtils.show(phoneNumber);
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(android.net.Uri.parse("tel:" + phoneNumber));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
});
}
// ====================== ViewHolder 内部类 ======================
public class CallLogViewHolder extends RecyclerView.ViewHolder {
TextView phoneNumber, callStatus, callDate;
Button dialButton;
TextView phoneNumber;
TextView callStatus;
TextView callDate;
AOHPCTCSeekBar dialAOHPCTCSeekBar;
public CallLogViewHolder(@NonNull View itemView) {
super(itemView);
phoneNumber = itemView.findViewById(R.id.phone_number);
callStatus = itemView.findViewById(R.id.call_status);
callDate = itemView.findViewById(R.id.call_date);
dialAOHPCTCSeekBar = itemView.findViewById(R.id.aohpctcseekbar_dial);
// Java7 适配:添加强制类型转换
phoneNumber = (TextView) itemView.findViewById(R.id.phone_number);
callStatus = (TextView) itemView.findViewById(R.id.call_status);
callDate = (TextView) itemView.findViewById(R.id.call_date);
dialAOHPCTCSeekBar = (AOHPCTCSeekBar) itemView.findViewById(R.id.aohpctcseekbar_dial);
}
}
}

View File

@@ -1,10 +1,5 @@
package cc.winboll.studio.contacts.adapters;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/02/26 13:35:44
* @Describe ContactAdapter
*/
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
@@ -20,111 +15,142 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.contacts.beans.ContactModel;
import cc.winboll.studio.contacts.model.ContactModel;
import cc.winboll.studio.contacts.utils.ContactUtils;
import cc.winboll.studio.libaes.views.AOHPCTCSeekBar;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import java.util.List;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/02/26 13:35:44
* @Describe 联系人列表适配器
*/
public class ContactAdapter extends RecyclerView.Adapter<ContactAdapter.ContactViewHolder> {
// ====================== 常量定义区 ======================
public static final String TAG = "ContactAdapter";
// 移除未使用的 REQUEST_CALL_PHONE 常量,精简冗余代码
private static final int REQUEST_CALL_PHONE = 1;
// ====================== 成员变量区 ======================
private Context mContext;
private List<ContactModel> contactList;
Context mContext;
// ====================== 构造函数区 ======================
public ContactAdapter(Context context, List<ContactModel> contactList) {
mContext = context;
LogUtils.d(TAG, "ContactAdapter: 初始化适配器,联系人数量=" + contactList.size());
this.mContext = context;
this.contactList = contactList;
}
// ====================== RecyclerView 重写方法区 ======================
@NonNull
@Override
public ContactViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LogUtils.d(TAG, "onCreateViewHolder: 创建联系人列表项ViewHolder");
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_contact, parent, false);
return new ContactViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ContactViewHolder holder, int position) {
LogUtils.d(TAG, "onBindViewHolder: 绑定联系人列表项数据position=" + position);
final ContactModel contact = contactList.get(position);
holder.llPhoneNumberMain.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View p1) {
// 弹出复制菜单
PopupMenu menu = new PopupMenu(mContext, holder.llPhoneNumberMain);
//加载菜单资源
menu.getMenuInflater().inflate(R.menu.toolbar_contact_phonenumber, menu.getMenu());
//设置点击事件的响应
menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
int nItemId = menuItem.getItemId();
if (nItemId == R.id.item_contact_phonenumber_copy) {
// Gets a handle to the clipboard service.
ClipboardManager clipboard = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
// Creates a new text clip to put on the clipboard
ClipData clip = ClipData.newPlainText("simple text", contact.getNumber());
// Set the clipboard's primary clip.
clipboard.setPrimaryClip(clip);
Toast.makeText(mContext, "Copy to clipboard.", Toast.LENGTH_SHORT).show();
} else if (nItemId == R.id.item_calllog_phonenumber_edit_contact) {
//ToastUtils.show("Test");
Long nContactId = ContactUtils.getContactIdByPhone(mContext, contact.getNumber());
//ToastUtils.show(String.format("%d", nContactId));
ContactUtils.jumpToEditContact(mContext, contact.getNumber(), nContactId);
}
return true;
}
});
//一定要调用show()来显示弹出式菜单
menu.show();
return true;
}
});
// 绑定联系人名称与号码
holder.contactName.setText(contact.getName());
holder.contactNumber.setText(contact.getNumber());
// 初始化拉动后拨号控件
holder.dialAOHPCTCSeekBar.setThumb(holder.itemView.getContext().getDrawable(R.drawable.ic_call));
holder.dialAOHPCTCSeekBar.setBlurRightDP(80);
holder.dialAOHPCTCSeekBar.setThumbOffset(0);
holder.dialAOHPCTCSeekBar.setOnOHPCListener(
new AOHPCTCSeekBar.OnOHPCListener(){
@Override
public void onOHPCommit() {
String phoneNumber = contact.getNumber().replaceAll("\\s", "");
ToastUtils.show(phoneNumber);
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(android.net.Uri.parse("tel:" + phoneNumber));
// 添加 FLAG_ACTIVITY_NEW_TASK 标志
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
holder.itemView.getContext().startActivity(intent);
}
});
// 长按联系人条目弹出操作菜单
holder.llPhoneNumberMain.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
showContactPopupMenu(holder.llPhoneNumberMain, contact);
return true;
}
});
// 初始化滑动拨号SeekBar
initDialSeekBar(holder.dialAOHPCTCSeekBar, contact);
}
@Override
public int getItemCount() {
return contactList.size();
// 增加空指针判断,避免空列表崩溃
return contactList == null ? 0 : contactList.size();
}
// ====================== 私有工具方法区 ======================
/**
* 显示联系人操作弹窗菜单
*/
private void showContactPopupMenu(View anchorView, final ContactModel contact) {
LogUtils.d(TAG, "showContactPopupMenu: 弹出联系人操作菜单");
PopupMenu menu = new PopupMenu(mContext, anchorView);
menu.getMenuInflater().inflate(R.menu.toolbar_contact_phonenumber, menu.getMenu());
menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
int itemId = menuItem.getItemId();
if (itemId == R.id.item_contact_phonenumber_copy) {
// 复制联系人号码到剪贴板
ClipboardManager clipboard = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("contact_phone", contact.getNumber());
clipboard.setPrimaryClip(clip);
Toast.makeText(mContext, "Copy to clipboard.", Toast.LENGTH_SHORT).show();
LogUtils.d(TAG, "showContactPopupMenu: 联系人号码" + contact.getNumber() + "已复制到剪贴板");
} else if (itemId == R.id.item_calllog_phonenumber_edit_contact) {
// 跳转到编辑联系人页面
Long contactId = ContactUtils.getContactIdByPhone(mContext, contact.getNumber());
ContactUtils.jumpToEditContact(mContext, contact.getNumber(), contactId);
LogUtils.d(TAG, "showContactPopupMenu: 跳转编辑联系人页面,号码=" + contact.getNumber() + "ID=" + contactId);
}
return true;
}
});
menu.show();
}
/**
* 初始化滑动拨号SeekBar
*/
private void initDialSeekBar(AOHPCTCSeekBar seekBar, final ContactModel contact) {
LogUtils.d(TAG, "initDialSeekBar: 初始化滑动拨号控件");
seekBar.setThumb(seekBar.getContext().getDrawable(R.drawable.ic_call));
seekBar.setBlurRightDP(80);
seekBar.setThumbOffset(0);
seekBar.setOnOHPCListener(new AOHPCTCSeekBar.OnOHPCListener() {
@Override
public void onOHPCommit() {
String phoneNumber = contact.getNumber().replaceAll("\\s", "");
LogUtils.d(TAG, "initDialSeekBar: 滑动拨号触发,号码=" + phoneNumber);
ToastUtils.show(phoneNumber);
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(android.net.Uri.parse("tel:" + phoneNumber));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
});
}
// ====================== ViewHolder 内部类 ======================
public class ContactViewHolder extends RecyclerView.ViewHolder {
LinearLayout llPhoneNumberMain;
LinearLayout llPhoneNumberMain;
TextView contactName;
TextView contactNumber;
AOHPCTCSeekBar dialAOHPCTCSeekBar;
public ContactViewHolder(@NonNull View itemView) {
super(itemView);
llPhoneNumberMain = itemView.findViewById(R.id.itemcontactLinearLayout1);
contactName = itemView.findViewById(R.id.contact_name);
contactNumber = itemView.findViewById(R.id.contact_number);
dialAOHPCTCSeekBar = itemView.findViewById(R.id.aohpctcseekbar_dial);
// Java7 适配:添加强制类型转换
llPhoneNumberMain = (LinearLayout) itemView.findViewById(R.id.itemcontactLinearLayout1);
contactName = (TextView) itemView.findViewById(R.id.contact_name);
contactNumber = (TextView) itemView.findViewById(R.id.contact_number);
dialAOHPCTCSeekBar = (AOHPCTCSeekBar) itemView.findViewById(R.id.aohpctcseekbar_dial);
}
}
}

View File

@@ -1,14 +0,0 @@
package cc.winboll.studio.contacts.adapters;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/02/20 14:20:38
* @Describe ImagePagerAdapter
*/
public class ImagePagerAdapter {
public static final String TAG = "ImagePagerAdapter";
}

View File

@@ -1,10 +1,5 @@
package cc.winboll.studio.contacts.adapters;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2025/03/02 17:27:41
* @Describe PhoneConnectRuleAdapter
*/
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
@@ -17,226 +12,230 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.contacts.beans.PhoneConnectRuleModel;
import cc.winboll.studio.contacts.model.PhoneConnectRuleModel;
import cc.winboll.studio.contacts.dun.Rules;
import cc.winboll.studio.contacts.views.LeftScrollView;
import cc.winboll.studio.libaes.dialogs.YesNoAlertDialog;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import java.util.ArrayList;
import java.util.List;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/03/02 17:27:41
* @Describe 通话规则列表适配器,支持简单查看/编辑两种视图切换
*/
public class PhoneConnectRuleAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
// ====================== 常量定义区 ======================
public static final String TAG = "PhoneConnectRuleAdapter";
private static final int VIEW_TYPE_SIMPLE = 0;
private static final int VIEW_TYPE_EDIT = 1;
private static final String NULL_RULE_TEXT = "[NULL]";
private Context context;
private List<PhoneConnectRuleModel> ruleList;
// ====================== 成员变量区 ======================
private Context mContext;
private List<PhoneConnectRuleModel> mRuleList;
// ====================== 构造函数区 ======================
public PhoneConnectRuleAdapter(Context context, List<PhoneConnectRuleModel> ruleList) {
this.context = context;
this.ruleList = ruleList;
LogUtils.d(TAG, "PhoneConnectRuleAdapter: 初始化适配器,规则数量=" + ruleList.size());
this.mContext = context;
this.mRuleList = ruleList;
}
// ====================== RecyclerView 重写方法区 ======================
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(context);
LayoutInflater inflater = LayoutInflater.from(mContext);
if (viewType == VIEW_TYPE_SIMPLE) {
LogUtils.d(TAG, "onCreateViewHolder: 创建简单视图ViewHolder");
View view = inflater.inflate(R.layout.view_phone_connect_rule_simple, parent, false);
return new SimpleViewHolder(parent, view);
} else {
LogUtils.d(TAG, "onCreateViewHolder: 创建编辑视图ViewHolder");
View view = inflater.inflate(R.layout.view_phone_connect_rule, parent, false);
return new EditViewHolder(parent, view);
return new EditViewHolder(view);
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int position) {
final PhoneConnectRuleModel model = ruleList.get(position);
final PhoneConnectRuleModel model = mRuleList.get(position);
LogUtils.d(TAG, "onBindViewHolder: 绑定规则数据position=" + position + ",视图类型=" + getItemViewType(position));
if (holder instanceof SimpleViewHolder) {
final SimpleViewHolder simpleViewHolder = (SimpleViewHolder) holder;
String szView = model.getRuleText().trim().equals("") ?"[NULL]": model.getRuleText();
simpleViewHolder.tvRuleText.setText(szView);
simpleViewHolder.checkBoxAllow.setChecked(model.isAllowConnection());
simpleViewHolder.checkBoxAllow.setEnabled(false);
simpleViewHolder.checkBoxEnable.setChecked(model.isEnable());
simpleViewHolder.checkBoxEnable.setEnabled(false);
simpleViewHolder.scrollView.setOnActionListener(new LeftScrollView.OnActionListener(){
@Override
public void onUp() {
ArrayList<PhoneConnectRuleModel> list = Rules.getInstance(context).getPhoneBlacRuleBeanList();
if (position > 0) {
ToastUtils.show("onUp");
simpleViewHolder.scrollView.smoothScrollTo(0, 0);
// PhoneConnectRuleModel newBean = new PhoneConnectRuleModel();
// newBean.setRuleText(list.get(position).getRuleText());
// newBean.setIsAllowConnection(list.get(position).isAllowConnection());
// newBean.setIsEnable(list.get(position).isEnable());
// newBean.setIsSimpleView(list.get(position).isSimpleView());
list.add(position - 1, list.get(position));
list.remove(position + 1);
Rules.getInstance(context).saveRules();
notifyDataSetChanged();
}
}
@Override
public void onDown() {
ArrayList<PhoneConnectRuleModel> list = Rules.getInstance(context).getPhoneBlacRuleBeanList();
if (position < list.size() - 1) {
ToastUtils.show("onDown");
simpleViewHolder.scrollView.smoothScrollTo(0, 0);
// PhoneConnectRuleModel newBean = new PhoneConnectRuleModel();
// newBean.setRuleText(list.get(position).getRuleText());
// newBean.setIsAllowConnection(list.get(position).isAllowConnection());
// newBean.setIsEnable(list.get(position).isEnable());
// newBean.setIsSimpleView(list.get(position).isSimpleView());
list.add(position + 2, list.get(position));
list.remove(position);
Rules.getInstance(context).saveRules();
notifyDataSetChanged();
}
}
@Override
public void onEdit() {
simpleViewHolder.scrollView.smoothScrollTo(0, 0);
model.setIsSimpleView(false);
notifyDataSetChanged();
//notifyItemChanged(position);
}
@Override
public void onDelete() {
YesNoAlertDialog.show(simpleViewHolder.scrollView.getContext(), "删除确认", "是否删除该通话规则?", new YesNoAlertDialog.OnDialogResultListener(){
@Override
public void onYes() {
simpleViewHolder.scrollView.smoothScrollTo(0, 0);
model.setIsSimpleView(true);
ArrayList<PhoneConnectRuleModel> list = Rules.getInstance(context).getPhoneBlacRuleBeanList();
list.remove(position);
Rules.getInstance(context).saveRules();
notifyDataSetChanged();
//notifyItemChanged(position);
}
@Override
public void onNo() {
}
});
}
});
// simpleViewHolder.editButton.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
// model.setIsSimpleView(false);
// notifyItemChanged(position);
// }
// });
// simpleViewHolder.deleteButton.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
// model.setIsSimpleView(false);
// ArrayList<PhoneConnectRuleModel> list = Rules.getInstance(context).getPhoneBlacRuleBeanList();
// list.remove(position);
// Rules.getInstance(context).saveRules();
// notifyItemChanged(position);
// }
// });
// // 触摸事件处理
// simpleViewHolder.contentLayout.setOnTouchListener(new View.OnTouchListener() {
// @Override
// public boolean onTouch(View v, MotionEvent event) {
// switch (event.getAction()) {
// case MotionEvent.ACTION_DOWN:
// simpleViewHolder.startX = event.getX();
// simpleViewHolder.isSwiping = true;
// break;
// case MotionEvent.ACTION_MOVE:
// if (simpleViewHolder.isSwiping) {
// float deltaX = simpleViewHolder.startX - event.getX();
// if (deltaX > 0) { // 左滑
// float translationX = Math.max(-simpleViewHolder.actionLayout.getWidth(), -deltaX);
// simpleViewHolder.contentLayout.setTranslationX(translationX);
// simpleViewHolder.actionLayout.setVisibility(View.VISIBLE);
// }
// }
// break;
// case MotionEvent.ACTION_UP:
// simpleViewHolder.isSwiping = false;
// if (simpleViewHolder.contentLayout.getTranslationX() < -simpleViewHolder.actionLayout.getWidth() / 2) {
// // 保持按钮显示
// simpleViewHolder.contentLayout.setTranslationX(-actionLayout.getWidth());
// } else {
// // 恢复原状
// simpleViewHolder.contentLayout.animate().translationX(0).setDuration(200).start();
// simpleViewHolder.actionLayout.setVisibility(View.INVISIBLE);
// }
// break;
// }
// return true;
// }
// });
bindSimpleViewHolder((SimpleViewHolder) holder, model, position);
} else if (holder instanceof EditViewHolder) {
final EditViewHolder editViewHolder = (EditViewHolder) holder;
editViewHolder.editText.setText(model.getRuleText());
editViewHolder.checkBoxAllow.setChecked(model.isAllowConnection());
editViewHolder.checkBoxEnable.setChecked(model.isEnable());
editViewHolder.buttonConfirm.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
model.setRuleText(editViewHolder.editText.getText().toString());
model.setIsAllowConnection(editViewHolder.checkBoxAllow.isChecked());
model.setIsEnable(editViewHolder.checkBoxEnable.isChecked());
model.setIsSimpleView(true);
Rules.getInstance(context).saveRules();
notifyItemChanged(position);
Toast.makeText(context, "保存成功", Toast.LENGTH_SHORT).show();
}
});
bindEditViewHolder((EditViewHolder) holder, model, position);
}
}
@Override
public int getItemCount() {
return ruleList.size();
return mRuleList == null ? 0 : mRuleList.size();
}
@Override
public int getItemViewType(int position) {
PhoneConnectRuleModel model = ruleList.get(position);
// 这里可以根据模型的状态来决定视图类型,简单起见,假设点击按钮后进入编辑视图
return model.isSimpleView() ? VIEW_TYPE_SIMPLE : VIEW_TYPE_EDIT;
return mRuleList.get(position).isSimpleView() ? VIEW_TYPE_SIMPLE : VIEW_TYPE_EDIT;
}
// ====================== 私有视图绑定方法区 ======================
/**
* 绑定简单视图数据
*/
private void bindSimpleViewHolder(final SimpleViewHolder holder, final PhoneConnectRuleModel model, final int position) {
// 绑定规则文本,空值显示[NULL]
String ruleText = model.getRuleText().trim().isEmpty() ? NULL_RULE_TEXT : model.getRuleText().trim();
holder.tvRuleText.setText(ruleText);
// 设置复选框状态并禁用编辑
holder.checkBoxAllow.setChecked(model.isAllowConnection());
holder.checkBoxAllow.setEnabled(false);
holder.checkBoxEnable.setChecked(model.isEnable());
holder.checkBoxEnable.setEnabled(false);
// 设置左滑操作监听
holder.scrollView.setOnActionListener(new LeftScrollView.OnActionListener() {
@Override
public void onUp() {
LogUtils.d(TAG, "onUp: 规则上移position=" + position);
moveRuleUp(position);
holder.scrollView.smoothScrollTo(0, 0);
}
@Override
public void onDown() {
LogUtils.d(TAG, "onDown: 规则下移position=" + position);
moveRuleDown(position);
holder.scrollView.smoothScrollTo(0, 0);
}
@Override
public void onEdit() {
LogUtils.d(TAG, "onEdit: 切换到编辑视图position=" + position);
model.setIsSimpleView(false);
notifyItemChanged(position);
holder.scrollView.smoothScrollTo(0, 0);
}
@Override
public void onDelete() {
LogUtils.d(TAG, "onDelete: 触发规则删除确认position=" + position);
showDeleteConfirmDialog(holder.scrollView.getContext(), model, position);
}
});
}
/**
* 绑定编辑视图数据
*/
private void bindEditViewHolder(final EditViewHolder holder, final PhoneConnectRuleModel model, final int position) {
// 绑定规则文本到输入框
holder.editText.setText(model.getRuleText());
// 绑定复选框状态
holder.checkBoxAllow.setChecked(model.isAllowConnection());
holder.checkBoxEnable.setChecked(model.isEnable());
// 确认按钮点击事件
holder.buttonConfirm.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String newRuleText = holder.editText.getText().toString().trim();
model.setRuleText(newRuleText);
model.setIsAllowConnection(holder.checkBoxAllow.isChecked());
model.setIsEnable(holder.checkBoxEnable.isChecked());
model.setIsSimpleView(true);
// 保存规则并刷新视图
Rules.getInstance(mContext).saveRules();
notifyItemChanged(position);
Toast.makeText(mContext, "保存成功", Toast.LENGTH_SHORT).show();
LogUtils.d(TAG, "bindEditViewHolder: 规则保存成功position=" + position + ",规则内容=" + newRuleText);
}
});
}
// ====================== 私有业务工具方法区 ======================
/**
* 规则上移
*/
private void moveRuleUp(int position) {
if (position <= 0) {
ToastUtils.show("已到顶部,无法上移");
return;
}
ArrayList<PhoneConnectRuleModel> ruleList = Rules.getInstance(mContext).getPhoneBlacRuleBeanList();
swapRulePosition(ruleList, position, position - 1);
}
/**
* 规则下移
*/
private void moveRuleDown(int position) {
ArrayList<PhoneConnectRuleModel> ruleList = Rules.getInstance(mContext).getPhoneBlacRuleBeanList();
if (position >= ruleList.size() - 1) {
ToastUtils.show("已到底部,无法下移");
return;
}
swapRulePosition(ruleList, position, position + 1);
}
/**
* 交换规则位置
*/
private void swapRulePosition(ArrayList<PhoneConnectRuleModel> list, int fromPos, int toPos) {
PhoneConnectRuleModel temp = list.get(fromPos);
list.set(fromPos, list.get(toPos));
list.set(toPos, temp);
Rules.getInstance(mContext).saveRules();
notifyDataSetChanged();
LogUtils.d(TAG, "swapRulePosition: 规则位置交换完成from=" + fromPos + "to=" + toPos);
}
/**
* 显示删除确认弹窗
*/
private void showDeleteConfirmDialog(Context dialogContext, final PhoneConnectRuleModel model, final int position) {
YesNoAlertDialog.show(dialogContext, "删除确认", "是否删除该通话规则?", new YesNoAlertDialog.OnDialogResultListener() {
@Override
public void onYes() {
ArrayList<PhoneConnectRuleModel> ruleList = Rules.getInstance(mContext).getPhoneBlacRuleBeanList();
ruleList.remove(position);
Rules.getInstance(mContext).saveRules();
notifyDataSetChanged();
LogUtils.d(TAG, "showDeleteConfirmDialog: 规则删除成功position=" + position);
}
@Override
public void onNo() {
LogUtils.d(TAG, "showDeleteConfirmDialog: 用户取消删除规则position=" + position);
}
});
}
// ====================== ViewHolder 内部类区 ======================
static class SimpleViewHolder extends RecyclerView.ViewHolder {
private final LeftScrollView scrollView;
private final TextView tvRuleText;
CheckBox checkBoxAllow;
LeftScrollView scrollView;
TextView tvRuleText;
CheckBox checkBoxAllow;
CheckBox checkBoxEnable;
public SimpleViewHolder(@NonNull ViewGroup parent, @NonNull View itemView) {
super(itemView);
scrollView = itemView.findViewById(R.id.scrollView);
LayoutInflater inflater = LayoutInflater.from(itemView.getContext());
View viewContent = inflater.inflate(R.layout.view_phone_connect_rule_simple_content, parent, false);
tvRuleText = viewContent.findViewById(R.id.ruletext_tv);
checkBoxAllow = viewContent.findViewById(R.id.checkbox_allow);
checkBoxEnable = viewContent.findViewById(R.id.checkbox_enable);
//tvRuleText = new TextView(itemView.getContext());
scrollView = (LeftScrollView) itemView.findViewById(R.id.scrollView);
// 初始化简单视图内容布局
LayoutInflater inflater = LayoutInflater.from(itemView.getContext());
View viewContent = inflater.inflate(R.layout.view_phone_connect_rule_simple_content, parent, false);
tvRuleText = (TextView) viewContent.findViewById(R.id.ruletext_tv);
checkBoxAllow = (CheckBox) viewContent.findViewById(R.id.checkbox_allow);
checkBoxEnable = (CheckBox) viewContent.findViewById(R.id.checkbox_enable);
// 设置内容宽度并添加到滚动视图
scrollView.setContentWidth(parent.getWidth());
//scrollView.setContentWidth(600);
scrollView.addContentLayout(viewContent);
}
}
static class EditViewHolder extends RecyclerView.ViewHolder {
@@ -245,17 +244,14 @@ public class PhoneConnectRuleAdapter extends RecyclerView.Adapter<RecyclerView.V
CheckBox checkBoxEnable;
Button buttonConfirm;
public EditViewHolder(@NonNull ViewGroup parent, @NonNull View itemView) {
public EditViewHolder(@NonNull View itemView) {
super(itemView);
editText = itemView.findViewById(R.id.edit_text);
checkBoxAllow = itemView.findViewById(R.id.checkbox_allow);
checkBoxEnable = itemView.findViewById(R.id.checkbox_enable);
buttonConfirm = itemView.findViewById(R.id.button_confirm);
// Java7 适配:添加强制类型转换
editText = (EditText) itemView.findViewById(R.id.edit_text);
checkBoxAllow = (CheckBox) itemView.findViewById(R.id.checkbox_allow);
checkBoxEnable = (CheckBox) itemView.findViewById(R.id.checkbox_enable);
buttonConfirm = (Button) itemView.findViewById(R.id.button_confirm);
}
}
private void setCheckBoxTouchListener(CheckBox checkBox) {
}
}

View File

@@ -7,8 +7,8 @@ package cc.winboll.studio.contacts.dun;
*/
import android.content.Context;
import cc.winboll.studio.contacts.activities.SettingsActivity;
import cc.winboll.studio.contacts.beans.PhoneConnectRuleModel;
import cc.winboll.studio.contacts.beans.SettingsModel;
import cc.winboll.studio.contacts.model.PhoneConnectRuleModel;
import cc.winboll.studio.contacts.model.SettingsModel;
import cc.winboll.studio.contacts.services.MainService;
import cc.winboll.studio.contacts.utils.ContactUtils;
import cc.winboll.studio.contacts.utils.IntUtils;

View File

@@ -19,7 +19,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.contacts.adapters.CallLogAdapter;
import cc.winboll.studio.contacts.beans.CallLogModel;
import cc.winboll.studio.contacts.model.CallLogModel;
import cc.winboll.studio.libappbase.LogUtils;
import java.util.ArrayList;
import java.util.Date;

View File

@@ -30,7 +30,7 @@ import java.util.concurrent.Executors;
import cc.winboll.studio.contacts.R;
import cc.winboll.studio.contacts.adapters.ContactAdapter;
import cc.winboll.studio.contacts.beans.ContactModel;
import cc.winboll.studio.contacts.model.ContactModel;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;

View File

@@ -1,4 +1,4 @@
package cc.winboll.studio.contacts.beans;
package cc.winboll.studio.contacts.model;
/**
* @Author ZhanGSKen<zhangsken@qq.com>

View File

@@ -1,4 +1,4 @@
package cc.winboll.studio.contacts.beans;
package cc.winboll.studio.contacts.model;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>

View File

@@ -1,4 +1,4 @@
package cc.winboll.studio.contacts.beans;
package cc.winboll.studio.contacts.model;
/**
* @Author ZhanGSKen<zhangsken@qq.com>

View File

@@ -1,4 +1,4 @@
package cc.winboll.studio.contacts.beans;
package cc.winboll.studio.contacts.model;
/**
* @Author ZhanGSKen<zhangsken@qq.com>

View File

@@ -1,4 +1,4 @@
package cc.winboll.studio.contacts.beans;
package cc.winboll.studio.contacts.model;
/**
* @Author ZhanGSKen<zhangsken@qq.com>

View File

@@ -1,4 +1,4 @@
package cc.winboll.studio.contacts.beans;
package cc.winboll.studio.contacts.model;
/**
* @Author ZhanGSKen<zhangsken@qq.com>

View File

@@ -19,7 +19,7 @@ import android.telecom.InCallService;
import android.telephony.TelephonyManager;
import androidx.annotation.RequiresApi;
import cc.winboll.studio.contacts.ActivityStack;
import cc.winboll.studio.contacts.beans.RingTongBean;
import cc.winboll.studio.contacts.model.RingTongBean;
import cc.winboll.studio.contacts.dun.Rules;
import cc.winboll.studio.contacts.fragments.CallLogFragment;
import cc.winboll.studio.libappbase.LogUtils;

View File

@@ -12,7 +12,7 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
import android.os.IBinder;
import cc.winboll.studio.contacts.beans.MainServiceBean;
import cc.winboll.studio.contacts.model.MainServiceBean;
import cc.winboll.studio.contacts.services.MainService;
import cc.winboll.studio.libappbase.LogUtils;

View File

@@ -18,8 +18,8 @@ import android.content.ServiceConnection;
import android.media.AudioManager;
import android.os.Binder;
import android.os.IBinder;
import cc.winboll.studio.contacts.beans.MainServiceBean;
import cc.winboll.studio.contacts.beans.RingTongBean;
import cc.winboll.studio.contacts.model.MainServiceBean;
import cc.winboll.studio.contacts.model.RingTongBean;
import cc.winboll.studio.contacts.bobulltoon.TomCat;
import cc.winboll.studio.contacts.dun.Rules;
import cc.winboll.studio.contacts.handlers.MainServiceHandler;

View File

@@ -9,7 +9,7 @@ import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.widget.TextView;
import cc.winboll.studio.contacts.beans.SettingsModel;
import cc.winboll.studio.contacts.model.SettingsModel;
import cc.winboll.studio.contacts.dun.Rules;
import cc.winboll.studio.libappbase.LogUtils;