diff --git a/app/build.gradle b/app/build.gradle
index 2416e85..39f9ed5 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -20,7 +20,7 @@ android {
applicationId "com.ttstd.dialer"
//There are no CERT files because If the mini sdk version is 23+, the AGP will ignore the V1 scheme signature.
- minSdkVersion 23
+// minSdkVersion 23
targetSdkVersion 37
versionCode 1
versionName "1.0"
@@ -95,6 +95,21 @@ android {
}
}
+ flavorDimensions "version"
+ productFlavors {
+ // 用于正常开发和发布的版本
+ normal {
+ dimension "version"
+ minSdkVersion 23 // 你的原始最低版本
+ }
+ // 专门用于调试高版本特性的版本
+ debugApi26 {
+ dimension "version"
+ minSdkVersion 31 // 为了使用 Database Inspector
+ }
+ }
+
+
signingConfigs {
keypub {
storeFile file(rootProject.ext.signingConfigs.keypub.storeFile)
@@ -194,9 +209,9 @@ dependencies {
implementation project(path: ':iconloader')
// 添加 Kotlin 标准库
- implementation "org.jetbrains.kotlin:kotlin-stdlib:2.2.10"
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
// 添加这行,使用 BOM 统一 Kotlin 相关库的版本
- implementation(platform("org.jetbrains.kotlin:kotlin-bom:2.2.10"))
+ implementation(platform("org.jetbrains.kotlin:kotlin-bom:$kotlin_version"))
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.11.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-rx3:1.11.0'
@@ -230,7 +245,9 @@ dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.3.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0'
- implementation 'com.google.android.material:material:1.13.0'
+ implementation 'com.google.android.material:material:1.14.0'
+ implementation 'com.googlecode.libphonenumber:libphonenumber:9.0.30'
+
//glide
implementation 'com.github.bumptech.glide:glide:4.15.1'
kapt 'com.github.bumptech.glide:compiler:4.15.1'
@@ -242,12 +259,13 @@ dependencies {
//RxJava
implementation 'io.reactivex.rxjava3:rxjava:3.1.12'
implementation 'io.reactivex.rxjava3:rxandroid:3.0.2'
- //
+
+ implementation 'com.squareup.moshi:moshi:1.15.2'
implementation 'com.squareup.okhttp3:okhttp:5.3.2'
implementation 'com.squareup.okhttp3:logging-interceptor:5.3.2'
implementation 'com.squareup.retrofit2:retrofit:3.0.0'
implementation 'com.squareup.retrofit2:converter-gson:3.0.0'
-// implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
+// implementation 'com.squareup.retrofit2:adapter-rxjava2:3.0.0'
implementation "com.squareup.retrofit2:adapter-rxjava3:3.0.0"
//Gson
implementation 'com.google.code.gson:gson:2.14.0'
@@ -262,7 +280,8 @@ dependencies {
implementation 'com.jakewharton.rxbinding4:rxbinding:4.0.0'
/*https://github.com/JeremyLiao/LiveEventBus*/
- implementation 'com.jeremyliao:live-event-bus-x:1.7.3'
+// implementation 'io.github.jeremyliao:live-event-bus:1.8.0'
+ implementation 'com.github.neo-turak:LiveEventBus:1.8.1'
implementation 'com.facebook.rebound:rebound:0.3.8'
//MMKV
@@ -344,10 +363,8 @@ dependencies {
implementation 'com.github.getActivity:Toaster:12.6'
// 权限请求框架:https://github.com/getActivity/XXPermissions
implementation 'com.github.getActivity:XXPermissions:20.0'
- //autosize会改变第三方view的大小
- //https://github.com/JessYanCoding/AndroidAutoSize
-// implementation 'me.jessyan:autosize:1.2.1'
-
+ implementation 'com.github.zcweng:switch-button:0.0.3@aar'
+ implementation "com.github.kongzue.DialogX:DialogX:0.0.50"
}
// 在 dependencies 之后添加
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 9567e3a..b45c3d6 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -80,6 +80,10 @@
android:name=".activity.contact.list.ContactListActivity"
android:launchMode="singleTask"
android:screenOrientation="portrait" />
+
{
@@ -39,18 +43,30 @@ public class ContactAddActivity extends BaseMvvmActivity() {
+ mViewModel.mOnlineIdData.observe(this, new Observer() {
@Override
public void onChanged(Long aLong) {
if (aLong > 0) {
+ Toaster.show("添加成功");
finish();
+ } else {
+
}
}
});
+ mViewModel.mDbIdData.observe(this, new Observer() {
+ @Override
+ public void onChanged(Long aLong) {
+ finish();
+ }
+ });
+
+
}
public class BtnClick {
@@ -59,9 +75,53 @@ public class ContactAddActivity extends BaseMvvmActivity mOnlineIdData = new MutableLiveData<>();
+ public MutableLiveData mDbIdData = new MutableLiveData<>();
+
@Override
public void setContext(Context context) {
super.setContext(context);
mRepository = new ContactRepository(context);
}
- public MutableLiveData mIntegerMutableLiveData = new MutableLiveData<>();
-
- public void saveContact(String name, String phone) {
- Observable.create(new ObservableOnSubscribe() {
- @Override
- public void subscribe(@NonNull ObservableEmitter emitter) throws Throwable {
- int count = mRepository.getTotalCount() + 1;
- ContactInfo contactInfo = new ContactInfo(name, phone, count);
- Logger.e(TAG, "saveContact: " + contactInfo);
- emitter.onNext(mRepository.insert(contactInfo));
- emitter.onComplete();
- }
- }).compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
+ public void addContact(ContactInfo contactInfo) {
+ OkHttpManager.getInstance().getContactInsertObservable(contactInfo, getLifecycle())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
+ .flatMap(baseResponse -> {
+ Logger.e(TAG, "addContact", "网络请求响应: " + baseResponse);
+ if (baseResponse.isSuccess()) {
+ Long id = baseResponse.getData();
+ mOnlineIdData.setValue(id);
+ return Observable.just(id);
+ } else {
+ Logger.w(TAG, "业务失败,降级到本地保存: " + baseResponse.getMsg());
+ return saveContactObservable(contactInfo);
+ }
+ })
+ .onErrorResumeNext(throwable -> {
+ Logger.e(TAG, "网络异常,降级到本地保存: " + throwable.getMessage());
+ return saveContactObservable(contactInfo);
+ })
+ .compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
+ .subscribe(new Observer() {
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {
+ Logger.d(TAG, "开始添加联系人");
+ }
+
+ @Override
+ public void onNext(@NonNull Long id) {
+ Logger.d(TAG, "联系人保存成功,ID: " + id);
+ if (mOnlineIdData.getValue() == null) {
+ mDbIdData.setValue(id);
+ }
+ }
+
+ @Override
+ public void onError(@NonNull Throwable e) {
+ Logger.e(TAG, "添加联系人最终失败: " + e.getMessage());
+ }
+
+ @Override
+ public void onComplete() {
+ Logger.d(TAG, "添加联系人流程完成");
+ }
+ });
+ }
+
+ private Observable saveContactObservable(ContactInfo contactInfo) {
+ return Observable.create((ObservableOnSubscribe) emitter -> {
+ Logger.d(TAG, "执行本地保存: " + contactInfo);
+ contactInfo.setPosition(mRepository.getTotalCount() + 1);
+ contactInfo.setLocalId(System.currentTimeMillis());
+ long id = mRepository.insert(contactInfo);
+ Logger.d(TAG, "执行本地保存: id = " + id);
+ emitter.onNext(id);
+ emitter.onComplete();
+ }).subscribeOn(Schedulers.io());
+ }
+
+ public void saveContact(ContactInfo contactInfo) {
+ saveContactObservable(contactInfo)
+ .compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
+ .observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer() {
@Override
public void onSubscribe(@NonNull Disposable d) {
@@ -55,7 +105,7 @@ public class ContactAddViewModel extends BaseViewModel {
+ private ContactInfo mContactInfo;
@Override
public boolean setNightMode() {
return true;
}
+ @Override
+ public boolean setfitWindow() {
+ return true;
+ }
+
@Override
protected int getLayoutId() {
return R.layout.activity_contact_edit;
@@ -33,15 +47,65 @@ public class ContactEditActivity extends BaseMvvmActivity() {
+ @Override
+ public void onChanged(Boolean aBoolean) {
+ if (aBoolean) {
+ Toaster.show("编辑成功");
+ finish();
+ }
+ }
+ });
}
-
- public class BtnClick{
+ public class BtnClick {
public void exit(View view) {
finish();
}
+
+ public void update(View view) {
+ ContactInfo contactInfo = new ContactInfo(mContactInfo);
+ if (mViewDataBinding.etName.getText() == null) {
+ Toaster.show("请输入姓名");
+ return;
+ }
+ String name = mViewDataBinding.etName.getText().toString();
+
+ if (mViewDataBinding.etPhone.getText() == null) {
+ Toaster.show("请输入手机号码");
+ return;
+ }
+
+ String phone = mViewDataBinding.etPhone.getText().toString();
+
+ if (!PhoneUtils.isValidPhone(phone)) {
+ Toaster.show("手机号码格式错误");
+ return;
+ }
+
+ contactInfo.setName(name);
+ contactInfo.setPhoneNumber(phone);
+ if (mViewDataBinding.etRemark.getText() != null) {
+ String remark = mViewDataBinding.etRemark.getText().toString();
+ contactInfo.setNickName(remark);
+ } else {
+ contactInfo.setNickName("");
+ }
+ contactInfo.setEmergency(mViewDataBinding.sbEmergency.isChecked());
+ contactInfo.setShowDesktop(mViewDataBinding.sbShow.isChecked());
+ contactInfo.setUpdateTime(System.currentTimeMillis());
+
+ mViewModel.updateContact(mContactInfo.getId(), contactInfo);
+ }
}
}
diff --git a/app/src/main/java/com/ttstd/dialer/activity/contact/edit/ContactEditViewModel.java b/app/src/main/java/com/ttstd/dialer/activity/contact/edit/ContactEditViewModel.java
index 45fcc9e..b47383c 100644
--- a/app/src/main/java/com/ttstd/dialer/activity/contact/edit/ContactEditViewModel.java
+++ b/app/src/main/java/com/ttstd/dialer/activity/contact/edit/ContactEditViewModel.java
@@ -1,8 +1,42 @@
package com.ttstd.dialer.activity.contact.edit;
+import android.util.Log;
+
+import androidx.lifecycle.MutableLiveData;
+
+import com.hjq.toast.Toaster;
import com.trello.rxlifecycle4.android.ActivityEvent;
import com.ttstd.dialer.base.mvvm.BaseViewModel;
+import com.ttstd.dialer.bean.BaseResponse;
import com.ttstd.dialer.databinding.ActivityContactEditBinding;
+import com.ttstd.dialer.db.contact.ContactInfo;
+import com.ttstd.dialer.network.BaseObserver;
+import com.ttstd.dialer.network.OkHttpManager;
-public class ContactEditViewModel extends BaseViewModel {
+public class ContactEditViewModel extends BaseViewModel {
+
+ public MutableLiveData mBooleanMutableLiveData = new MutableLiveData<>();
+
+ public void updateContact(long id, ContactInfo contactInfo) {
+ OkHttpManager.getInstance().getContactUpdateObservable(id, contactInfo, getLifecycle())
+ .safeSubscribe(new BaseObserver>() {
+ @Override
+ public void onSuccess(BaseResponse baseResponse) {
+ Log.e("updateContact", "onSuccess: " + baseResponse);
+ if (baseResponse.isSuccess()) {
+ mBooleanMutableLiveData.postValue(true);
+ } else {
+ mBooleanMutableLiveData.postValue(false);
+ Toaster.show(baseResponse.getMsg());
+ }
+ }
+
+ @Override
+ public void onFailure(Throwable e) {
+ Log.e("updateContact", "onFailure: " + e.getMessage());
+ mBooleanMutableLiveData.postValue(false);
+ Toaster.show("网络错误,请稍后再试");
+ }
+ });
+ }
}
diff --git a/app/src/main/java/com/ttstd/dialer/activity/contact/list/ContactListActivity.java b/app/src/main/java/com/ttstd/dialer/activity/contact/list/ContactListActivity.java
index f5a9541..2d97f67 100644
--- a/app/src/main/java/com/ttstd/dialer/activity/contact/list/ContactListActivity.java
+++ b/app/src/main/java/com/ttstd/dialer/activity/contact/list/ContactListActivity.java
@@ -8,14 +8,21 @@ import androidx.annotation.NonNull;
import androidx.lifecycle.Observer;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
+import com.kongzue.dialogx.dialogs.TipDialog;
+import com.kongzue.dialogx.dialogs.WaitDialog;
import com.ttstd.dialer.R;
import com.ttstd.dialer.activity.contact.add.ContactAddActivity;
+import com.ttstd.dialer.activity.contact.edit.ContactEditActivity;
+import com.ttstd.dialer.activity.contact.test.ContactTestActivity;
import com.ttstd.dialer.adapter.ContactInfoAdapter;
import com.ttstd.dialer.base.mvvm.BaseMvvmActivity;
import com.ttstd.dialer.databinding.ActivityContactListBinding;
import com.ttstd.dialer.db.contact.ContactInfo;
-import com.ttstd.dialer.fragment.dialog.call.CallFragment;
+import com.ttstd.dialer.fragment.dialog.contact.call.CallFragment;
+import com.ttstd.dialer.fragment.dialog.contact.delete.ContactDeleteDialogFragment;
+import com.ttstd.dialer.fragment.dialog.contact.edit.EditDialogFragment;
import com.ttstd.dialer.utils.Logger;
import com.ttstd.dialer.view.ItemTouchHelperCallback;
@@ -29,6 +36,9 @@ public class ContactListActivity extends BaseMvvmActivity mContactInfos;
+ private EditDialogFragment mEditDialogFragment;
+ private ContactDeleteDialogFragment mContactDeleteDialogFragment;
+ ;
@Override
public boolean setNightMode() {
@@ -55,6 +65,13 @@ public class ContactListActivity extends BaseMvvmActivity>() {
@Override
public void onChanged(List contactInfos) {
- Logger.e(TAG, "mContactListData: " + contactInfos);
- mContactInfos = contactInfos;
- mContactInfoAdapter.setContactInfos(mContactInfos);
+ mViewDataBinding.swipeRefreshLayout.setRefreshing(false);
+ if (contactInfos == null) {
+
+ } else {
+ Logger.e(TAG, "mContactListData: " + contactInfos);
+ mContactInfos = contactInfos;
+ mContactInfoAdapter.setContactInfos(mContactInfos);
+ }
+ }
+ });
+ mViewModel.mDeleteLiveData.observe(this, new Observer() {
+ @Override
+ public void onChanged(Boolean aBoolean) {
+ if (aBoolean) {
+ TipDialog.show("删除成功", WaitDialog.TYPE.SUCCESS);
+ mViewModel.getAllContacts();
+ } else {
+ TipDialog.show("删除失败", WaitDialog.TYPE.ERROR);
+ }
}
});
}
@@ -139,9 +210,13 @@ public class ContactListActivity extends BaseMvvmActivity {
private static final String TAG = "ContactListViewModel";
private ContactRepository mRepository;
+ private ContactManager mContactManager;
@Override
public void setContext(Context context) {
super.setContext(context);
mRepository = new ContactRepository(context);
+ mContactManager = ContactManager.getInstance(context);
}
public MutableLiveData> mContactListData = new MutableLiveData<>();
public void getAllContacts() {
- Observable.create(new ObservableOnSubscribe>() {
+ mContactManager.getContacts(getLifecycle(), new ContactManager.ContactCallback() {
@Override
- public void subscribe(@NonNull ObservableEmitter> emitter) throws Throwable {
- List contactInfos = mRepository.getAllContacts();
- emitter.onNext(contactInfos);
- emitter.onComplete();
+ public void onSuccess(List contacts) {
+ Logger.e(TAG, "获取联系人成功: " + contacts.size());
+ mContactListData.setValue(contacts);
}
- }).compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
+
+ @Override
+ public void onFailure(Throwable e) {
+ Logger.e(TAG, "获取联系人失败: " + e.getMessage());
+ mContactListData.setValue(null);
+ }
+ });
+
+// OkHttpManager.getInstance().getContactListObservable(getLifecycle())
+// .compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
+// .subscribe(new BaseObserver>>() {
+// @Override
+// public void onSuccess(BaseResponse> listBaseResponse) {
+// Log.e("getAllContacts", "onSuccess: " + listBaseResponse);
+// if (listBaseResponse.isSuccess()) {
+// List contactInfos = listBaseResponse.getData();
+// List sorted = contactInfos.stream().sorted(new Comparator() {
+// @Override
+// public int compare(ContactInfo t0, ContactInfo t1) {
+// return Integer.compare(t0.getPosition(), t1.getPosition());
+// }
+// }).collect(Collectors.toList());
+// mContactListData.setValue(sorted);
+// }
+// }
+//
+// @Override
+// public void onFailure(Throwable e) {
+// Log.e("getAllContacts", "onFailure: " + e.getMessage());
+// mContactListData.setValue(null);
+// }
+// });
+ }
+
+ private void getAllContactsFromDB() {
+ Observable.create(new ObservableOnSubscribe>() {
+ @Override
+ public void subscribe(@NonNull ObservableEmitter> emitter) throws Throwable {
+ List contactInfos = mRepository.getAllContacts();
+ emitter.onNext(contactInfos);
+ emitter.onComplete();
+ }
+ }).compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer>() {
@@ -55,7 +102,7 @@ public class ContactListViewModel extends BaseViewModel contactInfos) {
Logger.e("getAllContacts", "onNext: ");
- mContactListData.setValue(contactInfos);
+// mContactListData.setValue(contactInfos);
// List sorted = contacts.stream().sorted(new Comparator() {
// @Override
@@ -81,13 +128,13 @@ public class ContactListViewModel extends BaseViewModel() {
- @Override
- public void subscribe(@NonNull ObservableEmitter emitter) throws Throwable {
- int id = mRepository.update(contactInfo);
- emitter.onNext(id);
- emitter.onComplete();
- }
- }).compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
+ @Override
+ public void subscribe(@NonNull ObservableEmitter emitter) throws Throwable {
+ int id = mRepository.update(contactInfo);
+ emitter.onNext(id);
+ emitter.onComplete();
+ }
+ }).compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer() {
@@ -113,4 +160,22 @@ public class ContactListViewModel extends BaseViewModel mDeleteLiveData = new MutableLiveData<>();
+
+ public void deleteContact(long id) {
+ OkHttpManager.getInstance().getContactDeleteObservable(id, getLifecycle())
+ .safeSubscribe(new BaseObserver() {
+ @Override
+ public void onSuccess(BaseResponse baseResponse) {
+ Logger.e("deleteContact", "onSuccess: " + baseResponse);
+ mDeleteLiveData.setValue(baseResponse.isSuccess());
+ }
+
+ @Override
+ public void onFailure(Throwable e) {
+ Logger.e("deleteContact", "onFailure: " + e.getMessage());
+ mDeleteLiveData.setValue(false);
+ }
+ });
+ }
}
diff --git a/app/src/main/java/com/ttstd/dialer/activity/contact/test/ContactTestActivity.java b/app/src/main/java/com/ttstd/dialer/activity/contact/test/ContactTestActivity.java
new file mode 100644
index 0000000..cea1a39
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/activity/contact/test/ContactTestActivity.java
@@ -0,0 +1,245 @@
+package com.ttstd.dialer.activity.contact.test;
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.Observer;
+import androidx.recyclerview.widget.ItemTouchHelper;
+import androidx.recyclerview.widget.LinearLayoutManager;
+
+import com.ttstd.dialer.R;
+import com.ttstd.dialer.activity.contact.add.ContactAddActivity;
+import com.ttstd.dialer.adapter.ContactInfoAdapter;
+import com.ttstd.dialer.base.mvvm.BaseMvvmActivity;
+import com.ttstd.dialer.databinding.ActivityContactTestBinding;
+import com.ttstd.dialer.db.contact.ContactInfo;
+import com.ttstd.dialer.fragment.dialog.contact.call.CallFragment;
+import com.ttstd.dialer.manager.ContactManager;
+import com.ttstd.dialer.manager.ContactSyncManager;
+import com.ttstd.dialer.utils.Logger;
+import com.ttstd.dialer.view.ItemTouchHelperCallback;
+
+import java.util.List;
+
+public class ContactTestActivity extends BaseMvvmActivity {
+
+ private static final String TAG = "ContactTestActivity";
+ private static final int REQUEST_CODE_CALL = 7897;
+
+ private ContactInfoAdapter mOnlineContactInfoAdapter;
+ private ContactInfoAdapter mLocalContactInfoAdapter;
+
+ private List mOnlineContactInfos;
+ private List mLocalContactInfos;
+
+ @Override
+ public boolean setNightMode() {
+ return true;
+ }
+
+ @Override
+ public boolean setfitWindow() {
+ return true;
+ }
+
+ @Override
+ protected int getLayoutId() {
+ return R.layout.activity_contact_test;
+ }
+
+ @Override
+ protected void initDataBinding() {
+ mViewModel.setContext(this);
+ mViewModel.setVDBinding(mViewDataBinding);
+ mViewModel.setLifecycle(getLifecycleSubject());
+ mViewDataBinding.setClick(new BtnClick());
+ }
+
+ @Override
+ protected void initView() {
+ mOnlineContactInfoAdapter = new ContactInfoAdapter();
+ mOnlineContactInfoAdapter.setItemMoveCallback(new ContactInfoAdapter.ItemMoveCallback() {
+ @Override
+ public void onItemMove(int fromPosition, int toPosition) {
+ Logger.e(TAG, "mOnlineContactInfoAdapter onItemMove: ");
+ ContactInfo fromContactInfo = mOnlineContactInfos.get(fromPosition);
+ int fromContactPosition = fromContactInfo.getPosition();
+ ContactInfo toContactInfo = mOnlineContactInfos.get(toPosition);
+ int toContactPosition = toContactInfo.getPosition();
+ fromContactInfo.setPosition(toContactPosition);
+ mViewModel.updateItemPosition(fromContactInfo);
+ toContactInfo.setPosition(fromContactPosition);
+ mViewModel.updateItemPosition(toContactInfo);
+ }
+
+ @Override
+ public void onItemRemoved(int position) {
+ Logger.e(TAG, "mOnlineContactInfoAdapter onItemRemoved: ");
+
+ }
+ });
+ mOnlineContactInfoAdapter.setOnClickListener(new ContactInfoAdapter.ClickListener() {
+ @Override
+ public void onClick(ContactInfo contactInfo) {
+ new CallFragment(contactInfo).show(getSupportFragmentManager(), "CallFragment");
+ }
+
+ @Override
+ public void onLongClick(ContactInfo contactInfo) {
+
+ }
+
+ @Override
+ public void onMoreOperationClick(ContactInfo contactInfo) {
+
+ }
+ });
+ // 设置ItemTouchHelper,实现拖动和滑动删除
+ ItemTouchHelper.Callback callback = new ItemTouchHelperCallback(mOnlineContactInfoAdapter);
+ ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
+ touchHelper.attachToRecyclerView(mViewDataBinding.rvOnline);
+
+ LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
+ linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
+ mViewDataBinding.rvOnline.setLayoutManager(linearLayoutManager);
+ mViewDataBinding.rvOnline.setAdapter(mOnlineContactInfoAdapter);
+
+ mLocalContactInfoAdapter = new ContactInfoAdapter();
+ mLocalContactInfoAdapter.setItemMoveCallback(new ContactInfoAdapter.ItemMoveCallback() {
+ @Override
+ public void onItemMove(int fromPosition, int toPosition) {
+ Logger.e(TAG, "mLocalContactInfoAdapter onItemMove: ");
+ ContactInfo fromContactInfo = mOnlineContactInfos.get(fromPosition);
+ int fromContactPosition = fromContactInfo.getPosition();
+ ContactInfo toContactInfo = mOnlineContactInfos.get(toPosition);
+ int toContactPosition = toContactInfo.getPosition();
+ fromContactInfo.setPosition(toContactPosition);
+ mViewModel.updateItemPosition(fromContactInfo);
+ toContactInfo.setPosition(fromContactPosition);
+ mViewModel.updateItemPosition(toContactInfo);
+ }
+
+ @Override
+ public void onItemRemoved(int position) {
+ Logger.e(TAG, "mLocalContactInfoAdapter onItemRemoved: ");
+
+ }
+ });
+ mLocalContactInfoAdapter.setOnClickListener(new ContactInfoAdapter.ClickListener() {
+ @Override
+ public void onClick(ContactInfo contactInfo) {
+ new CallFragment(contactInfo).show(getSupportFragmentManager(), "CallFragment");
+ }
+
+ @Override
+ public void onLongClick(ContactInfo contactInfo) {
+
+ }
+
+ @Override
+ public void onMoreOperationClick(ContactInfo contactInfo) {
+
+ }
+ });
+ // 设置ItemTouchHelper,实现拖动和滑动删除
+ ItemTouchHelper.Callback callbackLocal = new ItemTouchHelperCallback(mLocalContactInfoAdapter);
+ ItemTouchHelper touchHelperLocal = new ItemTouchHelper(callbackLocal);
+ touchHelperLocal.attachToRecyclerView(mViewDataBinding.rvLocal);
+
+ LinearLayoutManager linearLayoutManagerLocal = new LinearLayoutManager(this);
+ linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
+ mViewDataBinding.rvLocal.setLayoutManager(linearLayoutManagerLocal);
+ mViewDataBinding.rvLocal.setAdapter(mLocalContactInfoAdapter);
+ }
+
+ @Override
+ protected void initData() {
+ mViewModel.mOnlineContactListData.observe(this, new Observer>() {
+ @Override
+ public void onChanged(List contactInfos) {
+ if (contactInfos == null) {
+
+ } else {
+ mViewDataBinding.tvOnline.setText("在线联系人:" + contactInfos.size() + "个");
+ Logger.e(TAG, "mOnlineContactListData: " + contactInfos);
+ mOnlineContactInfos = contactInfos;
+ mOnlineContactInfoAdapter.setContactInfos(mOnlineContactInfos);
+ }
+ }
+ });
+
+ mViewModel.mLocalContactListData.observe(this, new Observer>() {
+ @Override
+ public void onChanged(List contactInfos) {
+ if (contactInfos == null) {
+
+ } else {
+ mViewDataBinding.tvLocal.setText("本地联系人:" + contactInfos.size() + "个");
+ Logger.e(TAG, "mLocalContactListData: " + contactInfos);
+ mLocalContactInfos = contactInfos;
+ mLocalContactInfoAdapter.setContactInfos(mLocalContactInfos);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+
+ if (requestCode == REQUEST_CODE_CALL) {
+ // 检查所有权限的结果是否都已授予
+ boolean allGranted = true;
+ for (int result : grantResults) {
+ if (result != PackageManager.PERMISSION_GRANTED) {
+ allGranted = false;
+ break;
+ }
+ }
+
+// if (allGranted) {
+// onAllPermissionsGranted(); // 权限全部获取成功
+// } else {
+// // 处理权限被拒绝的情况
+// handlePermissionDenied();
+// }
+ }
+ }
+
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mViewModel.getAllContacts();
+ mViewModel.getAllContactsFromDB();
+ }
+
+ public class BtnClick {
+ public void exit(View view) {
+ finish();
+ }
+
+ public void getOnlineContact(View view) {
+ mViewModel.getAllContacts();
+ }
+
+ public void getLocalContact(View view) {
+ mViewModel.getAllContactsFromDB();
+ }
+
+ public void compareContact(View view) {
+ ContactManager.getInstance(ContactTestActivity.this).compareContacts(mOnlineContactInfos, mLocalContactInfos);
+ }
+
+ public void syncContact(View view) {
+ ContactSyncManager.getInstance(ContactTestActivity.this).syncPendingContacts();
+ }
+
+
+ public void addContact(View view) {
+ startActivity(new Intent(ContactTestActivity.this, ContactAddActivity.class));
+ }
+ }
+}
diff --git a/app/src/main/java/com/ttstd/dialer/activity/contact/test/ContactTestViewModel.java b/app/src/main/java/com/ttstd/dialer/activity/contact/test/ContactTestViewModel.java
new file mode 100644
index 0000000..62e43f0
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/activity/contact/test/ContactTestViewModel.java
@@ -0,0 +1,151 @@
+package com.ttstd.dialer.activity.contact.test;
+
+import android.content.Context;
+import android.util.Log;
+
+import androidx.lifecycle.MutableLiveData;
+
+import com.trello.rxlifecycle4.RxLifecycle;
+import com.trello.rxlifecycle4.android.ActivityEvent;
+import com.ttstd.dialer.base.mvvm.BaseViewModel;
+import com.ttstd.dialer.bean.BaseResponse;
+import com.ttstd.dialer.databinding.ActivityContactTestBinding;
+import com.ttstd.dialer.db.contact.ContactInfo;
+import com.ttstd.dialer.db.contact.ContactRepository;
+import com.ttstd.dialer.network.BaseObserver;
+import com.ttstd.dialer.network.OkHttpManager;
+import com.ttstd.dialer.utils.Logger;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
+import io.reactivex.rxjava3.annotations.NonNull;
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.core.ObservableEmitter;
+import io.reactivex.rxjava3.core.ObservableOnSubscribe;
+import io.reactivex.rxjava3.core.Observer;
+import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.schedulers.Schedulers;
+
+public class ContactTestViewModel extends BaseViewModel {
+ private static final String TAG = "ContactListViewModel";
+ private ContactRepository mRepository;
+
+ @Override
+ public void setContext(Context context) {
+ super.setContext(context);
+ mRepository = new ContactRepository(context);
+ }
+
+ public MutableLiveData> mOnlineContactListData = new MutableLiveData<>();
+
+ public void getAllContacts() {
+ OkHttpManager.getInstance().getContactListObservable(getLifecycle())
+ .compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
+ .subscribe(new BaseObserver>>() {
+ @Override
+ public void onSuccess(BaseResponse> listBaseResponse) {
+ Log.e("getAllContacts", "onSuccess: " + listBaseResponse);
+ if (listBaseResponse.isSuccess()) {
+ List contactInfos = listBaseResponse.getData();
+ List sorted = contactInfos.stream().sorted(new Comparator() {
+ @Override
+ public int compare(ContactInfo t0, ContactInfo t1) {
+ return Integer.compare(t0.getPosition(), t1.getPosition());
+ }
+ }).collect(Collectors.toList());
+ mOnlineContactListData.setValue(sorted);
+ }
+ }
+
+ @Override
+ public void onFailure(Throwable e) {
+ Log.e("getAllContacts", "onFailure: " + e.getMessage());
+ mOnlineContactListData.setValue(null);
+ }
+ });
+ }
+
+ public MutableLiveData> mLocalContactListData = new MutableLiveData<>();
+
+ public void getAllContactsFromDB() {
+ Observable.create(new ObservableOnSubscribe>() {
+ @Override
+ public void subscribe(@NonNull ObservableEmitter> emitter) throws Throwable {
+ List contactInfos = mRepository.getAllContacts();
+ emitter.onNext(contactInfos);
+ emitter.onComplete();
+ }
+ }).compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new Observer>() {
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {
+ Logger.e("getAllContacts", "onSubscribe: ");
+ }
+
+ @Override
+ public void onNext(@NonNull List contactInfos) {
+ Logger.e("getAllContacts", "onNext: ");
+// mLocalContactListData.setValue(contactInfos);
+
+ List sorted = contactInfos.stream().sorted(new Comparator() {
+ @Override
+ public int compare(ContactInfo o1, ContactInfo o2) {
+ return Integer.compare(o1.getPosition(), o2.getPosition());
+ }
+ }).collect(Collectors.toList());
+ mLocalContactListData.setValue(sorted);
+ }
+
+ @Override
+ public void onError(@NonNull Throwable e) {
+ Logger.e("getAllContacts", "onError: " + e.getMessage());
+ }
+
+ @Override
+ public void onComplete() {
+ Logger.e("getAllContacts", "onComplete: ");
+ }
+ });
+ }
+
+ public void updateItemPosition(ContactInfo contactInfo) {
+ Logger.e(TAG, "updateItemPosition: " + contactInfo);
+ Observable.create(new ObservableOnSubscribe() {
+ @Override
+ public void subscribe(@NonNull ObservableEmitter emitter) throws Throwable {
+ int id = mRepository.update(contactInfo);
+ emitter.onNext(id);
+ emitter.onComplete();
+ }
+ }).compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new Observer() {
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {
+ Logger.e("updateItemPosition", "onComplete: ");
+ }
+
+ @Override
+ public void onNext(@NonNull Integer integer) {
+ Logger.e("updateItemPosition", "onNext: " + integer);
+ }
+
+ @Override
+ public void onError(@NonNull Throwable e) {
+ Logger.e("updateItemPosition", "onError: " + e.getMessage());
+ }
+
+ @Override
+ public void onComplete() {
+ Logger.e("updateItemPosition", "onComplete: ");
+ }
+ });
+ }
+
+}
diff --git a/app/src/main/java/com/ttstd/dialer/activity/main/MainActivity.java b/app/src/main/java/com/ttstd/dialer/activity/main/MainActivity.java
index a1465f4..0c1bb38 100644
--- a/app/src/main/java/com/ttstd/dialer/activity/main/MainActivity.java
+++ b/app/src/main/java/com/ttstd/dialer/activity/main/MainActivity.java
@@ -4,6 +4,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.util.Log;
import android.view.KeyEvent;
import android.view.Window;
import android.view.WindowManager;
@@ -35,14 +36,12 @@ import com.ttstd.dialer.wallpager.WallpaperScrollHelper;
import net.lucode.hackware.magicindicator.ViewPagerHelper;
-import java.util.concurrent.CancellationException;
import java.util.ArrayList;
import java.util.List;
import kotlinx.coroutines.CoroutineScope;
import kotlinx.coroutines.CoroutineScopeKt;
import kotlinx.coroutines.Job;
-import kotlinx.coroutines.flow.FlowKt;
public class MainActivity extends BaseMvvmActivity {
private static final String TAG = "MainActivity";
@@ -94,14 +93,14 @@ public class MainActivity extends BaseMvvmActivity {
- if (weatherNowResponse != null) {
- Logger.e(TAG, "weatherNowResponse = " + weatherNowResponse);
- }
- return null;
- });
- }
+// private void observeWeatherUpdates() {
+// // 观察当前天气更新
+// weatherNowJob = weatherUpdateManager.observeWeatherNow(weatherScope, weatherNowResponse -> {
+// if (weatherNowResponse != null) {
+// Logger.e(TAG, "observeWeatherUpdates", "weatherNowResponse = " + weatherNowResponse);
+// }
+// return null;
+// });
+//
+// weatherHourlyJob = weatherUpdateManager.observeWeatherHourly(weatherScope, weatherHourlyResponse -> {
+// if (weatherHourlyResponse != null) {
+// Logger.e(TAG, "observeWeatherUpdates", "weatherHourlyResponse = " + weatherHourlyResponse);
+// }
+// return null;
+// });
+//
+// weatherDailyJob = weatherUpdateManager.observeWeatherDaily(weatherScope, weatherDailyResponse -> {
+// if (weatherDailyResponse != null) {
+// Logger.e(TAG, "observeWeatherUpdates", "weatherDailyResponse = " + weatherDailyResponse);
+// }
+// return null;
+// });
+// }
private void showSystemWallpaperBehindActivity() {
Window window = getWindow();
-
// 关键:让系统壁纸显示在当前窗口后面
window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
-
// 窗口背景透明
window.setBackgroundDrawableResource(android.R.color.transparent);
}
@@ -357,7 +378,7 @@ public class MainActivity extends BaseMvvmActivity params = new HashMap<>();
- params.put("sn", SystemUtils.getSerial());
+ params.put("sn", DeviceManagerService.getInstance().getSerial());
params.put("createtime", String.valueOf(createTime));
// Call call = NetInterfaceManager.getInstance().getScreenshotCall().sendScreenshot(params, body);
// call.enqueue(new RetryCallback(call, 10, 30 * 1000) {
diff --git a/app/src/main/java/com/ttstd/dialer/activity/weather/main/WeatherMainActivity.java b/app/src/main/java/com/ttstd/dialer/activity/weather/main/WeatherMainActivity.java
index d9e9497..ebd025d 100644
--- a/app/src/main/java/com/ttstd/dialer/activity/weather/main/WeatherMainActivity.java
+++ b/app/src/main/java/com/ttstd/dialer/activity/weather/main/WeatherMainActivity.java
@@ -15,6 +15,7 @@ import com.ttstd.dialer.base.mvvm.BaseMvvmActivity;
import com.ttstd.dialer.bean.req.SnLocationReq;
import com.ttstd.dialer.config.CommonConfig;
import com.ttstd.dialer.databinding.ActivityWeatherMainBinding;
+import com.ttstd.dialer.manager.MapManager;
import com.ttstd.dialer.utils.DateUtil;
import com.ttstd.dialer.utils.Logger;
@@ -84,6 +85,7 @@ public class WeatherMainActivity extends BaseMvvmActivity>() {
@Override
public void onChanged(List weatherDailies) {
@@ -103,10 +105,12 @@ public class WeatherMainActivity extends BaseMvvmActivity() {
@Override
@@ -117,7 +121,6 @@ public class WeatherMainActivity extends BaseMvvmActivity() {
+ WeatherManager.getInstance().getWeatherNowAdCode(adCode, new Callback() {
@Override
public void onSuccess(WeatherNowResponse response) {
Logger.e("getWeatherNow", "onSuccess: " + response);
@@ -52,7 +52,7 @@ public class WeatherMainViewModel extends BaseViewModel() {
+ WeatherManager.getInstance().getWeather24HourAdCode(adCode, new Callback() {
@Override
public void onSuccess(WeatherHourlyResponse weatherHourlyResponse) {
Logger.e("getWeather24h", "onSuccess: " + weatherHourlyResponse);
@@ -79,7 +79,7 @@ public class WeatherMainViewModel extends BaseViewModel() {
+ WeatherManager.getInstance().getWeather10Day(adCode, new Callback() {
@Override
public void onSuccess(WeatherDailyResponse weatherDailyResponse) {
Logger.e("getWeather10D", "onSuccess: ");
diff --git a/app/src/main/java/com/ttstd/dialer/adapter/ContactInfoAdapter.java b/app/src/main/java/com/ttstd/dialer/adapter/ContactInfoAdapter.java
index 33b2de4..e72f942 100644
--- a/app/src/main/java/com/ttstd/dialer/adapter/ContactInfoAdapter.java
+++ b/app/src/main/java/com/ttstd/dialer/adapter/ContactInfoAdapter.java
@@ -13,6 +13,8 @@ import androidx.recyclerview.widget.RecyclerView;
import com.shehuan.niv.NiceImageView;
import com.ttstd.dialer.R;
import com.ttstd.dialer.db.contact.ContactInfo;
+import com.ttstd.dialer.utils.AvatarCacheUtil;
+import com.ttstd.dialer.utils.GlideUtils;
import com.ttstd.dialer.utils.Logger;
import java.util.Collections;
@@ -42,14 +44,18 @@ public class ContactInfoAdapter extends RecyclerView.Adapter extends BaseDialogFragment {
protected String mTag = this.getClass().getSimpleName();
/**
@@ -43,11 +39,8 @@ public abstract class BaseMvvmDialogFragment vmClass;
- //
-// protected Toolbar toolbar;
-// protected View statusBarView;
- //
- protected Bundle bundle;//来自getArguments()
+
+ protected Bundle bundle;
protected Bundle savedInstanceState;
// protected Context context;
@@ -64,7 +57,6 @@ public abstract class BaseMvvmDialogFragment(context);
}
@@ -82,14 +74,16 @@ public abstract class BaseMvvmDialogFragment) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
- mViewModel = new ViewModelProvider(this).get(vmClass);
- //
+ try {
+ vmClass = (Class) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
+ mViewModel = new ViewModelProvider(this).get(vmClass);
+ } catch (Exception e) {
+ Logger.e(mTag, "Failed to create ViewModel: " + e.getMessage());
+ throw new RuntimeException("Failed to create ViewModel", e);
+ }
+
return mViewDataBinding.getRoot();
}
@@ -98,33 +92,16 @@ public abstract class BaseMvvmDialogFragment {
-// L.e(" >> LiveDataBus >> DATA_BUS_LOADING_FRAGMENT: %s", bool);
-// if(bool) {
-// showLoading(R.string.str_please_wait);
-// } else {
-// hideLoading();
-// }
-// });
}
@Override
@@ -132,10 +109,7 @@ public abstract class BaseMvvmDialogFragment> hideLoading :: isShow: %s", isShow);
-// if (isShow)
-// mWaitDiaLogger.dismiss();
-// } catch (Exception e) {
-// e.printStackTrace();
-// }
-// }
-
- /**
- * 進入界面
- */
protected void onEnter() {
}
- /**
- * 離開界面
- */
protected void onExit() {
}
diff --git a/app/src/main/java/com/ttstd/dialer/base/mvvm/fragment/BaseMvvmFragment.java b/app/src/main/java/com/ttstd/dialer/base/mvvm/fragment/BaseMvvmFragment.java
index 29fe070..6ac925a 100644
--- a/app/src/main/java/com/ttstd/dialer/base/mvvm/fragment/BaseMvvmFragment.java
+++ b/app/src/main/java/com/ttstd/dialer/base/mvvm/fragment/BaseMvvmFragment.java
@@ -84,7 +84,6 @@ public abstract class BaseMvvmFragment) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
@@ -97,6 +96,7 @@ public abstract class BaseMvvmFragment searchContacts(String searchQuery);
+
+ @Query("SELECT * FROM contacts WHERE sync_status = :status ORDER BY create_time ASC")
+ List getContactsBySyncStatus(int status);
+
+ @Query("SELECT * FROM contacts WHERE sync_status != 1 ORDER BY create_time ASC")
+ List getUnsyncedContacts();
+
+ @Query("UPDATE contacts SET sync_status = :status, local_id = :localId, update_time = :updateTime WHERE id = :id")
+ int updateSyncStatus(long id, int status, Long localId, long updateTime);
+
+ @Query("UPDATE contacts SET sync_status = :status WHERE id = :id")
+ int updateSyncStatusById(long id, int status);
}
diff --git a/app/src/main/java/com/ttstd/dialer/db/contact/ContactInfo.java b/app/src/main/java/com/ttstd/dialer/db/contact/ContactInfo.java
index 8771e56..cc78ca8 100644
--- a/app/src/main/java/com/ttstd/dialer/db/contact/ContactInfo.java
+++ b/app/src/main/java/com/ttstd/dialer/db/contact/ContactInfo.java
@@ -2,11 +2,14 @@ package com.ttstd.dialer.db.contact;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.room.ColumnInfo;
import androidx.room.Entity;
+import androidx.room.Ignore;
import androidx.room.PrimaryKey;
import com.google.gson.Gson;
import com.google.gson.JsonParser;
+import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
import java.util.Objects;
@@ -14,25 +17,81 @@ import java.util.Objects;
@Entity(tableName = "contacts")
public class ContactInfo implements Serializable {
+ private static final long serialVersionUID = -4899154548784231165L;
+
@PrimaryKey(autoGenerate = true)
- private int id;
+ private long id;
+
private String name;
+
+ @SerializedName(value = "nickName", alternate = {"nick_name"})
+ private String nickName;
+
+ @SerializedName(value = "phoneNumber", alternate = {"phone_number"})
private String phoneNumber;
+
private String avatar;
- private String wxid;
+
private int position;
+ private boolean emergency;
+
+ @SerializedName(value = "showDesktop", alternate = {"show_desktop"})
+ private boolean showDesktop;
+
+ @SerializedName(value = "bindPhone", alternate = {"bind_phone"})
+ private String bindPhone;
+
+ @SerializedName(value = "bindSn", alternate = {"bind_sn"})
+ private String bindSn;
+
+ @SerializedName(value = "updateTime", alternate = {"update_time"})
+ @ColumnInfo(name = "update_time")
+ private long updateTime;
+
+ @SerializedName(value = "createTime", alternate = {"create_time"})
+ @ColumnInfo(name = "create_time")
+ private long createTime;
+
+ @ColumnInfo(name = "local_id")
+ private Long localId;
+
+ @ColumnInfo(name = "sync_status")
+ private int syncStatus;
+
+ public ContactInfo() {
+
+ }
+
+ // 本地添加
+ @Ignore
public ContactInfo(String name, String phoneNumber, int position) {
this.name = name;
this.phoneNumber = phoneNumber;
this.position = position;
}
- public int getId() {
+ @Ignore
+ public ContactInfo(ContactInfo contactInfo) {
+ this.id = contactInfo.getId();
+ this.name = contactInfo.name;
+ this.nickName = contactInfo.nickName;
+ this.phoneNumber = contactInfo.phoneNumber;
+ this.avatar = contactInfo.avatar;
+ this.position = contactInfo.position;
+ this.emergency = contactInfo.emergency;
+ this.showDesktop = contactInfo.showDesktop;
+ this.bindPhone = contactInfo.bindPhone;
+ this.bindSn = contactInfo.bindSn;
+ this.updateTime = contactInfo.updateTime;
+ this.createTime = contactInfo.createTime;
+ }
+
+ public long getId() {
return id;
}
- public void setId(int id) {
+ public void setId(long id) {
this.id = id;
}
@@ -44,6 +103,14 @@ public class ContactInfo implements Serializable {
this.name = name;
}
+ public String getNickName() {
+ return nickName;
+ }
+
+ public void setNickName(String nickName) {
+ this.nickName = nickName;
+ }
+
public String getPhoneNumber() {
return phoneNumber;
}
@@ -60,14 +127,6 @@ public class ContactInfo implements Serializable {
this.avatar = avatar;
}
- public String getWxid() {
- return wxid;
- }
-
- public void setWxid(String wxid) {
- this.wxid = wxid;
- }
-
public int getPosition() {
return position;
}
@@ -76,6 +135,70 @@ public class ContactInfo implements Serializable {
this.position = position;
}
+ public boolean isEmergency() {
+ return emergency;
+ }
+
+ public void setEmergency(boolean emergency) {
+ this.emergency = emergency;
+ }
+
+ public boolean isShowDesktop() {
+ return showDesktop;
+ }
+
+ public void setShowDesktop(boolean showDesktop) {
+ this.showDesktop = showDesktop;
+ }
+
+ public String getBindPhone() {
+ return bindPhone;
+ }
+
+ public void setBindPhone(String bindPhone) {
+ this.bindPhone = bindPhone;
+ }
+
+ public String getBindSn() {
+ return bindSn;
+ }
+
+ public void setBindSn(String bindSn) {
+ this.bindSn = bindSn;
+ }
+
+ public long getUpdateTime() {
+ return updateTime;
+ }
+
+ public void setUpdateTime(long updateTime) {
+ this.updateTime = updateTime;
+ }
+
+ public long getCreateTime() {
+ return createTime;
+ }
+
+ public void setCreateTime(long createTime) {
+ this.createTime = createTime;
+ }
+
+ public Long getLocalId() {
+ return localId;
+ }
+
+ public void setLocalId(Long localId) {
+ this.localId = localId;
+ }
+
+ public int getSyncStatus() {
+ return syncStatus;
+ }
+
+ public void setSyncStatus(int syncStatus) {
+ this.syncStatus = syncStatus;
+ }
+
@Override
public boolean equals(@Nullable Object obj) {
if (obj instanceof ContactInfo) {
diff --git a/app/src/main/java/com/ttstd/dialer/db/contact/ContactRepository.java b/app/src/main/java/com/ttstd/dialer/db/contact/ContactRepository.java
index 69ded99..877196a 100644
--- a/app/src/main/java/com/ttstd/dialer/db/contact/ContactRepository.java
+++ b/app/src/main/java/com/ttstd/dialer/db/contact/ContactRepository.java
@@ -60,4 +60,20 @@ public class ContactRepository {
public int deleteAll() {
return mContactDao.deleteAll();
}
+
+ public List getContactsBySyncStatus(int status) {
+ return mContactDao.getContactsBySyncStatus(status);
+ }
+
+ public List getUnsyncedContacts() {
+ return mContactDao.getUnsyncedContacts();
+ }
+
+ public int updateSyncStatus(long id, int status, Long localId, long updateTime) {
+ return mContactDao.updateSyncStatus(id, status, localId, updateTime);
+ }
+
+ public int updateSyncStatusById(long id, int status) {
+ return mContactDao.updateSyncStatusById(id, status);
+ }
}
diff --git a/app/src/main/java/com/ttstd/dialer/db/contact/ContactViewModel.java b/app/src/main/java/com/ttstd/dialer/db/contact/ContactViewModel.java
index 12af605..70bb4cb 100644
--- a/app/src/main/java/com/ttstd/dialer/db/contact/ContactViewModel.java
+++ b/app/src/main/java/com/ttstd/dialer/db/contact/ContactViewModel.java
@@ -6,6 +6,7 @@ import androidx.lifecycle.AndroidViewModel;
import java.util.List;
+@Deprecated
public class ContactViewModel extends AndroidViewModel {
private ContactRepository mRepository;
diff --git a/app/src/main/java/com/ttstd/dialer/db/contact/SyncStatus.java b/app/src/main/java/com/ttstd/dialer/db/contact/SyncStatus.java
new file mode 100644
index 0000000..7ef2f2a
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/db/contact/SyncStatus.java
@@ -0,0 +1,21 @@
+package com.ttstd.dialer.db.contact;
+
+
+public interface SyncStatus {
+ int PENDING = 0;
+ int SYNCED = 1;
+ int FAILED = 2;
+
+ static String getStatusName(int status) {
+ switch (status) {
+ case PENDING:
+ return "待同步";
+ case SYNCED:
+ return "已同步";
+ case FAILED:
+ return "同步失败";
+ default:
+ return "未知状态";
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/ttstd/dialer/filter/NoSpaceInputFilter.java b/app/src/main/java/com/ttstd/dialer/filter/NoSpaceInputFilter.java
index b01edc5..fc98fae 100644
--- a/app/src/main/java/com/ttstd/dialer/filter/NoSpaceInputFilter.java
+++ b/app/src/main/java/com/ttstd/dialer/filter/NoSpaceInputFilter.java
@@ -4,18 +4,22 @@ import android.text.InputFilter;
import android.text.Spanned;
/**
- * 过滤输入中的空格(包括普通空格和全角空格)
+ * 过滤输入中的空格、换行等特殊字符
*/
public class NoSpaceInputFilter implements InputFilter {
@Override
public CharSequence filter(CharSequence source, int start, int end,
Spanned dest, int dstart, int dend) {
- // 遍历输入的字符序列,过滤掉所有空格
+ // 遍历输入的字符序列,过滤掉所有特殊字符
StringBuilder filtered = new StringBuilder();
for (int i = start; i < end; i++) {
char c = source.charAt(i);
- // 过滤普通空格(ASCII 32)和全角空格(Unicode 12288)
- if (c != ' ' && c != '\u3000') {
+ // 过滤以下字符:
+ // 1. 普通空格(ASCII 32)
+ // 2. 全角空格(Unicode 12288)
+ // 3. 换行符(\n, \r)
+ // 4. 制表符(\t)
+ if (!Character.isWhitespace(c)) {
filtered.append(c);
}
}
diff --git a/app/src/main/java/com/ttstd/dialer/fragment/app/AppFragment.java b/app/src/main/java/com/ttstd/dialer/fragment/app/AppFragment.java
index 75e61c8..aa88802 100644
--- a/app/src/main/java/com/ttstd/dialer/fragment/app/AppFragment.java
+++ b/app/src/main/java/com/ttstd/dialer/fragment/app/AppFragment.java
@@ -98,7 +98,7 @@ public class AppFragment extends BaseMvvmFragment() {
+ mViewModel.mAppUpdateData.observe(getViewLifecycleOwner(), new Observer() {
@Override
public void onChanged(Integer integer) {
if (integer > 0) {
diff --git a/app/src/main/java/com/ttstd/dialer/fragment/contact/ContactFragment.java b/app/src/main/java/com/ttstd/dialer/fragment/contact/ContactFragment.java
index 1d9d290..2016e74 100644
--- a/app/src/main/java/com/ttstd/dialer/fragment/contact/ContactFragment.java
+++ b/app/src/main/java/com/ttstd/dialer/fragment/contact/ContactFragment.java
@@ -11,7 +11,7 @@ import com.ttstd.dialer.adapter.HomeContactAdapter;
import com.ttstd.dialer.base.mvvm.fragment.BaseMvvmFragment;
import com.ttstd.dialer.databinding.FragmentContactBinding;
import com.ttstd.dialer.db.contact.ContactInfo;
-import com.ttstd.dialer.fragment.dialog.call.CallFragment;
+import com.ttstd.dialer.fragment.dialog.contact.call.CallFragment;
import com.ttstd.dialer.utils.Logger;
import com.ttstd.dialer.view.EqualSpaceDecoration;
@@ -60,7 +60,8 @@ public class ContactFragment extends BaseMvvmFragment>() {
+ mViewModel.preloadData();
+ mViewModel.mContactListData.observe(getViewLifecycleOwner(), new Observer>() {
@Override
public void onChanged(List contactInfos) {
Logger.e(TAG, "mContactListData: " + contactInfos);
diff --git a/app/src/main/java/com/ttstd/dialer/fragment/contact/ContactViewModel.java b/app/src/main/java/com/ttstd/dialer/fragment/contact/ContactViewModel.java
index 59bbe5d..7537909 100644
--- a/app/src/main/java/com/ttstd/dialer/fragment/contact/ContactViewModel.java
+++ b/app/src/main/java/com/ttstd/dialer/fragment/contact/ContactViewModel.java
@@ -13,18 +13,32 @@ import com.ttstd.dialer.db.contact.ContactRepository;
import com.ttstd.dialer.utils.Logger;
import java.util.List;
+import java.util.concurrent.Callable;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.annotations.NonNull;
import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.core.ObservableEmitter;
-import io.reactivex.rxjava3.core.ObservableOnSubscribe;
import io.reactivex.rxjava3.core.Observer;
import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.schedulers.Schedulers;
public class ContactViewModel extends BaseViewModel {
private ContactRepository mRepository;
+ private boolean isDataLoaded = false;
+
+ public void preloadData() {
+ if (isDataLoaded) return;
+
+ getAllContactsObservable()
+ .subscribe(new Consumer>() {
+ @Override
+ public void accept(List contactInfos) throws Throwable {
+ mContactListData.postValue(contactInfos);
+ isDataLoaded = true;
+ }
+ });
+ }
@Override
public void setContext(Context context) {
@@ -34,17 +48,20 @@ public class ContactViewModel extends BaseViewModel> mContactListData = new MutableLiveData<>();
- public void getAllContacts() {
- Observable.create(new ObservableOnSubscribe>() {
- @Override
- public void subscribe(@NonNull ObservableEmitter> emitter) throws Throwable {
- List contactInfos = mRepository.getAllContacts();
- emitter.onNext(contactInfos);
- emitter.onComplete();
- }
- }).compose(RxLifecycle.bindUntilEvent(getLifecycle(), FragmentEvent.DESTROY))
+ public Observable> getAllContactsObservable() {
+ return Observable.fromCallable(new Callable>() {
+ @Override
+ public List call() throws Exception {
+ List contactInfos = mRepository.getAllContacts();
+ return contactInfos;
+ }
+ }).compose(RxLifecycle.bindUntilEvent(getLifecycle(), FragmentEvent.STOP))
.subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
+ .observeOn(AndroidSchedulers.mainThread());
+ }
+
+ public void getAllContacts() {
+ getAllContactsObservable()
.subscribe(new Observer>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
diff --git a/app/src/main/java/com/ttstd/dialer/fragment/dialog/call/CallViewModel.java b/app/src/main/java/com/ttstd/dialer/fragment/dialog/call/CallViewModel.java
deleted file mode 100644
index fa7ccef..0000000
--- a/app/src/main/java/com/ttstd/dialer/fragment/dialog/call/CallViewModel.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.ttstd.dialer.fragment.dialog.call;
-
-import com.trello.rxlifecycle4.android.FragmentEvent;
-import com.ttstd.dialer.base.mvvm.BaseViewModel;
-import com.ttstd.dialer.databinding.FragmentCallBinding;
-
-public class CallViewModel extends BaseViewModel {
-
-
-}
diff --git a/app/src/main/java/com/ttstd/dialer/fragment/dialog/call/CallFragment.java b/app/src/main/java/com/ttstd/dialer/fragment/dialog/contact/call/CallFragment.java
similarity index 96%
rename from app/src/main/java/com/ttstd/dialer/fragment/dialog/call/CallFragment.java
rename to app/src/main/java/com/ttstd/dialer/fragment/dialog/contact/call/CallFragment.java
index f9494e9..bebe2f3 100644
--- a/app/src/main/java/com/ttstd/dialer/fragment/dialog/call/CallFragment.java
+++ b/app/src/main/java/com/ttstd/dialer/fragment/dialog/contact/call/CallFragment.java
@@ -1,4 +1,4 @@
-package com.ttstd.dialer.fragment.dialog.call;
+package com.ttstd.dialer.fragment.dialog.contact.call;
import android.Manifest;
import android.app.Activity;
@@ -23,14 +23,14 @@ import androidx.fragment.app.FragmentTransaction;
import com.hjq.toast.Toaster;
import com.ttstd.dialer.R;
import com.ttstd.dialer.base.mvvm.fragment.BaseMvvmDialogFragment;
-import com.ttstd.dialer.databinding.FragmentCallBinding;
+import com.ttstd.dialer.databinding.DialogFragmentContactCallBinding;
import com.ttstd.dialer.db.contact.ContactInfo;
import com.ttstd.dialer.service.DialerAccessibilityService;
import com.ttstd.dialer.utils.AccessibilityServiceHelper;
import com.ttstd.dialer.utils.ApkUtils;
import com.ttstd.dialer.utils.Logger;
-public class CallFragment extends BaseMvvmDialogFragment {
+public class CallFragment extends BaseMvvmDialogFragment {
private static final String TAG = "CallFragment";
private static final int REQUEST_CODE_CALL = 7897;
@@ -47,7 +47,7 @@ public class CallFragment extends BaseMvvmDialogFragment {
+
+
+}
diff --git a/app/src/main/java/com/ttstd/dialer/fragment/dialog/contact/delete/ContactDeleteDialogFragment.java b/app/src/main/java/com/ttstd/dialer/fragment/dialog/contact/delete/ContactDeleteDialogFragment.java
new file mode 100644
index 0000000..77d516e
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/fragment/dialog/contact/delete/ContactDeleteDialogFragment.java
@@ -0,0 +1,131 @@
+package com.ttstd.dialer.fragment.dialog.contact.delete;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentTransaction;
+
+import com.hjq.toast.Toaster;
+import com.ttstd.dialer.R;
+import com.ttstd.dialer.base.mvvm.fragment.BaseMvvmDialogFragment;
+import com.ttstd.dialer.databinding.DialogFragmentDeleteContactBinding;
+import com.ttstd.dialer.db.contact.ContactInfo;
+import com.ttstd.dialer.utils.AvatarCacheUtil;
+import com.ttstd.dialer.utils.GlideUtils;
+import com.ttstd.dialer.utils.Logger;
+
+public class ContactDeleteDialogFragment extends BaseMvvmDialogFragment {
+
+ private static final String TAG = "EditDialogFragment";
+ private Activity mContext;
+ private ContactInfo mContactInfo;
+
+ public interface ContactOperationListener {
+ void onCancel(ContactInfo contactInfo);
+
+ void onDelete(ContactInfo contactInfo);
+ }
+
+ private ContactOperationListener mContactOperationListener;
+
+ public void setContactOperateListener(ContactOperationListener contactOperationListener) {
+ mContactOperationListener = contactOperationListener;
+ }
+
+ public ContactDeleteDialogFragment(ContactInfo contactInfo) {
+ mContactInfo = contactInfo;
+ }
+
+ @Override
+ protected int getLayoutId() {
+ return R.layout.dialog_fragment_delete_contact;
+ }
+
+ @Override
+ protected void initDataBinding() {
+ mContext = getActivity();
+ mViewModel.setContext(mContext);
+ mViewModel.setVDBinding(mViewDataBinding);
+ mViewModel.setLifecycle(getLifecycleSubject());
+ mViewDataBinding.setClick(new BtnClick());
+ }
+
+ @Override
+ protected void initView(Bundle bundle) {
+ if (mContactInfo != null) {
+ mViewDataBinding.setContactInfo(mContactInfo);
+ GlideUtils.loadImageSafe(mContext, mContactInfo.getAvatar(), mViewDataBinding.contactAvatar, AvatarCacheUtil.getAvatar(mContext, mContactInfo.getName()));
+ } else {
+ Toaster.show("无联系人信息");
+ dismiss();
+ }
+ }
+
+ @Override
+ protected void initData(Bundle savedInstanceState) {
+
+ }
+
+ @Override
+ public void fetchData() {
+
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ if (getDialog() != null) {
+ Window window = getDialog().getWindow();
+ if (window == null) return;
+ WindowManager.LayoutParams params = window.getAttributes();
+ params.width = WindowManager.LayoutParams.WRAP_CONTENT;
+ params.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ params.gravity = Gravity.CENTER;
+ window.setAttributes(params);
+ window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ getDialog().setCancelable(true);
+ getDialog().setCanceledOnTouchOutside(true);
+ }
+ }
+
+ @Override
+ public void show(FragmentManager manager, String tag) {
+ DialogFragment fragment = (DialogFragment) manager.findFragmentByTag(tag);
+ if (fragment != null && fragment.isAdded()
+ && fragment.getDialog() != null && fragment.getDialog().isShowing()) {
+ return;
+ }
+
+ try {
+ FragmentTransaction ft = manager.beginTransaction();
+ ft.add(this, tag);
+ ft.commitAllowingStateLoss();
+ } catch (Exception e) {
+ Logger.e(TAG, "show: " + e.getMessage());
+ }
+ }
+
+ public class BtnClick {
+ public void onCancel(View view) {
+ if (mContactOperationListener != null) {
+ mContactOperationListener.onCancel(mContactInfo);
+ }
+ dismiss();
+ }
+
+ public void onDelete(View view) {
+ if (mContactOperationListener != null) {
+ mContactOperationListener.onDelete(mContactInfo);
+ }
+ dismiss();
+ }
+ }
+}
diff --git a/app/src/main/java/com/ttstd/dialer/fragment/dialog/contact/delete/ContactDeleteViewModel.java b/app/src/main/java/com/ttstd/dialer/fragment/dialog/contact/delete/ContactDeleteViewModel.java
new file mode 100644
index 0000000..8e3bf2b
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/fragment/dialog/contact/delete/ContactDeleteViewModel.java
@@ -0,0 +1,8 @@
+package com.ttstd.dialer.fragment.dialog.contact.delete;
+
+import com.trello.rxlifecycle4.android.FragmentEvent;
+import com.ttstd.dialer.base.mvvm.BaseViewModel;
+import com.ttstd.dialer.databinding.DialogFragmentDeleteContactBinding;
+
+public class ContactDeleteViewModel extends BaseViewModel {
+}
diff --git a/app/src/main/java/com/ttstd/dialer/fragment/dialog/contact/edit/EditDialogFragment.java b/app/src/main/java/com/ttstd/dialer/fragment/dialog/contact/edit/EditDialogFragment.java
new file mode 100644
index 0000000..d083690
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/fragment/dialog/contact/edit/EditDialogFragment.java
@@ -0,0 +1,131 @@
+package com.ttstd.dialer.fragment.dialog.contact.edit;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentTransaction;
+
+import com.hjq.toast.Toaster;
+import com.ttstd.dialer.R;
+import com.ttstd.dialer.base.mvvm.fragment.BaseMvvmDialogFragment;
+import com.ttstd.dialer.databinding.DialogFragmentEditContactBinding;
+import com.ttstd.dialer.db.contact.ContactInfo;
+import com.ttstd.dialer.utils.AvatarCacheUtil;
+import com.ttstd.dialer.utils.GlideUtils;
+import com.ttstd.dialer.utils.Logger;
+
+public class EditDialogFragment extends BaseMvvmDialogFragment {
+
+ private static final String TAG = "EditDialogFragment";
+ private Activity mContext;
+ private ContactInfo mContactInfo;
+
+ public interface ContactOperationListener {
+ void onContactEdit(ContactInfo contactInfo);
+
+ void onContactDelete(ContactInfo contactInfo);
+ }
+
+ private ContactOperationListener mContactOperationListener;
+
+ public void setContactOperateListener(ContactOperationListener contactOperationListener) {
+ mContactOperationListener = contactOperationListener;
+ }
+
+ public EditDialogFragment(ContactInfo contactInfo) {
+ mContactInfo = contactInfo;
+ }
+
+ @Override
+ protected int getLayoutId() {
+ return R.layout.dialog_fragment_edit_contact;
+ }
+
+ @Override
+ protected void initDataBinding() {
+ mContext = getActivity();
+ mViewModel.setContext(mContext);
+ mViewModel.setVDBinding(mViewDataBinding);
+ mViewModel.setLifecycle(getLifecycleSubject());
+ mViewDataBinding.setClick(new BtnClick());
+ }
+
+ @Override
+ protected void initView(Bundle bundle) {
+ if (mContactInfo != null) {
+ mViewDataBinding.setContactInfo(mContactInfo);
+ GlideUtils.loadImageSafe(mContext, mContactInfo.getAvatar(), mViewDataBinding.contactAvatar, AvatarCacheUtil.getAvatar(mContext, mContactInfo.getName()));
+ } else {
+ Toaster.show("无联系人信息");
+ dismiss();
+ }
+ }
+
+ @Override
+ protected void initData(Bundle savedInstanceState) {
+
+ }
+
+ @Override
+ public void fetchData() {
+
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ if (getDialog() != null) {
+ Window window = getDialog().getWindow();
+ if (window == null) return;
+ WindowManager.LayoutParams params = window.getAttributes();
+ params.width = WindowManager.LayoutParams.WRAP_CONTENT;
+ params.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ params.gravity = Gravity.CENTER;
+ window.setAttributes(params);
+ window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ getDialog().setCancelable(true);
+ getDialog().setCanceledOnTouchOutside(true);
+ }
+ }
+
+ @Override
+ public void show(FragmentManager manager, String tag) {
+ DialogFragment fragment = (DialogFragment) manager.findFragmentByTag(tag);
+ if (fragment != null && fragment.isAdded()
+ && fragment.getDialog() != null && fragment.getDialog().isShowing()) {
+ return;
+ }
+
+ try {
+ FragmentTransaction ft = manager.beginTransaction();
+ ft.add(this, tag);
+ ft.commitAllowingStateLoss();
+ } catch (Exception e) {
+ Logger.e(TAG, "show: " + e.getMessage());
+ }
+ }
+
+ public class BtnClick {
+ public void onEditContact(View view) {
+ if (mContactOperationListener != null) {
+ mContactOperationListener.onContactEdit(mContactInfo);
+ }
+ dismiss();
+ }
+
+ public void onDeleteContact(View view) {
+ if (mContactOperationListener != null) {
+ mContactOperationListener.onContactDelete(mContactInfo);
+ }
+ dismiss();
+ }
+ }
+}
diff --git a/app/src/main/java/com/ttstd/dialer/fragment/dialog/contact/edit/EditViewModel.java b/app/src/main/java/com/ttstd/dialer/fragment/dialog/contact/edit/EditViewModel.java
new file mode 100644
index 0000000..1bc7202
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/fragment/dialog/contact/edit/EditViewModel.java
@@ -0,0 +1,8 @@
+package com.ttstd.dialer.fragment.dialog.contact.edit;
+
+import com.trello.rxlifecycle4.android.FragmentEvent;
+import com.ttstd.dialer.base.mvvm.BaseViewModel;
+import com.ttstd.dialer.databinding.DialogFragmentEditContactBinding;
+
+public class EditViewModel extends BaseViewModel {
+}
diff --git a/app/src/main/java/com/ttstd/dialer/fragment/home/HomeFragment.java b/app/src/main/java/com/ttstd/dialer/fragment/home/HomeFragment.java
index 57adcc1..4cec410 100644
--- a/app/src/main/java/com/ttstd/dialer/fragment/home/HomeFragment.java
+++ b/app/src/main/java/com/ttstd/dialer/fragment/home/HomeFragment.java
@@ -8,21 +8,32 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.provider.Settings;
+import android.text.TextUtils;
import android.view.View;
import androidx.fragment.app.Fragment;
+import androidx.lifecycle.Observer;
import com.hjq.toast.Toaster;
+import com.jeremyliao.liveeventbus.LiveEventBus;
+import com.qweather.sdk.response.weather.WeatherDaily;
import com.ttstd.dialer.R;
import com.ttstd.dialer.activity.contact.list.ContactListActivity;
import com.ttstd.dialer.activity.weather.main.WeatherMainActivity;
import com.ttstd.dialer.base.mvvm.fragment.BaseMvvmFragment;
+import com.ttstd.dialer.bean.req.SnLocationReq;
+import com.ttstd.dialer.config.CommonConfig;
import com.ttstd.dialer.databinding.FragmentHomeBinding;
+import com.ttstd.dialer.manager.WeatherUpdateManager;
import com.ttstd.dialer.utils.ApkUtils;
import com.ttstd.dialer.utils.DateUtil;
import com.ttstd.dialer.utils.Logger;
import com.ttstd.dialer.utils.LunarCalendarFestivalUtils;
+import kotlinx.coroutines.CoroutineScope;
+import kotlinx.coroutines.CoroutineScopeKt;
+import kotlinx.coroutines.Job;
+
/**
* A simple {@link Fragment} subclass.
* Use the {@link HomeFragment#newInstance} factory method to
@@ -33,6 +44,12 @@ public class HomeFragment extends BaseMvvmFragment() {
+ @Override
+ public void onChanged(SnLocationReq snLocationReq) {
+ mViewDataBinding.tvLocation.setText(snLocationReq.getDistrict());
+ }
+ });
}
@Override
@@ -118,6 +143,58 @@ public class HomeFragment extends BaseMvvmFragment {
+ if (weatherNowResponse != null) {
+ Logger.e(TAG, "observeWeatherUpdates", "weatherNowResponse = " + weatherNowResponse);
+ mViewDataBinding.tvWeather.setText(weatherNowResponse.getNow().getText());
+ }
+
+ return null;
+ });
+
+ weatherHourlyJob = weatherUpdateManager.observeWeatherHourly(weatherScope, weatherHourlyResponse -> {
+ if (weatherHourlyResponse != null) {
+ Logger.e(TAG, "observeWeatherUpdates", "weatherHourlyResponse = " + weatherHourlyResponse);
+ }
+ return null;
+ });
+
+ weatherDailyJob = weatherUpdateManager.observeWeatherDaily(weatherScope, weatherDailyResponse -> {
+ if (weatherDailyResponse != null) {
+ Logger.e(TAG, "observeWeatherUpdates", "weatherDailyResponse = " + weatherDailyResponse);
+
+ WeatherDaily weatherDaily = weatherDailyResponse.getDaily().get(0);
+ String iconDay = weatherDaily.getIconDay();
+ // 获取资源ID
+ String fileName = "qweather_" + iconDay; // 替换为你的文件名
+ int resId = mContext.getResources().getIdentifier(fileName, "raw", mContext.getPackageName());
+ if (resId != 0) {
+ mViewDataBinding.ivQweatherIcon.setImageDrawable(mContext.getDrawable(resId));
+ } else {
+ // 处理错误
+ Logger.e("GlideLoad", "Raw resource not found: " + fileName);
+ }
+ }
+ return null;
+ });
+ }
+
public void registerReceivers() {
registerTimeReceiver();
}
@@ -152,14 +229,16 @@ public class HomeFragment extends BaseMvvmFragment DEFAULT_APP_PACKAGES = new ArrayList() {{
- this.add("com.android.settings");
this.add("com.android.dialer");
- this.add("com.android.camera2");
- this.add("com.android.messaging");
this.add("com.android.contacts");
+ this.add("com.android.messaging");
+ this.add("com.android.settings");
+ this.add("com.android.camera2");
+
this.add("com.tencent.mm");
this.add("com.jiangjia.gif");
this.add("com.ss.android.ugc.aweme");
diff --git a/app/src/main/java/com/ttstd/dialer/manager/ContactManager.java b/app/src/main/java/com/ttstd/dialer/manager/ContactManager.java
new file mode 100644
index 0000000..25c6db8
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/manager/ContactManager.java
@@ -0,0 +1,322 @@
+package com.ttstd.dialer.manager;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.trello.rxlifecycle4.RxLifecycle;
+import com.trello.rxlifecycle4.android.ActivityEvent;
+import com.ttstd.dialer.bean.BaseResponse;
+import com.ttstd.dialer.db.contact.ContactInfo;
+import com.ttstd.dialer.db.contact.ContactRepository;
+import com.ttstd.dialer.network.BaseObserver;
+import com.ttstd.dialer.network.OkHttpManager;
+import com.ttstd.dialer.utils.Logger;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
+import io.reactivex.rxjava3.annotations.NonNull;
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.core.ObservableEmitter;
+import io.reactivex.rxjava3.core.ObservableOnSubscribe;
+import io.reactivex.rxjava3.core.Observer;
+import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.schedulers.Schedulers;
+import io.reactivex.rxjava3.subjects.BehaviorSubject;
+
+public class ContactManager {
+ private static final String TAG = "ContactManager";
+ private static volatile ContactManager instance;
+
+ private ContactRepository mRepository;
+ private Context mContext;
+
+ // 同步状态常量
+ private static final int SYNC_STATUS_SYNCED = 1; // 已同步
+ private static final int SYNC_STATUS_UNSYNCED = 0; // 未同步
+ private static final int SYNC_STATUS_DELETED = 2; // 已删除(待同步删除)
+
+ private ContactManager(Context context) {
+ this.mContext = context.getApplicationContext();
+ this.mRepository = new ContactRepository(mContext);
+ }
+
+ public static ContactManager getInstance(Context context) {
+ if (instance == null) {
+ synchronized (ContactManager.class) {
+ if (instance == null) {
+ instance = new ContactManager(context);
+ }
+ }
+ }
+ return instance;
+ }
+
+ /**
+ * 获取联系人列表(优先从网络获取,失败则从本地数据库获取)
+ *
+ * @param lifecycle RxLifecycle 生命周期绑定
+ * @param callback 回调接口
+ */
+ public void getContacts(BehaviorSubject lifecycle, ContactCallback callback) {
+ Logger.e(TAG, "开始获取联系人列表");
+
+ OkHttpManager.getInstance().getContactListObservable(lifecycle)
+ .compose(RxLifecycle.bindUntilEvent(lifecycle, ActivityEvent.DESTROY))
+ .subscribe(new BaseObserver>>() {
+ @Override
+ public void onSuccess(BaseResponse> listBaseResponse) {
+ Log.e(TAG, "网络请求成功: " + listBaseResponse);
+ if (listBaseResponse.isSuccess()) {
+ List networkContacts = listBaseResponse.getData();
+ if (networkContacts != null && !networkContacts.isEmpty()) {
+ // 在子线程中处理数据库同步
+ Observable.create(new ObservableOnSubscribe>() {
+ @Override
+ public void subscribe(@NonNull ObservableEmitter> emitter) throws Throwable {
+ try {
+ // 同步网络和本地数据
+ syncContacts(networkContacts);
+ // 返回排序后的联系人列表
+ List sortedContacts = networkContacts.stream()
+ .sorted((c1, c2) -> Integer.compare(c1.getPosition(), c2.getPosition()))
+ .collect(Collectors.toList());
+ emitter.onNext(sortedContacts);
+ emitter.onComplete();
+ } catch (Exception e) {
+ emitter.onError(e);
+ }
+ }
+ })
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new Observer>() {
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {
+ Logger.e(TAG, "开始处理联系人数据");
+ }
+
+ @Override
+ public void onNext(@NonNull List contactInfos) {
+ Logger.e(TAG, "联系人数据处理完成,数量: " + contactInfos.size());
+ if (callback != null) {
+ callback.onSuccess(contactInfos);
+ }
+ }
+
+ @Override
+ public void onError(@NonNull Throwable e) {
+ Logger.e(TAG, "处理联系人数据失败: " + e.getMessage());
+ if (callback != null) {
+ callback.onFailure(e);
+ }
+ }
+
+ @Override
+ public void onComplete() {
+ Logger.e(TAG, "联系人数据处理完成");
+ }
+ });
+ } else {
+ // 网络返回空数据,从本地获取
+ getLocalContacts(callback);
+ }
+ } else {
+ // 网络请求业务失败,从本地获取
+ getLocalContacts(callback);
+ }
+ }
+
+ @Override
+ public void onFailure(Throwable e) {
+ Log.e(TAG, "网络请求失败: " + e.getMessage());
+ // 网络请求失败,从本地获取
+ getLocalContacts(callback);
+ }
+ });
+ }
+
+ /**
+ * 从本地数据库获取联系人
+ */
+ private void getLocalContacts(ContactCallback callback) {
+ Logger.e(TAG, "从本地数据库获取联系人");
+
+ Observable.create(new ObservableOnSubscribe>() {
+ @Override
+ public void subscribe(@NonNull ObservableEmitter> emitter) throws Throwable {
+ try {
+ List localContacts = mRepository.getAllContacts();
+ List sortedContacts = localContacts.stream()
+ .sorted((c1, c2) -> Integer.compare(c1.getPosition(), c2.getPosition()))
+ .collect(Collectors.toList());
+ emitter.onNext(sortedContacts);
+ emitter.onComplete();
+ } catch (Exception e) {
+ emitter.onError(e);
+ }
+ }
+ })
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new Observer>() {
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {
+ Logger.e(TAG, "开始获取本地联系人");
+ }
+
+ @Override
+ public void onNext(@NonNull List contactInfos) {
+ Logger.e(TAG, "获取本地联系人成功,数量: " + contactInfos.size());
+ if (callback != null) {
+ callback.onSuccess(contactInfos);
+ }
+ }
+
+ @Override
+ public void onError(@NonNull Throwable e) {
+ Logger.e(TAG, "获取本地联系人失败: " + e.getMessage());
+ if (callback != null) {
+ callback.onFailure(e);
+ }
+ }
+
+ @Override
+ public void onComplete() {
+ Logger.e(TAG, "获取本地联系人完成");
+ }
+ });
+ }
+
+ /**
+ * 同步网络和本地联系人数据(增删改查对比)
+ *
+ * @param networkContacts 网络联系人列表
+ */
+ private void syncContacts(List networkContacts) {
+ Logger.e(TAG, "开始同步联系人数据");
+
+ // 获取本地所有联系人
+ List localContacts = mRepository.getAllContacts();
+
+ // 创建 Map 以便快速查找
+ // 网络联系人:以 id 为 key
+ Map networkMap = new HashMap<>();
+ for (ContactInfo networkContact : networkContacts) {
+ networkMap.put(networkContact.getId(), networkContact);
+ }
+
+ // 本地联系人:以 id 为 key
+ Map localMap = new HashMap<>();
+ for (ContactInfo localContact : localContacts) {
+ localMap.put(localContact.getId(), localContact);
+ }
+
+ Logger.e(TAG, "网络联系人数量: " + networkMap.size() + ", 本地联系人数量: " + localMap.size());
+
+ // 1. 处理新增和更新:遍历网络联系人
+ for (ContactInfo networkContact : networkContacts) {
+ ContactInfo localContact = localMap.get(networkContact.getId());
+
+ if (localContact == null) {
+ // 本地不存在,需要新增
+ Logger.e(TAG, "新增联系人: " + networkContact.getName());
+ networkContact.setSyncStatus(SYNC_STATUS_SYNCED);
+ networkContact.setLocalId(null);
+ mRepository.insert(networkContact);
+ } else {
+ // 本地存在,检查是否需要更新(比较 updateTime)
+ if (networkContact.getUpdateTime() > localContact.getUpdateTime()) {
+ Logger.e(TAG, "更新联系人: " + networkContact.getName());
+ networkContact.setSyncStatus(SYNC_STATUS_SYNCED);
+ networkContact.setLocalId(localContact.getLocalId());
+ mRepository.update(networkContact);
+ } else {
+ Logger.e(TAG, "联系人无变化: " + networkContact.getName());
+ }
+ }
+ }
+
+ // 2. 处理删除:遍历本地联系人,找出网络中不存在的
+ for (ContactInfo localContact : localContacts) {
+ if (!networkMap.containsKey(localContact.getId())) {
+ if (localContact.getSyncStatus() == SYNC_STATUS_SYNCED) {
+ Logger.e(TAG, "删除联系人: " + localContact.getName());
+ mRepository.delete(localContact);
+ } else {
+ Logger.e(TAG, "联系人未同步,不删除: " + localContact.getName());
+ }
+ }
+ }
+
+ Logger.e(TAG, "联系人同步完成");
+ }
+
+ public void compareContacts(List networkContacts, List localContacts) {
+ Logger.e(TAG, "开始比较联系人数据");
+
+ // 创建 Map 以便快速查找
+ // 网络联系人:以 id 为 key
+ Map networkMap = new HashMap<>();
+ for (ContactInfo networkContact : networkContacts) {
+ networkMap.put(networkContact.getId(), networkContact);
+ }
+
+ // 本地联系人:以 id 为 key
+ Map localMap = new HashMap<>();
+ for (ContactInfo localContact : localContacts) {
+ localMap.put(localContact.getId(), localContact);
+ }
+
+ Logger.e(TAG, "网络联系人数量: " + networkMap.size() + ", 本地联系人数量: " + localMap.size());
+
+ // 1. 处理新增和更新:遍历网络联系人
+ for (ContactInfo networkContact : networkContacts) {
+ ContactInfo localContact = localMap.get(networkContact.getId());
+
+ if (localContact == null) {
+ // 本地不存在,需要新增
+ Logger.e(TAG, "新增联系人: " + networkContact.getName());
+ networkContact.setSyncStatus(SYNC_STATUS_SYNCED);
+ networkContact.setLocalId(null);
+// mRepository.insert(networkContact);
+ } else {
+ // 本地存在,检查是否需要更新(比较 updateTime)
+ if (networkContact.getUpdateTime() > localContact.getUpdateTime()) {
+ Logger.e(TAG, "更新联系人: " + networkContact.getName());
+ networkContact.setSyncStatus(SYNC_STATUS_SYNCED);
+ networkContact.setLocalId(localContact.getLocalId());
+// mRepository.update(networkContact);
+ } else {
+ Logger.e(TAG, "联系人无变化: " + networkContact.getName());
+ }
+ }
+ }
+
+ // 2. 处理删除:遍历本地联系人,找出网络中不存在的
+ for (ContactInfo localContact : localContacts) {
+ if (!networkMap.containsKey(localContact.getId())) {
+ if (localContact.getSyncStatus() == SYNC_STATUS_SYNCED) {
+ Logger.e(TAG, "删除联系人: " + localContact.getName());
+// mRepository.delete(localContact);
+ } else {
+ Logger.e(TAG, "联系人未同步,不删除: " + localContact.getName());
+ }
+ }
+ }
+
+ Logger.e(TAG, "联系人比较完成");
+ }
+
+ /**
+ * 联系人回调接口
+ */
+ public interface ContactCallback {
+ void onSuccess(List contacts);
+
+ void onFailure(Throwable e);
+ }
+}
diff --git a/app/src/main/java/com/ttstd/dialer/manager/ContactSyncManager.java b/app/src/main/java/com/ttstd/dialer/manager/ContactSyncManager.java
new file mode 100644
index 0000000..fbaad48
--- /dev/null
+++ b/app/src/main/java/com/ttstd/dialer/manager/ContactSyncManager.java
@@ -0,0 +1,95 @@
+package com.ttstd.dialer.manager;
+
+import android.content.Context;
+
+import com.ttstd.dialer.db.contact.ContactInfo;
+import com.ttstd.dialer.db.contact.ContactRepository;
+import com.ttstd.dialer.db.contact.SyncStatus;
+import com.ttstd.dialer.network.OkHttpManager;
+import com.ttstd.dialer.utils.Logger;
+
+import java.util.List;
+
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.disposables.CompositeDisposable;
+import io.reactivex.rxjava3.schedulers.Schedulers;
+
+public class ContactSyncManager {
+ private static final String TAG = "ContactSyncManager";
+ private static volatile ContactSyncManager INSTANCE;
+
+ private ContactRepository mRepository;
+ private CompositeDisposable mDisposable;
+
+ private ContactSyncManager(Context context) {
+ mRepository = new ContactRepository(context);
+ mDisposable = new CompositeDisposable();
+ }
+
+ public static ContactSyncManager getInstance(Context context) {
+ if (INSTANCE == null) {
+ synchronized (ContactSyncManager.class) {
+ if (INSTANCE == null) {
+ INSTANCE = new ContactSyncManager(context.getApplicationContext());
+ }
+ }
+ }
+ return INSTANCE;
+ }
+
+ public void syncPendingContacts() {
+ Observable.fromCallable(() -> {
+ List pendingContacts = mRepository.getUnsyncedContacts();
+ Logger.d(TAG, "查询到 " + pendingContacts.size() + " 个待同步联系人");
+ return pendingContacts;
+ })
+ .subscribeOn(Schedulers.io())
+ .flatMapIterable(pendingContacts -> pendingContacts)
+ .subscribeOn(Schedulers.io())
+ .flatMap(contactInfo -> syncSingleContactObservable(contactInfo))
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ result -> Logger.d(TAG, "同步完成: " + result),
+ throwable -> Logger.e(TAG, "同步流程异常: " + throwable.getMessage())
+ );
+ }
+
+ private Observable syncSingleContactObservable(ContactInfo contactInfo) {
+ return OkHttpManager.getInstance().getContactInsertObservable(contactInfo)
+ .subscribeOn(Schedulers.io())
+ .flatMap(baseResponse -> {
+ if (baseResponse.isSuccess()) {
+ Long onlineId = baseResponse.getData();
+ return saveSuccessSyncStatus(contactInfo.getId(), onlineId)
+ .map(v -> "成功: " + contactInfo.getName());
+ } else {
+ return saveFailedSyncStatus(contactInfo.getId())
+ .flatMap(v -> Observable.error(new Exception("业务失败: " + baseResponse.getMsg())));
+ }
+ })
+ .onErrorResumeNext(throwable -> {
+ return saveFailedSyncStatus(contactInfo.getId())
+ .map(v -> "失败: " + contactInfo.getName());
+ });
+ }
+
+ private Observable