Compare commits
12 Commits
appbase-v1
...
50d4cd830b
Author | SHA1 | Date | |
---|---|---|---|
![]() |
50d4cd830b | ||
![]() |
2079822c00 | ||
![]() |
297c76f328 | ||
![]() |
43b18ee662 | ||
![]() |
d581cd9842 | ||
![]() |
cef50d087d | ||
![]() |
04df902b6b | ||
![]() |
33c71ea868 | ||
![]() |
ba861d910e | ||
![]() |
f5d9aafe43 | ||
![]() |
9e9402f84e | ||
![]() |
ec18330022 |
@@ -45,9 +45,9 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api fileTree(dir: 'libs', include: ['*.jar'])
|
api fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
api 'cc.winboll.studio:libaes:15.9.2'
|
api 'cc.winboll.studio:libaes:15.9.3'
|
||||||
api 'cc.winboll.studio:libapputils:15.8.4'
|
api 'cc.winboll.studio:libapputils:15.8.5'
|
||||||
api 'cc.winboll.studio:libappbase:15.8.4'
|
api 'cc.winboll.studio:libappbase:15.9.5'
|
||||||
|
|
||||||
// 权限请求框架:https://github.com/getActivity/XXPermissions
|
// 权限请求框架:https://github.com/getActivity/XXPermissions
|
||||||
api 'com.github.getActivity:XXPermissions:18.63'
|
api 'com.github.getActivity:XXPermissions:18.63'
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
#Created by .winboll/winboll_app_build.gradle
|
#Created by .winboll/winboll_app_build.gradle
|
||||||
#Thu Jul 17 09:57:24 HKT 2025
|
#Sun Aug 31 06:05:42 CST 2025
|
||||||
stageCount=12
|
stageCount=17
|
||||||
libraryProject=
|
libraryProject=
|
||||||
baseVersion=15.3
|
baseVersion=15.3
|
||||||
publishVersion=15.3.11
|
publishVersion=15.3.16
|
||||||
buildCount=0
|
buildCount=0
|
||||||
baseBetaVersion=15.3.12
|
baseBetaVersion=15.3.17
|
||||||
|
@@ -1,5 +1,10 @@
|
|||||||
package cc.winboll.studio.contacts;
|
package cc.winboll.studio.contacts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen&豆包大模型<zhangsken@188.com>
|
||||||
|
* @Date 2025/08/30 14:32
|
||||||
|
* @Describe 主窗口
|
||||||
|
*/
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.ActivityManager;
|
import android.app.ActivityManager;
|
||||||
@@ -8,6 +13,7 @@ import android.content.Intent;
|
|||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
import android.telecom.TelecomManager;
|
import android.telecom.TelecomManager;
|
||||||
import android.telephony.PhoneStateListener;
|
import android.telephony.PhoneStateListener;
|
||||||
import android.telephony.TelephonyManager;
|
import android.telephony.TelephonyManager;
|
||||||
@@ -55,13 +61,10 @@ final public class MainActivity extends AppCompatActivity implements IWinBoLLAct
|
|||||||
MainServiceBean mMainServiceBean;
|
MainServiceBean mMainServiceBean;
|
||||||
private TabLayout tabLayout;
|
private TabLayout tabLayout;
|
||||||
private ViewPager viewPager;
|
private ViewPager viewPager;
|
||||||
private List<View> views; //用来存放放进ViewPager里面的布局
|
private List<View> views;
|
||||||
//实例化存储imageView(导航原点)的集合
|
|
||||||
ImageView[] imageViews;
|
ImageView[] imageViews;
|
||||||
//MyPagerAdapter adapter;//适配器
|
LinearLayout linearLayout;
|
||||||
MyPagerAdapter pagerAdapter;
|
int currentPoint = 0;
|
||||||
LinearLayout linearLayout;//下标所在在LinearLayout布局里
|
|
||||||
int currentPoint = 0;//当前被选中中页面的下标
|
|
||||||
|
|
||||||
private TelephonyManager telephonyManager;
|
private TelephonyManager telephonyManager;
|
||||||
private MyPhoneStateListener phoneStateListener;
|
private MyPhoneStateListener phoneStateListener;
|
||||||
@@ -70,30 +73,6 @@ final public class MainActivity extends AppCompatActivity implements IWinBoLLAct
|
|||||||
|
|
||||||
private static final int DIALER_REQUEST_CODE = 1;
|
private static final int DIALER_REQUEST_CODE = 1;
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public Activity getActivity() {
|
|
||||||
// return this;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public APPInfo getAppInfo() {
|
|
||||||
// String szBranchName = "contacts";
|
|
||||||
//
|
|
||||||
// APPInfo appInfo = AboutActivityFactory.buildDefaultAPPInfo();
|
|
||||||
// appInfo.setAppName("Contacts");
|
|
||||||
// appInfo.setAppIcon(cc.winboll.studio.libapputils.R.drawable.ic_winboll);
|
|
||||||
// appInfo.setAppDescription("Contacts Description");
|
|
||||||
// appInfo.setAppGitName("APP");
|
|
||||||
// appInfo.setAppGitOwner("Studio");
|
|
||||||
// appInfo.setAppGitAPPBranch(szBranchName);
|
|
||||||
// appInfo.setAppGitAPPSubProjectFolder(szBranchName);
|
|
||||||
// appInfo.setAppHomePage("https://www.winboll.cc/studio/details.php?app=Contacts");
|
|
||||||
// appInfo.setAppAPKName("Contacts");
|
|
||||||
// appInfo.setAppAPKFolderName("Contacts");
|
|
||||||
// return appInfo;
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Activity getActivity() {
|
public Activity getActivity() {
|
||||||
@@ -107,89 +86,62 @@ final public class MainActivity extends AppCompatActivity implements IWinBoLLAct
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
// 接收并处理 Intent 数据,函数 Intent 处理接收就直接返回
|
|
||||||
//if (prosessIntents(getIntent())) return;
|
|
||||||
// 以下正常创建主窗口
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
_MainActivity = this;
|
_MainActivity = this;
|
||||||
setContentView(R.layout.activity_main);
|
setContentView(R.layout.activity_main);
|
||||||
|
|
||||||
// 初始化工具栏
|
// 初始化工具栏(仅加载基础UI)
|
||||||
mToolbar = findViewById(R.id.activitymainToolbar1);
|
mToolbar = (Toolbar) findViewById(R.id.activitymainToolbar1);
|
||||||
setSupportActionBar(mToolbar);
|
setSupportActionBar(mToolbar);
|
||||||
// if (isEnableDisplayHomeAsUp()) {
|
|
||||||
// // 显示后退按钮
|
|
||||||
// getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
// }
|
|
||||||
getSupportActionBar().setSubtitle(TAG);
|
getSupportActionBar().setSubtitle(TAG);
|
||||||
|
|
||||||
tabLayout = findViewById(R.id.tabLayout);
|
tabLayout = (TabLayout) findViewById(R.id.tabLayout);
|
||||||
viewPager = findViewById(R.id.viewPager);
|
viewPager = (ViewPager) findViewById(R.id.viewPager);
|
||||||
|
|
||||||
// 创建Fragment列表和标题列表
|
// 创建Fragment列表(仅实例化,不加载数据)
|
||||||
fragmentList = new ArrayList<>();
|
fragmentList = new ArrayList<Fragment>();
|
||||||
tabTitleList = new ArrayList<>();
|
tabTitleList = new ArrayList<String>();
|
||||||
fragmentList.add(CallLogFragment.newInstance(0));
|
fragmentList.add(CallLogFragment.newInstance(0));
|
||||||
fragmentList.add(ContactsFragment.newInstance(1));
|
fragmentList.add(ContactsFragment.newInstance(1)); // 延迟加载联系人数据
|
||||||
fragmentList.add(LogFragment.newInstance(2));
|
fragmentList.add(LogFragment.newInstance(2));
|
||||||
tabTitleList.add("通话记录");
|
tabTitleList.add("通话记录");
|
||||||
tabTitleList.add("联系人");
|
tabTitleList.add("联系人");
|
||||||
tabTitleList.add("应用日志");
|
tabTitleList.add("应用日志");
|
||||||
|
|
||||||
// 设置ViewPager的适配器
|
// 设置ViewPager适配器
|
||||||
MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager(), fragmentList, tabTitleList);
|
MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager(), fragmentList, tabTitleList);
|
||||||
viewPager.setAdapter(adapter);
|
viewPager.setAdapter(adapter);
|
||||||
|
|
||||||
|
// 关键:关闭预加载,仅当前页初始化
|
||||||
|
viewPager.setOffscreenPageLimit(0);
|
||||||
|
|
||||||
// 关联TabLayout和ViewPager
|
// 关联TabLayout和ViewPager
|
||||||
tabLayout.setupWithViewPager(viewPager);
|
tabLayout.setupWithViewPager(viewPager);
|
||||||
|
|
||||||
|
// 初始化服务状态(延迟启动非核心服务)
|
||||||
|
|
||||||
// initData();
|
|
||||||
// initView();
|
|
||||||
// //initPoint();//调用初始化导航原点的方法
|
|
||||||
// viewPager.addOnPageChangeListener(this);//滑动事件
|
|
||||||
|
|
||||||
//ViewPager viewPager = findViewById(R.id.activitymainViewPager1);
|
|
||||||
//MyPagerAdapter pagerAdapter = new MyPagerAdapter(getSupportFragmentManager());
|
|
||||||
//viewPager.setAdapter(pagerAdapter);
|
|
||||||
//TabLayout tabLayout = findViewById(R.id.activitymainTabLayout1);
|
|
||||||
//tabLayout.setupWithViewPager(viewPager);
|
|
||||||
|
|
||||||
// mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
|
|
||||||
// if (mMainServiceBean == null) {
|
|
||||||
// mMainServiceBean = new MainServiceBean();
|
|
||||||
// }
|
|
||||||
// cbMainService = findViewById(R.id.activitymainCheckBox1);
|
|
||||||
// cbMainService.setChecked(mMainServiceBean.isEnable());
|
|
||||||
// cbMainService.setOnClickListener(new View.OnClickListener(){
|
|
||||||
// @Override
|
|
||||||
// public void onClick(View view) {
|
|
||||||
// if (cbMainService.isChecked()) {
|
|
||||||
// MainService.startMainService(MainActivity.this);
|
|
||||||
// } else {
|
|
||||||
// MainService.stopMainService(MainActivity.this);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
MainServiceBean mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
|
MainServiceBean mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
|
||||||
if (mMainServiceBean == null) {
|
if (mMainServiceBean == null) {
|
||||||
mMainServiceBean = new MainServiceBean();
|
mMainServiceBean = new MainServiceBean();
|
||||||
MainServiceBean.saveBean(this, mMainServiceBean);
|
MainServiceBean.saveBean(this, mMainServiceBean);
|
||||||
}
|
}
|
||||||
if (mMainServiceBean.isEnable()) {
|
if (mMainServiceBean.isEnable()) {
|
||||||
MainService.startMainService(this);
|
// 延迟1秒启动服务,避免阻塞启动
|
||||||
|
new Handler().postDelayed(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
MainService.startMainService(MainActivity.this);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化TelephonyManager和PhoneStateListener
|
// 初始化电话状态监听(基础功能保留)
|
||||||
telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
|
telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
|
||||||
phoneStateListener = new MyPhoneStateListener();
|
phoneStateListener = new MyPhoneStateListener();
|
||||||
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
|
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ViewPager的适配器
|
// ViewPager适配器(Java 7语法)
|
||||||
private class MyPagerAdapter extends FragmentPagerAdapter {
|
private class MyPagerAdapter extends FragmentPagerAdapter {
|
||||||
|
|
||||||
private List<Fragment> fragmentList;
|
private List<Fragment> fragmentList;
|
||||||
@@ -226,91 +178,22 @@ final public class MainActivity extends AppCompatActivity implements IWinBoLLAct
|
|||||||
_MainActivity.startActivity(intent);
|
_MainActivity.startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
//初始化view,即显示的图片
|
// OnPageChangeListener接口实现
|
||||||
// void initView() {
|
|
||||||
// viewPager = findViewById(R.id.activitymainViewPager1);
|
|
||||||
// pagerAdapter = new MyPagerAdapter(getSupportFragmentManager());
|
|
||||||
// viewPager.setAdapter(pagerAdapter);
|
|
||||||
// //adapter = new MyPagerAdapter(views);
|
|
||||||
// //viewPager = findViewById(R.id.activitymainViewPager1);
|
|
||||||
// //viewPager.setAdapter(adapter);
|
|
||||||
// //linearLayout = findViewById(R.id.activitymainLinearLayout1);
|
|
||||||
// //initPoint();//初始化页面下方的点
|
|
||||||
// viewPager.setOnPageChangeListener(this);
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
|
|
||||||
//初始化所要显示的布局
|
|
||||||
// void initData() {
|
|
||||||
// LayoutInflater inflater = LayoutInflater.from(getActivity());
|
|
||||||
// View view1 = inflater.inflate(R.layout.fragment_call_log, viewPager, false);
|
|
||||||
// View view2 = inflater.inflate(R.layout.fragment_contacts, viewPager, false);
|
|
||||||
// View view3 = inflater.inflate(R.layout.fragment_log, viewPager, false);
|
|
||||||
//
|
|
||||||
// views = new ArrayList<>();
|
|
||||||
// views.add(view1);
|
|
||||||
// views.add(view2);
|
|
||||||
// views.add(view3);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// void initPoint() {
|
|
||||||
// imageViews = new ImageView[5];//实例化5个图片
|
|
||||||
// for (int i = 0; i < linearLayout.getChildCount(); i++) {
|
|
||||||
// imageViews[i] = (ImageView) linearLayout.getChildAt(i);
|
|
||||||
// imageViews[i].setImageResource(R.drawable.ic_launcher);
|
|
||||||
// imageViews[i].setOnClickListener(this);//点击导航点,即可跳转
|
|
||||||
// imageViews[i].setTag(i);//重复利用实例化的对象
|
|
||||||
// }
|
|
||||||
// currentPoint = 0;//默认第一个坐标
|
|
||||||
// imageViews[currentPoint].setImageResource(R.drawable.ic_launcher);
|
|
||||||
// }
|
|
||||||
|
|
||||||
//OnPageChangeListener接口要实现的三个方法
|
|
||||||
/* onPageScrollStateChanged(int state)
|
|
||||||
此方法是在状态改变的时候调用,其中state这个参数有三种状态:
|
|
||||||
SCROLL_STATE_DRAGGING(1)表示用户手指“按在屏幕上并且开始拖动”的状态
|
|
||||||
(手指按下但是还没有拖动的时候还不是这个状态,只有按下并且手指开始拖动后log才打出。)
|
|
||||||
SCROLL_STATE_IDLE(0)滑动动画做完的状态。
|
|
||||||
SCROLL_STATE_SETTLING(2)在“手指离开屏幕”的状态。*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onPageScrollStateChanged(int state) {
|
public void onPageScrollStateChanged(int state) {}
|
||||||
|
|
||||||
}
|
|
||||||
/* onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
|
|
||||||
当页面在滑动的时候会调用此方法,在滑动被停止之前,此方法回一直得到调用。其中三个参数的含义分别为:
|
|
||||||
|
|
||||||
position :当前页面,即你点击滑动的页面(从A滑B,则是A页面的position。
|
|
||||||
positionOffset:当前页面偏移的百分比
|
|
||||||
positionOffsetPixels:当前页面偏移的像素位置*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
|
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}
|
||||||
|
|
||||||
}
|
|
||||||
/* onPageSelected(int position)
|
|
||||||
此方法是页面滑动完后得到调用,position是你当前选中的页面的Position(位置编号)
|
|
||||||
(从A滑动到B,就是B的position)*/
|
|
||||||
public void onPageSelected(int position) {
|
|
||||||
|
|
||||||
// ImageView preView = imageViews[currentPoint];
|
|
||||||
// preView.setImageResource(R.drawable.ic_launcher);
|
|
||||||
// ImageView currView = imageViews[position];
|
|
||||||
// currView.setImageResource(R.drawable.ic_launcher);
|
|
||||||
// currentPoint = position;
|
|
||||||
}
|
|
||||||
|
|
||||||
//小圆点点击事件
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onPageSelected(int position) {}
|
||||||
// TODO Auto-generated method stub
|
|
||||||
//通过getTag(),可以判断是哪个控件
|
@Override
|
||||||
// int i = (Integer) v.getTag();
|
public void onClick(View v) {}
|
||||||
// viewPager.setCurrentItem(i);//直接跳转到某一个页面的情况
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostCreate(Bundle savedInstanceState) {
|
protected void onPostCreate(Bundle savedInstanceState) {
|
||||||
super.onPostCreate(savedInstanceState);
|
super.onPostCreate(savedInstanceState);
|
||||||
//setSubTitle("");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MyPhoneStateListener extends PhoneStateListener {
|
private class MyPhoneStateListener extends PhoneStateListener {
|
||||||
@@ -336,109 +219,31 @@ final public class MainActivity extends AppCompatActivity implements IWinBoLLAct
|
|||||||
LogUtils.d(TAG, "onDestroy() SOS");
|
LogUtils.d(TAG, "onDestroy() SOS");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// 处理传入的 Intent 数据
|
|
||||||
//
|
|
||||||
// boolean prosessIntents(Intent intent) {
|
|
||||||
// if (intent == null
|
|
||||||
// || intent.getAction() == null
|
|
||||||
// || intent.getAction().equals(""))
|
|
||||||
// return false;
|
|
||||||
//
|
|
||||||
// if (intent.getAction().equals(StringToQrCodeView.ACTION_UNITTEST_QRCODE)) {
|
|
||||||
// try {
|
|
||||||
// WinBoLLActivity clazzActivity = UnitTestActivity.class.newInstance();
|
|
||||||
// String tag = clazzActivity.getTag();
|
|
||||||
// LogUtils.d(TAG, "String tag = clazzActivity.getTag(); tag " + tag);
|
|
||||||
// Intent subIntent = new Intent(this, UnitTestActivity.class);
|
|
||||||
// subIntent.setAction(intent.getAction());
|
|
||||||
// File file = new File(getCacheDir(), UUID.randomUUID().toString());
|
|
||||||
// //取出文件uri
|
|
||||||
// Uri uri = intent.getData();
|
|
||||||
// if (uri == null) {
|
|
||||||
// uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
|
||||||
// }
|
|
||||||
// //获取文件真实地址
|
|
||||||
// String szSrcPath = UriUtils.getFileFromUri(getApplication(), uri);
|
|
||||||
// if (TextUtils.isEmpty(szSrcPath)) {
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Files.copy(Paths.get(szSrcPath), Paths.get(file.getPath()));
|
|
||||||
// //startWinBoLLActivity(subIntent, tag);
|
|
||||||
// WinBoLLActivityManager.getInstance(this).startWinBoLLActivity(this, subIntent, UnitTestActivity.class);
|
|
||||||
// } catch (IllegalAccessException | InstantiationException | IOException e) {
|
|
||||||
// LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
|
||||||
// // 函数处理异常返回失败
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// LogUtils.d(TAG, "prosessIntents|" + intent.getAction() + "|yet");
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public String getTag() {
|
|
||||||
// return TAG;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public void onBackPressed() {
|
|
||||||
// exit();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// void exit() {
|
|
||||||
// YesNoAlertDialog.OnDialogResultListener listener = new YesNoAlertDialog.OnDialogResultListener(){
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void onYes() {
|
|
||||||
// WinBoLLActivityManager.getInstance(getApplicationContext()).finishAll();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void onNo() {
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// YesNoAlertDialog.show(this, "[ " + getString(R.string.app_name) + " ]", "Exit(Yes/No).\nIs close all activity?", listener);
|
|
||||||
// }
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
getMenuInflater().inflate(R.menu.toolbar_main, menu);
|
getMenuInflater().inflate(R.menu.toolbar_main, menu);
|
||||||
return super.onCreateOptionsMenu(menu);
|
return super.onCreateOptionsMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
if (item.getItemId() == R.id.item_settings) {
|
if (item.getItemId() == R.id.item_settings) {
|
||||||
Intent intent = new Intent(this, SettingsActivity.class);
|
Intent intent = new Intent(this, SettingsActivity.class);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
//WinBoLLActivityManager.getInstance(this).startWinBoLLActivity(this, CallActivity.class);
|
|
||||||
}
|
}
|
||||||
// } else
|
|
||||||
// if (item.getItemId() == R.id.item_exit) {
|
|
||||||
// exit();
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Android M 及以上检查是否是系统默认电话应用
|
* 检查是否是系统默认电话应用
|
||||||
*/
|
*/
|
||||||
public boolean isDefaultPhoneCallApp() {
|
public boolean isDefaultPhoneCallApp() {
|
||||||
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
TelecomManager manger = (TelecomManager) getSystemService(TELECOM_SERVICE);
|
TelecomManager manger = (TelecomManager) getSystemService(TELECOM_SERVICE);
|
||||||
if (manger != null && manger.getDefaultDialerPackage() != null) {
|
if (manger != null && manger.getDefaultDialerPackage() != null) {
|
||||||
return manger.getDefaultDialerPackage().equals(getPackageName());
|
return manger.getDefaultDialerPackage().equals(getPackageName());
|
||||||
@@ -457,25 +262,11 @@ final public class MainActivity extends AppCompatActivity implements IWinBoLLAct
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
// switch (resultCode) {
|
|
||||||
// case REQUEST_HOME_ACTIVITY : {
|
|
||||||
// LogUtils.d(TAG, "REQUEST_HOME_ACTIVITY");
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// case REQUEST_ABOUT_ACTIVITY : {
|
|
||||||
// LogUtils.d(TAG, "REQUEST_ABOUT_ACTIVITY");
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// default : {
|
|
||||||
// super.onActivityResult(requestCode, resultCode, data);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
if (requestCode == DIALER_REQUEST_CODE) {
|
if (requestCode == DIALER_REQUEST_CODE) {
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
Toast.makeText(MainActivity.this, getString(R.string.app_name) + " 已成为默认电话应用",
|
Toast.makeText(MainActivity.this, getString(R.string.app_name) + " 已成为默认电话应用",
|
||||||
@@ -484,3 +275,4 @@ final public class MainActivity extends AppCompatActivity implements IWinBoLLAct
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -41,6 +41,10 @@ public class CallLogAdapter extends RecyclerView.Adapter<CallLogAdapter.CallLogV
|
|||||||
this.callLogList = callLogList;
|
this.callLogList = callLogList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void relaodContacts() {
|
||||||
|
this.mContactUtils.relaodContacts();
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public CallLogViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public CallLogViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
package cc.winboll.studio.contacts.beans;
|
package cc.winboll.studio.contacts.beans;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Author ZhanGSKen<zhangsken@188.com>
|
* @Author ZhanGSKen&豆包大模型<zhangsken@188.com>
|
||||||
* @Date 2025/02/26 13:37:00
|
* @Date 2025/08/30 14:32
|
||||||
* @Describe ContactModel
|
* @Describe 联系人信息数据模型
|
||||||
*/
|
*/
|
||||||
import net.sourceforge.pinyin4j.PinyinHelper;
|
import net.sourceforge.pinyin4j.PinyinHelper;
|
||||||
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
|
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
|
||||||
@@ -18,13 +18,18 @@ public class ContactModel {
|
|||||||
private String name;
|
private String name;
|
||||||
private String number;
|
private String number;
|
||||||
private String pinyin;
|
private String pinyin;
|
||||||
|
// 新增:存储姓名的拼音首字母(如"啊牛"→"an")
|
||||||
|
private String pinyinFirstLetter;
|
||||||
|
|
||||||
public ContactModel(String name, String number) {
|
public ContactModel(String name, String number) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.number = number.replaceAll("\\s", "");
|
this.number = number.replaceAll("\\s", "");
|
||||||
this.pinyin = convertToPinyin(name);
|
this.pinyin = convertToPinyin(name);
|
||||||
|
// 初始化时生成拼音首字母
|
||||||
|
this.pinyinFirstLetter = convertToPinyinFirstLetter(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 原方法:转换为全拼(如"啊牛"→"aniu")
|
||||||
private String convertToPinyin(String chinese) {
|
private String convertToPinyin(String chinese) {
|
||||||
HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
|
HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
|
||||||
format.setCaseType(HanyuPinyinCaseType.LOWERCASE);
|
format.setCaseType(HanyuPinyinCaseType.LOWERCASE);
|
||||||
@@ -33,22 +38,55 @@ public class ContactModel {
|
|||||||
StringBuilder pinyin = new StringBuilder();
|
StringBuilder pinyin = new StringBuilder();
|
||||||
for (int i = 0; i < chinese.length(); i++) {
|
for (int i = 0; i < chinese.length(); i++) {
|
||||||
char ch = chinese.charAt(i);
|
char ch = chinese.charAt(i);
|
||||||
if (Character.toString(ch).matches("[\\u4e00-\\u9fa5]")) {
|
if (Character.toString(ch).matches("[\\u4e00-\\u9fa5]")) { // 仅处理汉字
|
||||||
try {
|
try {
|
||||||
String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(ch, format);
|
String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(ch, format);
|
||||||
if (pinyinArray != null) {
|
if (pinyinArray != null && pinyinArray.length > 0) {
|
||||||
pinyin.append(pinyinArray[0]);
|
pinyin.append(pinyinArray[0]); // 取第一个拼音(多音字默认首选项)
|
||||||
}
|
}
|
||||||
} catch (BadHanyuPinyinOutputFormatCombination e) {
|
} catch (BadHanyuPinyinOutputFormatCombination e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
pinyin.append(ch);
|
pinyin.append(ch); // 非汉字直接拼接(如字母、数字、符号)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return pinyin.toString();
|
return pinyin.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 新增:转换为拼音首字母(如"啊牛"→"an")
|
||||||
|
private String convertToPinyinFirstLetter(String chinese) {
|
||||||
|
HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
|
||||||
|
format.setCaseType(HanyuPinyinCaseType.LOWERCASE);
|
||||||
|
format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
|
||||||
|
|
||||||
|
StringBuilder firstLetters = new StringBuilder();
|
||||||
|
for (int i = 0; i < chinese.length(); i++) {
|
||||||
|
char ch = chinese.charAt(i);
|
||||||
|
if (Character.toString(ch).matches("[\\u4e00-\\u9fa5]")) { // 仅处理汉字
|
||||||
|
try {
|
||||||
|
String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(ch, format);
|
||||||
|
if (pinyinArray != null && pinyinArray.length > 0) {
|
||||||
|
// 取拼音的第一个字母(如"a"、"niu"→"a"、"n")
|
||||||
|
firstLetters.append(pinyinArray[0].charAt(0));
|
||||||
|
}
|
||||||
|
} catch (BadHanyuPinyinOutputFormatCombination e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 非汉字可根据需求处理:此处保留原字符(如"李3"→"l3","张A"→"za")
|
||||||
|
firstLetters.append(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return firstLetters.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增:获取拼音首字母
|
||||||
|
public String getPinyinFirstLetter() {
|
||||||
|
return pinyinFirstLetter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 原有getter方法
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
@@ -11,6 +11,7 @@ import cc.winboll.studio.contacts.dun.Rules;
|
|||||||
import cc.winboll.studio.libappbase.LogUtils;
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
import com.hjq.toast.ToastUtils;
|
import com.hjq.toast.ToastUtils;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileFilter;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -155,6 +156,62 @@ public class TomCat {
|
|||||||
return mContext.getExternalFilesDir(TAG);
|
return mContext.getExternalFilesDir(TAG);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public File getBoBullToonDataFolder() {
|
||||||
|
File fCheckRoot = getWorkingFolder();
|
||||||
|
if (fCheckRoot == null || !fCheckRoot.exists()) {
|
||||||
|
return fCheckRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归查找符合条件的文件夹
|
||||||
|
File targetFolder = findTargetFolder(fCheckRoot);
|
||||||
|
return targetFolder != null ? targetFolder : fCheckRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 递归查找同时包含LICENSE和README.md文件的文件夹
|
||||||
|
*/
|
||||||
|
private File findTargetFolder(File currentFolder) {
|
||||||
|
// 检查当前文件夹是否符合条件
|
||||||
|
if (hasRequiredFiles(currentFolder)) {
|
||||||
|
return currentFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找子文件夹(Java 7不支持方法引用,用匿名内部类过滤)
|
||||||
|
File[] subFolders = currentFolder.listFiles(new FileFilter() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(File file) {
|
||||||
|
return file.isDirectory(); // 仅保留子文件夹
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (subFolders != null) {
|
||||||
|
for (File subFolder : subFolders) {
|
||||||
|
File result = findTargetFolder(subFolder);
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查文件夹中是否同时存在LICENSE和README.md文件
|
||||||
|
*/
|
||||||
|
private boolean hasRequiredFiles(File folder) {
|
||||||
|
if (folder == null || !folder.isDirectory()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查两个文件是否同时存在且均为文件(非文件夹)
|
||||||
|
File licenseFile = new File(folder, "LICENSE");
|
||||||
|
File readmeFile = new File(folder, "README.md");
|
||||||
|
|
||||||
|
return licenseFile.exists() && licenseFile.isFile()
|
||||||
|
&& readmeFile.exists() && readmeFile.isFile();
|
||||||
|
}
|
||||||
|
|
||||||
public void cleanBoBullToon() {
|
public void cleanBoBullToon() {
|
||||||
String destinationFolder = getWorkingFolder().getPath(); // 替换为实际的目标文件夹路径
|
String destinationFolder = getWorkingFolder().getPath(); // 替换为实际的目标文件夹路径
|
||||||
// 删除旧文件
|
// 删除旧文件
|
||||||
@@ -170,9 +227,9 @@ public class TomCat {
|
|||||||
|
|
||||||
public boolean loadPhoneBoBullToon() {
|
public boolean loadPhoneBoBullToon() {
|
||||||
listPhoneBoBullToon.clear();
|
listPhoneBoBullToon.clear();
|
||||||
File fBoBullToon = new File(getWorkingFolder(), "bobulltoon");
|
File fBoBullToon = getBoBullToonDataFolder();
|
||||||
if (fBoBullToon.exists()) {
|
if (fBoBullToon.exists()) {
|
||||||
LogUtils.d(TAG, String.format("getWorkingFolder() %s", getWorkingFolder()));
|
LogUtils.d(TAG, String.format("getBoBullToonDataFolder() %s", getWorkingFolder()));
|
||||||
for (File userFolder : fBoBullToon.listFiles()) {
|
for (File userFolder : fBoBullToon.listFiles()) {
|
||||||
if (userFolder.isDirectory()) {
|
if (userFolder.isDirectory()) {
|
||||||
for (File recordFile : userFolder.listFiles()) {
|
for (File recordFile : userFolder.listFiles()) {
|
||||||
|
@@ -161,4 +161,12 @@ public class CallLogFragment extends Fragment {
|
|||||||
_CallLogFragment.triggerUpdate();
|
_CallLogFragment.triggerUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
//ToastUtils.show("onResume");
|
||||||
|
callLogAdapter.relaodContacts();
|
||||||
|
readCallLog(); // 窗口回显时更新通话记录
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,15 +1,18 @@
|
|||||||
package cc.winboll.studio.contacts.fragments;
|
package cc.winboll.studio.contacts.fragments;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Author ZhanGSKen<zhangsken@188.com>
|
* @Author ZhanGSKen&豆包大模型<zhangsken@188.com>
|
||||||
* @Date 2025/02/20 12:57:50
|
* @Date 2025/08/30 14:32
|
||||||
* @Describe 联系人
|
* @Describe 联系人视图
|
||||||
*/
|
*/
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
import android.provider.ContactsContract;
|
import android.provider.ContactsContract;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
@@ -27,24 +30,39 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||||||
import cc.winboll.studio.contacts.R;
|
import cc.winboll.studio.contacts.R;
|
||||||
import cc.winboll.studio.contacts.adapters.ContactAdapter;
|
import cc.winboll.studio.contacts.adapters.ContactAdapter;
|
||||||
import cc.winboll.studio.contacts.beans.ContactModel;
|
import cc.winboll.studio.contacts.beans.ContactModel;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
import com.hjq.toast.ToastUtils;
|
import com.hjq.toast.ToastUtils;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
public class ContactsFragment extends Fragment {
|
public class ContactsFragment extends Fragment {
|
||||||
|
|
||||||
public static final String TAG = "ContactsFragment";
|
public static final String TAG = "ContactsFragment";
|
||||||
|
|
||||||
private static final String ARG_PAGE = "ARG_PAGE";
|
private static final String ARG_PAGE = "ARG_PAGE";
|
||||||
private int mPage;
|
|
||||||
|
|
||||||
private static final int REQUEST_READ_CONTACTS = 1;
|
private static final int REQUEST_READ_CONTACTS = 1;
|
||||||
|
|
||||||
|
private int mPage;
|
||||||
private RecyclerView recyclerView;
|
private RecyclerView recyclerView;
|
||||||
private ContactAdapter contactAdapter;
|
private ContactAdapter contactAdapter;
|
||||||
private List<ContactModel> contactList = new ArrayList<>();
|
|
||||||
private List<ContactModel> originalContactList = new ArrayList<>();
|
|
||||||
private EditText searchEditText;
|
private EditText searchEditText;
|
||||||
|
private Button btnDial;
|
||||||
|
private boolean isViewInitialized = false; // 标记视图是否已初始化
|
||||||
|
|
||||||
|
// 静态缓存:全局复用联系人数据
|
||||||
|
private static List<ContactModel> sCachedOriginalList = new ArrayList<ContactModel>();
|
||||||
|
private static List<ContactModel> sCachedFilteredList = new ArrayList<ContactModel>();
|
||||||
|
|
||||||
|
// 当前页面数据容器
|
||||||
|
private List<ContactModel> contactList = new ArrayList<ContactModel>();
|
||||||
|
private List<ContactModel> originalContactList = new ArrayList<ContactModel>();
|
||||||
|
|
||||||
|
// 异步工具
|
||||||
|
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||||
|
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||||
|
private boolean isDataLoaded = false;
|
||||||
|
|
||||||
|
|
||||||
public static ContactsFragment newInstance(int page) {
|
public static ContactsFragment newInstance(int page) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
@@ -65,103 +83,272 @@ public class ContactsFragment extends Fragment {
|
|||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
return inflater.inflate(R.layout.fragment_contacts, container, false);
|
// 加载布局(已移除进度条相关代码)
|
||||||
|
View view = inflater.inflate(R.layout.fragment_contacts, container, false);
|
||||||
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
recyclerView = view.findViewById(R.id.contacts_recycler_view);
|
// 初始化RecyclerView
|
||||||
|
recyclerView = (RecyclerView) view.findViewById(R.id.contacts_recycler_view);
|
||||||
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
contactList = new ArrayList<ContactModel>();
|
||||||
contactAdapter = new ContactAdapter(getContext(), contactList);
|
contactAdapter = new ContactAdapter(getContext(), contactList);
|
||||||
recyclerView.setAdapter(contactAdapter);
|
recyclerView.setAdapter(contactAdapter);
|
||||||
|
// 初始隐藏列表,数据加载后显示
|
||||||
|
recyclerView.setVisibility(View.GONE);
|
||||||
|
|
||||||
searchEditText = view.findViewById(R.id.search_edit_text);
|
// 绑定搜索框和拨号按钮
|
||||||
searchEditText.addTextChangedListener(new TextWatcher() {
|
searchEditText = (EditText) view.findViewById(R.id.search_edit_text);
|
||||||
@Override
|
btnDial = (Button) view.findViewById(R.id.btn_dial);
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
// 初始隐藏搜索相关控件,延迟到首次可见时显示
|
||||||
|
searchEditText.setVisibility(View.GONE);
|
||||||
|
btnDial.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 首次可见时初始化资源
|
||||||
@Override
|
@Override
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
public void onResume() {
|
||||||
filterContacts(s.toString());
|
super.onResume();
|
||||||
|
if (!isViewInitialized) {
|
||||||
|
initSearchAndDial(); // 初始化搜索和拨号功能
|
||||||
|
checkContactPermission(); // 检查权限并加载数据
|
||||||
|
isViewInitialized = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 初始化搜索框和拨号按钮
|
||||||
|
private void initSearchAndDial() {
|
||||||
|
// 显示搜索相关控件
|
||||||
|
searchEditText.setVisibility(View.VISIBLE);
|
||||||
|
btnDial.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
// 搜索框防抖监听
|
||||||
|
searchEditText.addTextChangedListener(new DebounceTextWatcher(300) {
|
||||||
@Override
|
@Override
|
||||||
public void afterTextChanged(Editable s) {
|
public void onDebounceTextChanged(String query) {
|
||||||
|
filterContacts(query);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
|
// 拨号按钮点击事件
|
||||||
ActivityCompat.requestPermissions(requireActivity(), new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_READ_CONTACTS);
|
|
||||||
} else {
|
|
||||||
readContacts();
|
|
||||||
}
|
|
||||||
|
|
||||||
Button btnDial = view.findViewById(R.id.btn_dial);
|
|
||||||
btnDial.setOnClickListener(new View.OnClickListener() {
|
btnDial.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View p1) {
|
public void onClick(View v) {
|
||||||
|
|
||||||
String phoneNumber = searchEditText.getText().toString().replaceAll("\\s", "");
|
String phoneNumber = searchEditText.getText().toString().replaceAll("\\s", "");
|
||||||
//phoneNumber = "+8616769764848";
|
if (phoneNumber.isEmpty()) {
|
||||||
ToastUtils.show(phoneNumber);
|
ToastUtils.show("请输入号码");
|
||||||
|
return;
|
||||||
|
}
|
||||||
Intent intent = new Intent(Intent.ACTION_CALL);
|
Intent intent = new Intent(Intent.ACTION_CALL);
|
||||||
intent.setData(android.net.Uri.parse("tel:" + phoneNumber));
|
intent.setData(android.net.Uri.parse("tel:" + phoneNumber));
|
||||||
// 添加 FLAG_ACTIVITY_NEW_TASK 标志
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 权限检查
|
||||||
|
private void checkContactPermission() {
|
||||||
|
if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
ActivityCompat.requestPermissions(requireActivity(), new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_READ_CONTACTS);
|
||||||
|
} else {
|
||||||
|
loadContacts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载联系人(延迟到首次可见时)
|
||||||
|
private void loadContacts() {
|
||||||
|
// 若有缓存,直接复用
|
||||||
|
if (!sCachedOriginalList.isEmpty() && !sCachedFilteredList.isEmpty()) {
|
||||||
|
originalContactList.clear();
|
||||||
|
originalContactList.addAll(sCachedOriginalList);
|
||||||
|
contactList.clear();
|
||||||
|
contactList.addAll(sCachedFilteredList);
|
||||||
|
contactAdapter.notifyDataSetChanged();
|
||||||
|
recyclerView.setVisibility(View.VISIBLE); // 显示列表
|
||||||
|
isDataLoaded = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 无缓存时异步加载
|
||||||
|
if (!isDataLoaded) {
|
||||||
|
recyclerView.setVisibility(View.GONE); // 加载中隐藏列表
|
||||||
|
|
||||||
|
executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// 子线程读取联系人
|
||||||
|
final List<ContactModel> tempList = readContactsInBackground();
|
||||||
|
|
||||||
|
// 主线程更新UI
|
||||||
|
mainHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// 更新缓存
|
||||||
|
sCachedOriginalList.clear();
|
||||||
|
sCachedOriginalList.addAll(tempList);
|
||||||
|
sCachedFilteredList.clear();
|
||||||
|
sCachedFilteredList.addAll(tempList);
|
||||||
|
|
||||||
|
// 更新当前列表
|
||||||
|
originalContactList.clear();
|
||||||
|
originalContactList.addAll(sCachedOriginalList);
|
||||||
|
contactList.clear();
|
||||||
|
contactList.addAll(sCachedFilteredList);
|
||||||
|
contactAdapter.notifyDataSetChanged();
|
||||||
|
LogUtils.d(TAG, String.format("联系人加载完成,共%d条数据", contactList.size()));
|
||||||
|
|
||||||
|
// 数据加载后显示列表
|
||||||
|
recyclerView.setVisibility(View.VISIBLE);
|
||||||
|
isDataLoaded = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 子线程读取联系人
|
||||||
|
private List<ContactModel> readContactsInBackground() {
|
||||||
|
List<ContactModel> tempList = new ArrayList<ContactModel>();
|
||||||
|
Cursor cursor = null;
|
||||||
|
try {
|
||||||
|
// 查询联系人姓名和号码
|
||||||
|
cursor = requireContext().getContentResolver().query(
|
||||||
|
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
|
||||||
|
new String[]{
|
||||||
|
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
|
||||||
|
ContactsContract.CommonDataKinds.Phone.NUMBER
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
int nameIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME);
|
||||||
|
int numberIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
|
||||||
|
|
||||||
|
do {
|
||||||
|
String name = cursor.getString(nameIndex);
|
||||||
|
String number = cursor.getString(numberIndex).replaceAll("\\s", ""); // 去除空格
|
||||||
|
tempList.add(new ContactModel(name, number));
|
||||||
|
} while (cursor.moveToNext());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.d(TAG, "读取联系人失败:" + e);
|
||||||
|
} finally {
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.close(); // 关闭游标,避免内存泄漏
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tempList;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过滤联系人
|
||||||
|
private void filterContacts(String query) {
|
||||||
|
contactList.clear();
|
||||||
|
if (query.isEmpty()) {
|
||||||
|
contactList.addAll(originalContactList);
|
||||||
|
sCachedFilteredList.clear();
|
||||||
|
sCachedFilteredList.addAll(originalContactList);
|
||||||
|
} else {
|
||||||
|
String lowerQuery = query.toLowerCase();
|
||||||
|
for (ContactModel contact : originalContactList) {
|
||||||
|
// 匹配姓名、全拼、简拼、号码
|
||||||
|
boolean matchName = contact.getName().toLowerCase().contains(lowerQuery);
|
||||||
|
boolean matchPinyin = contact.getPinyin().toLowerCase().contains(lowerQuery);
|
||||||
|
boolean matchFirstLetter = contact.getPinyinFirstLetter().toLowerCase().contains(lowerQuery);
|
||||||
|
boolean matchNumber = contact.getNumber().contains(lowerQuery);
|
||||||
|
|
||||||
|
if (matchName || matchPinyin || matchFirstLetter || matchNumber) {
|
||||||
|
contactList.add(contact);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sCachedFilteredList.clear();
|
||||||
|
sCachedFilteredList.addAll(contactList);
|
||||||
|
}
|
||||||
|
contactAdapter.notifyDataSetChanged();
|
||||||
|
// 过滤后确保列表可见
|
||||||
|
recyclerView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 权限回调
|
||||||
@Override
|
@Override
|
||||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
if (requestCode == REQUEST_READ_CONTACTS) {
|
if (requestCode == REQUEST_READ_CONTACTS) {
|
||||||
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
readContacts();
|
loadContacts(); // 授权后加载联系人
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void readContacts() {
|
|
||||||
contactList.clear();
|
|
||||||
originalContactList.clear();
|
|
||||||
Cursor cursor = requireContext().getContentResolver().query(
|
|
||||||
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME + " ASC");
|
|
||||||
|
|
||||||
if (cursor != null) {
|
|
||||||
while (cursor.moveToNext()) {
|
|
||||||
String name = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
|
|
||||||
String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
|
|
||||||
ContactModel contact = new ContactModel(name, number);
|
|
||||||
contactList.add(contact);
|
|
||||||
originalContactList.add(contact);
|
|
||||||
}
|
|
||||||
cursor.close();
|
|
||||||
contactAdapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void filterContacts(String query) {
|
|
||||||
contactList.clear();
|
|
||||||
if (query.isEmpty()) {
|
|
||||||
contactList.addAll(originalContactList);
|
|
||||||
} else {
|
} else {
|
||||||
for (ContactModel contact : originalContactList) {
|
ToastUtils.show("请授予联系人权限以查看联系人列表");
|
||||||
if (contact.getName().toLowerCase().contains(query.toLowerCase()) ||
|
recyclerView.setVisibility(View.VISIBLE); // 显示空列表
|
||||||
contact.getPinyin().toLowerCase().contains(query.toLowerCase()) ||
|
}
|
||||||
contact.getNumber().toLowerCase().contains(query.toLowerCase())) {
|
}
|
||||||
contactList.add(contact);
|
}
|
||||||
|
|
||||||
|
// 防抖TextWatcher(Java 7实现)
|
||||||
|
public abstract static class DebounceTextWatcher implements TextWatcher {
|
||||||
|
private final long debounceDelay;
|
||||||
|
private Handler handler = new Handler(Looper.getMainLooper());
|
||||||
|
private Runnable pendingRunnable;
|
||||||
|
|
||||||
|
public DebounceTextWatcher(long debounceDelay) {
|
||||||
|
this.debounceDelay = debounceDelay;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||||
|
// 无需处理
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(final CharSequence s, int start, int before, int count) {
|
||||||
|
// 移除之前的延迟任务
|
||||||
|
if (pendingRunnable != null) {
|
||||||
|
handler.removeCallbacks(pendingRunnable);
|
||||||
|
}
|
||||||
|
// 延迟执行过滤
|
||||||
|
pendingRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
onDebounceTextChanged(s.toString());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
handler.postDelayed(pendingRunnable, debounceDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(Editable s) {
|
||||||
|
// 无需处理
|
||||||
|
}
|
||||||
|
|
||||||
|
// 抽象方法:防抖后的回调
|
||||||
|
public abstract void onDebounceTextChanged(String query);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 资源释放
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
executor.shutdown(); // 关闭线程池
|
||||||
|
mainHandler.removeCallbacksAndMessages(null); // 清除未执行任务
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fragment隐藏/显示时的处理
|
||||||
|
@Override
|
||||||
|
public void onHiddenChanged(boolean hidden) {
|
||||||
|
super.onHiddenChanged(hidden);
|
||||||
|
if (!hidden && isDataLoaded) {
|
||||||
|
// 复用缓存数据并显示列表
|
||||||
|
contactList.clear();
|
||||||
|
contactList.addAll(sCachedFilteredList);
|
||||||
|
contactAdapter.notifyDataSetChanged();
|
||||||
|
recyclerView.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
contactAdapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
package cc.winboll.studio.contacts.utils;
|
package cc.winboll.studio.contacts.utils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Author ZhanGSKen<zhangsken@188.com>
|
* @Author ZhanGSKen&豆包大模型<zhangsken@188.com>
|
||||||
* @Date 2025/03/06 21:08:16
|
* @Date 2025/08/30 14:32
|
||||||
* @Describe ContactUtils
|
* @Describe 联系人工具集
|
||||||
*/
|
*/
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
@@ -2,6 +2,6 @@
|
|||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<string name="app_name">Contacts</string>
|
<string name="app_name">Contacts</string>
|
||||||
<string name="default_bobulltoon_url">https://gitea.winboll.cc/Studio/BoBullToon/archive/main.zip</string>
|
<string name="default_bobulltoon_url">https://gitee.com/zhangsken/bobulltoon/repository/archive/main.zip</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
Reference in New Issue
Block a user