feat: 联系人增加头像选择

This commit is contained in:
2026-05-30 04:07:18 +08:00
parent 87903b7216
commit 1c89943459
52 changed files with 1892 additions and 226 deletions

View File

@@ -22,7 +22,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"
@@ -98,20 +98,19 @@ android {
}
}
flavorDimensions "version"
productFlavors {
// 用于正常开发和发布的版本
normal {
dimension "version"
minSdkVersion 23 // 你的原始最低版本
}
// 专门用于调试高版本特性的版本
debugApi26 {
dimension "version"
minSdkVersion 31 // 为了使用 Database Inspector
}
}
// flavorDimensions "version"
// productFlavors {
// // 用于正常开发和发布的版本
// normal {
// dimension "version"
// minSdkVersion 23 // 你的原始最低版本
// }
// // 专门用于调试高版本特性的版本
// debugApi26 {
// dimension "version"
// minSdkVersion 31 // 为了使用 Database Inspector
// }
// }
signingConfigs {
keypub {
@@ -232,6 +231,10 @@ dependencies {
implementation "androidx.viewpager2:viewpager2:1.1.0"
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation "androidx.work:work-runtime:2.9.0"
implementation 'androidx.browser:browser:1.8.0'
implementation "androidx.webkit:webkit:1.14.0"
// Room依赖
implementation "androidx.room:room-runtime:2.8.4"
implementation "androidx.room:room-rxjava3:2.8.4"
@@ -358,8 +361,6 @@ dependencies {
kapt 'com.arialyy.aria:compiler:3.8.15'
//状态栏透明
implementation 'com.gitee.zackratos:UltimateBarX:0.8.0'
//指示器
implementation 'com.github.hackware1993:MagicIndicator:1.7.0'
implementation 'com.opencsv:opencsv:5.12.0'
// 吐司框架https://github.com/getActivity/Toaster
@@ -368,6 +369,24 @@ dependencies {
implementation 'com.github.getActivity:XXPermissions:20.0'
implementation 'com.github.zcweng:switch-button:0.0.3@aar'
implementation "com.github.kongzue.DialogX:DialogX:0.0.50"
implementation 'cn.6tail:lunar:1.7.7'
implementation "io.github.cymchad:BaseRecyclerViewAdapterHelper4:4.4.0"
implementation 'io.github.youth5201314:banner:2.2.3'
// TinyPinyin核心包约80KB
implementation 'com.github.promeg:tinypinyin:2.0.3'
// 可选适用于Android的中国地区词典
implementation 'com.github.promeg:tinypinyin-lexicons-android-cncity:2.0.3'
// PictureSelector 基础 (必须)
implementation 'io.github.lucksiege:pictureselector:v3.11.2'
// 图片压缩 (按需引入)
implementation 'io.github.lucksiege:compress:v3.11.2'
// 图片裁剪 (按需引入)
implementation 'io.github.lucksiege:ucrop:v3.11.2'
// 自定义相机 (按需引入)
implementation 'io.github.lucksiege:camerax:v3.11.2'
}
// 在 dependencies 之后添加

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
coreApp="true"
package="com.ttstd.dialer"
android:sharedUserId="android.uid.system">
@@ -23,6 +24,13 @@
<uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.SET_ALARM" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!--baidumap start-->
<!-- 这个权限用于进行网络定位-->
@@ -104,13 +112,18 @@
android:name=".activity.settings.utils.SettingsUtilsActivity"
android:launchMode="singleTask"
android:screenOrientation="portrait" />
<activity
android:name=".activity.alarm.AlarmAlertActivity"
android:showOnLockScreen="true"
android:turnScreenOn="true"
android:launchMode="singleInstance"
android:exported="false" />
<service
android:name=".service.main.MainService"
android:enabled="true"
android:exported="false" />
<service
android:name=".service.DialerAccessibilityService"
android:exported="true"
@@ -125,18 +138,29 @@
android:resource="@xml/accessibility_service_config" />
</service>
<receiver
android:name=".receiver.AppChangedReceiver"
android:enabled="true"
android:exported="true">
<intent-filter android:priority="1000">
<action android:name="android.intent.action.PACKAGE_ADDED" />
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<action android:name="android.intent.action.PACKAGE_REPLACED" />
<!-- <receiver-->
<!-- android:name=".receiver.AppChangedReceiver"-->
<!-- android:enabled="true"-->
<!-- android:exported="true">-->
<!-- <intent-filter android:priority="1000">-->
<!-- <action android:name="android.intent.action.PACKAGE_INSTALL" />-->
<!-- <action android:name="android.intent.action.PACKAGE_ADDED" />-->
<!-- <action android:name="android.intent.action.PACKAGE_REPLACED" />-->
<!-- <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />-->
<!-- <action android:name="android.intent.action.PACKAGE_REMOVED" />-->
<!-- <action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />-->
<!-- <action android:name="android.intent.action.PACKAGE_CHANGED" />-->
<!-- <action android:name="android.intent.action.PACKAGE_ENABLE_ROLLBACK" />-->
<!-- <action android:name="android.intent.action.CANCEL_ENABLE_ROLLBACK" />-->
<!-- <action android:name="android.intent.action.ROLLBACK_COMMITTED" />-->
<data android:scheme="package" />
</intent-filter>
</receiver>
<!-- <data android:scheme="package" />-->
<!-- </intent-filter>-->
<!-- </receiver>-->
<receiver
android:name=".receiver.AlarmReceiver"
android:exported="false" />
<provider
android:name="androidx.core.content.FileProvider"

View File

@@ -0,0 +1,73 @@
package com.ttstd.dialer.activity.alarm;
import android.media.AudioAttributes;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.WindowManager;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;
import com.ttstd.dialer.R;
public class AlarmAlertActivity extends AppCompatActivity {
private Ringtone ringtone;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 允许在锁屏上显示并点亮屏幕
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setShowWhenLocked(true);
setTurnScreenOn(true);
} else {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
setContentView(R.layout.activity_alarm_alert);
Button btnStop = findViewById(R.id.btn_stop_alarm);
btnStop.setOnClickListener(v -> {
if (ringtone != null && ringtone.isPlaying()) {
ringtone.stop();
}
finish();
});
playAlarmRingtone();
}
private void playAlarmRingtone() {
Uri alarmUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
if (alarmUri == null) {
alarmUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
}
ringtone = RingtoneManager.getRingtone(this, alarmUri);
if (ringtone != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
AudioAttributes aa = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ALARM)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build();
ringtone.setAudioAttributes(aa);
}
ringtone.play();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (ringtone != null && ringtone.isPlaying()) {
ringtone.stop();
}
}
}

View File

@@ -11,8 +11,10 @@ import com.ttstd.dialer.R;
import com.ttstd.dialer.adapter.MoreAppAdapter;
import com.ttstd.dialer.base.mvvm.BaseMvvmActivity;
import com.ttstd.dialer.config.CommonConfig;
import com.ttstd.dialer.config.LiveDataAction;
import com.ttstd.dialer.databinding.ActivityAppListBinding;
import com.ttstd.dialer.db.app.AppInfo;
import com.ttstd.dialer.livedata.LiveDataBus;
import java.util.List;
@@ -64,6 +66,11 @@ public class AppListActivity extends BaseMvvmActivity<AppListViewModel, Activity
@Override
protected void initData() {
LiveDataBus.get().<String>on(LiveDataAction.ACTION_UPDATE_APPS)
.observe(this, event -> {
mViewModel.getDbAppList();
});
mViewModel.mDesktopSortAppData.observe(this, new Observer<List<AppInfo>>() {
@Override
public void onChanged(List<AppInfo> appInfos) {

View File

@@ -1,21 +1,55 @@
package com.ttstd.dialer.activity.contact.add;
import android.content.Intent;
import android.text.InputFilter;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.lifecycle.Observer;
import com.hjq.toast.Toaster;
import com.kongzue.dialogx.dialogs.PopTip;
import com.kongzue.dialogx.dialogs.WaitDialog;
import com.luck.picture.lib.basic.PictureSelector;
import com.luck.picture.lib.config.SelectMimeType;
import com.luck.picture.lib.entity.LocalMedia;
import com.ttstd.dialer.R;
import com.ttstd.dialer.base.mvvm.BaseMvvmActivity;
import com.ttstd.dialer.databinding.ActivityContactAddBinding;
import com.ttstd.dialer.db.contact.ContactInfo;
import com.ttstd.dialer.filter.NoSpaceInputFilter;
import com.ttstd.dialer.mdm.DeviceManagerService;
import com.ttstd.dialer.utils.GlideUtils;
import com.ttstd.dialer.utils.PhoneUtils;
import com.ttstd.dialer.view.GlideEngine;
import java.util.ArrayList;
public class ContactAddActivity extends BaseMvvmActivity<ContactAddViewModel, ActivityContactAddBinding> {
private static final String TAG = "ContactAddActivity";
private ActivityResultLauncher<Intent> launcherResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult result) {
int resultCode = result.getResultCode();
if (resultCode == RESULT_OK) {
ArrayList<LocalMedia> selectList = PictureSelector.obtainSelectorList(result.getData());
// analyticalSelectResults(selectList);
Log.i(TAG, "onActivityResult PictureSelector size = " + selectList.size());
GlideUtils.loadBitmapSafe(ContactAddActivity.this, selectList.get(0).getRealPath(), mViewDataBinding.nvAvatar);
} else if (resultCode == RESULT_CANCELED) {
Log.i(TAG, "onActivityResult PictureSelector Cancel");
}
}
});
;
@Override
public boolean setNightMode() {
return true;
@@ -44,6 +78,8 @@ public class ContactAddActivity extends BaseMvvmActivity<ContactAddViewModel, Ac
mViewDataBinding.etName.setFilters(new InputFilter[]{new NoSpaceInputFilter()});
mViewDataBinding.etPhone.setFilters(new InputFilter[]{new NoSpaceInputFilter()});
mViewDataBinding.etRemark.setFilters(new InputFilter[]{new NoSpaceInputFilter()});
mViewDataBinding.sbPinned.setEnabled(false);
}
@Override
@@ -52,7 +88,7 @@ public class ContactAddActivity extends BaseMvvmActivity<ContactAddViewModel, Ac
@Override
public void onChanged(Long aLong) {
if (aLong > 0) {
Toaster.show("添加成功");
PopTip.show("添加成功").iconSuccess();
finish();
} else {
@@ -62,6 +98,7 @@ public class ContactAddActivity extends BaseMvvmActivity<ContactAddViewModel, Ac
mViewModel.mDbIdData.observe(this, new Observer<Long>() {
@Override
public void onChanged(Long aLong) {
PopTip.show("添加成功").iconSuccess();
finish();
}
});
@@ -76,20 +113,26 @@ public class ContactAddActivity extends BaseMvvmActivity<ContactAddViewModel, Ac
public void save(View view) {
if (mViewDataBinding.etName.getText() == null) {
Toaster.show("请输入姓名");
PopTip.show("请输入姓名").iconWarning();
return;
}
String name = mViewDataBinding.etName.getText().toString();
if (mViewDataBinding.etPhone.getText() == null) {
Toaster.show("请输入手机号码");
if (TextUtils.isEmpty(name)) {
PopTip.show("请输入姓名").iconWarning();
return;
}
if (mViewDataBinding.etPhone.getText() == null) {
PopTip.show("请输入手机号码").iconWarning();
return;
}
String phone = mViewDataBinding.etPhone.getText().toString();
if (TextUtils.isEmpty(phone)) {
PopTip.show("请输入手机号码").iconWarning();
return;
}
if (!PhoneUtils.isValidPhone(phone)) {
Toaster.show("手机号码格式错误");
PopTip.show("手机号码格式错误").iconWarning();
return;
}
@@ -105,7 +148,7 @@ public class ContactAddActivity extends BaseMvvmActivity<ContactAddViewModel, Ac
contactInfo.setCreateTime(System.currentTimeMillis());
contactInfo.setUpdateTime(System.currentTimeMillis());
mViewModel.addContact(contactInfo);
WaitDialog.show("正在上传联系人");
}
public void saveLocalDb(View view) {
@@ -121,7 +164,17 @@ public class ContactAddActivity extends BaseMvvmActivity<ContactAddViewModel, Ac
contactInfo.setBindSn(DeviceManagerService.getInstance().getSerial());
contactInfo.setCreateTime(System.currentTimeMillis());
contactInfo.setUpdateTime(System.currentTimeMillis());
mViewModel.saveContact(contactInfo);
mViewModel.saveContactDb(contactInfo);
}
public void openSelector(View view) {
PictureSelector.create(ContactAddActivity.this)
.openGallery(SelectMimeType.ofImage())
.setImageEngine(GlideEngine.createGlideEngine())
// .setVideoPlayerEngine(videoPlayerEngine)
.forResult(launcherResult);
}
}
}

View File

@@ -46,12 +46,12 @@ public class ContactAddViewModel extends BaseViewModel<ActivityContactAddBinding
return Observable.just(id);
} else {
Logger.w(TAG, "业务失败,降级到本地保存: " + baseResponse.getMsg());
return saveContactObservable(contactInfo);
return saveContactDbObservable(contactInfo);
}
})
.onErrorResumeNext(throwable -> {
Logger.e(TAG, "网络异常,降级到本地保存: " + throwable.getMessage());
return saveContactObservable(contactInfo);
return saveContactDbObservable(contactInfo);
})
.compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
.subscribe(new Observer<Long>() {
@@ -80,7 +80,7 @@ public class ContactAddViewModel extends BaseViewModel<ActivityContactAddBinding
});
}
private Observable<Long> saveContactObservable(ContactInfo contactInfo) {
private Observable<Long> saveContactDbObservable(ContactInfo contactInfo) {
return Observable.create((ObservableOnSubscribe<Long>) emitter -> {
Logger.d(TAG, "执行本地保存: " + contactInfo);
contactInfo.setPosition(mRepository.getTotalCount() + 1);
@@ -92,8 +92,8 @@ public class ContactAddViewModel extends BaseViewModel<ActivityContactAddBinding
}).subscribeOn(Schedulers.io());
}
public void saveContact(ContactInfo contactInfo) {
saveContactObservable(contactInfo)
public void saveContactDb(ContactInfo contactInfo) {
saveContactDbObservable(contactInfo)
.compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Long>() {

View File

@@ -1,9 +1,6 @@
package com.ttstd.dialer.activity.main;
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;
@@ -20,12 +17,14 @@ import com.ttstd.dialer.R;
import com.ttstd.dialer.adapter.AppGridAdapter;
import com.ttstd.dialer.base.mvvm.BaseMvvmActivity;
import com.ttstd.dialer.config.CommonConfig;
import com.ttstd.dialer.config.LiveDataAction;
import com.ttstd.dialer.databinding.ActivityMainBinding;
import com.ttstd.dialer.db.app.AppInfo;
import com.ttstd.dialer.fragment.app.AppFragment;
import com.ttstd.dialer.fragment.contact.ContactFragment;
import com.ttstd.dialer.fragment.home.HomeFragment;
import com.ttstd.dialer.fragment.settings.SettingsFragment;
import com.ttstd.dialer.livedata.LiveDataBus;
import com.ttstd.dialer.manager.AppManager;
import com.ttstd.dialer.manager.WeatherUpdateManager;
import com.ttstd.dialer.service.main.MainService;
@@ -157,7 +156,7 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
mViewDataBinding.viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
Logger.e(TAG, "onPageScrolled: position = " + position);
Logger.v(TAG, "onPageScrolled: position = " + position);
if (mCurrentIndex == -1 && position == 0) {
mViewDataBinding.viewPager.setCurrentItem(mDefaultIndex, true);
mCurrentIndex = mDefaultIndex;
@@ -168,12 +167,12 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
@Override
public void onPageSelected(int position) {
Logger.e(TAG, "onPageSelected: position = " + position);
Logger.v(TAG, "onPageSelected: position = " + position);
}
@Override
public void onPageScrollStateChanged(int state) {
Logger.e(TAG, "onPageScrollStateChanged: state = " + state);
Logger.v(TAG, "onPageScrollStateChanged: state = " + state);
}
});
mViewDataBinding.magicIndicator.setNavigator(mScaleCircleNavigator);
@@ -213,6 +212,12 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
}
});
LiveDataBus.get().<String>on(LiveDataAction.ACTION_UPDATE_APPS)
.observe(this, event -> {
mViewModel.getOutsideApp();
mViewModel.getHotseatApp();
});
mViewModel.mDesktopSortAppData.observe(this, new Observer<List<AppInfo>>() {
@Override
public void onChanged(List<AppInfo> appInfos) {
@@ -319,35 +324,11 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
}
private void registerReceivers() {
registerAppChangedReceive();
}
private void unregisterReceivers() {
if (mPackageChangedReceiver != null) {
unregisterReceiver(mPackageChangedReceiver);
}
}
private PackageChangedReceiver mPackageChangedReceiver;
private void registerAppChangedReceive() {
if (null == mPackageChangedReceiver) {
mPackageChangedReceiver = new PackageChangedReceiver();
}
IntentFilter filter = new IntentFilter();
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme("package");
registerReceiver(mPackageChangedReceiver, filter);
}
private class PackageChangedReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Logger.e(TAG, "onReceive: " + intent.getAction());
}
}
private void setAppList() {

View File

@@ -1,8 +1,12 @@
package com.ttstd.dialer.activity.settings.home;
import android.content.Intent;
import android.net.Uri;
import android.view.View;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.core.content.ContextCompat;
import com.ttstd.dialer.R;
import com.ttstd.dialer.activity.settings.call.SettingsCallActivity;
import com.ttstd.dialer.activity.settings.utils.SettingsUtilsActivity;
@@ -58,5 +62,14 @@ public class SettingsActivity extends BaseMvvmActivity<SettingsViewModel, Activi
public void openUtilsSettings(View view) {
startActivity(new Intent(SettingsActivity.this, SettingsUtilsActivity.class));
}
public void aboutUs(View view) {
String url = "https://www.ttstd.com";
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
// (可选)自定义配置
builder.setToolbarColor(ContextCompat.getColor(SettingsActivity.this, R.color.colorPrimary));
CustomTabsIntent customTabsIntent = builder.build();
customTabsIntent.launchUrl(SettingsActivity.this, Uri.parse(url));
}
}
}

View File

@@ -59,15 +59,22 @@ public class SettingsUtilsActivity extends BaseMvvmActivity<SettingsUtilsViewMod
@Override
protected void initView() {
if (mViewDataBinding.siFloatWindow.isChecked()) {
mViewDataBinding.siFloatWindowKill.setEnabled(true);
} else {
mViewDataBinding.siFloatWindowKill.setEnabled(false);
}
mViewDataBinding.siFloatWindow.setOnToggleChanged(new ToggleButton.OnToggleChanged() {
@Override
public void onToggle(boolean on) {
Intent intent = new Intent(SettingsUtilsActivity.this, MainService.class);
if (on) {
mViewDataBinding.siFloatWindowKill.setEnabled(true);
intent.setAction(MainService.SHOW_FLOAT_WINDOW_ACTION);
mMMKV.encode(CommonConfig.FLOAT_WINDOW_ENABLE, 1);
} else {
mViewDataBinding.siFloatWindowKill.setEnabled(false);
intent.setAction(MainService.HIDE_FLOAT_WINDOW_ACTION);
mMMKV.encode(CommonConfig.FLOAT_WINDOW_ENABLE, 0);
}
@@ -99,6 +106,7 @@ public class SettingsUtilsActivity extends BaseMvvmActivity<SettingsUtilsViewMod
}
}
});
}
@Override

View File

@@ -79,7 +79,7 @@ public class WeatherMainViewModel extends BaseViewModel<ActivityWeatherMainBindi
public void getWeather10D(String adCode) {
Logger.e(TAG, "getWeather10D: " + adCode);
WeatherManager.getInstance().getWeather10Day(adCode, new Callback<WeatherDailyResponse>() {
WeatherManager.getInstance().getWeather10DayAdCode(adCode, new Callback<WeatherDailyResponse>() {
@Override
public void onSuccess(WeatherDailyResponse weatherDailyResponse) {
Logger.e("getWeather10D", "onSuccess: ");

View File

@@ -16,12 +16,13 @@ import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.recyclerview.widget.RecyclerView;
import com.kongzue.dialogx.dialogs.PopTip;
import com.shehuan.niv.NiceImageView;
import com.tencent.mmkv.MMKV;
import com.ttstd.dialer.R;
import com.ttstd.dialer.config.CommonConfig;
import com.ttstd.dialer.db.app.AppInfo;
import com.ttstd.dialer.fragment.dialog.shortcut.ShortcutDialogFagment;
import com.ttstd.dialer.fragment.dialog.shortcut.MoveAppDialogFagment;
import com.ttstd.dialer.utils.ApkUtils;
import com.ttstd.dialer.utils.Logger;
import com.ttstd.iconloader.IconCacheManager;
@@ -82,29 +83,49 @@ public class AppAdapter extends RecyclerView.Adapter<AppAdapter.AppHolder> imple
holder.root.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ApkUtils.openApp(mContext, appInfo.getComponentName());
if (!ApkUtils.openApp(mContext, appInfo.getComponentName())) {
PopTip.show("打开失败");
}
}
});
holder.root.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
ShortcutDialogFagment shortcutDialogFagment = new ShortcutDialogFagment(appInfo);
shortcutDialogFagment.setTitil("温馨提示");
shortcutDialogFagment.setTips("是否将应用放入更多应用");
shortcutDialogFagment.setOnClickListener(new ShortcutDialogFagment.OnClickListener() {
// ShortcutDialogFagment shortcutDialogFagment = new ShortcutDialogFagment(appInfo);
// shortcutDialogFagment.setTitile("温馨提示");
// shortcutDialogFagment.setTips("是否把这个应用移到“更多应用”里?");
// shortcutDialogFagment.setOnClickListener(new ShortcutDialogFagment.OnClickListener() {
// @Override
// public void onPositiveClick() {
// if (mShortcutCallback != null)
// mShortcutCallback.setAppInside(appInfo);
// shortcutDialogFagment.dismiss();
// }
//
// @Override
// public void onNegativeClick() {
// shortcutDialogFagment.dismiss();
// }
// });
// shortcutDialogFagment.show(mContext.getSupportFragmentManager(), "ShortcutDialogFagment");
MoveAppDialogFagment moveAppDialogFagment = new MoveAppDialogFagment(appInfo);
moveAppDialogFagment.setTitile("温馨提示");
moveAppDialogFagment.setTips("是否把这个应用移到“更多应用”里?");
moveAppDialogFagment.setOnClickListener(new MoveAppDialogFagment.OnClickListener() {
@Override
public void onPositiveClick() {
if (mShortcutCallback != null)
mShortcutCallback.setAppInside(appInfo);
shortcutDialogFagment.dismiss();
moveAppDialogFagment.dismiss();
}
@Override
public void onNegativeClick() {
shortcutDialogFagment.dismiss();
moveAppDialogFagment.dismiss();
}
});
shortcutDialogFagment.show(mContext.getSupportFragmentManager(), "ShortcutDialogFagment");
moveAppDialogFagment.show(mContext.getSupportFragmentManager(), "MoveAppDialogFagment");
return false;
}
});

View File

@@ -1,7 +1,6 @@
package com.ttstd.dialer.adapter;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.LayoutInflater;
@@ -111,8 +110,8 @@ public class AppGridAdapter extends BaseAdapter implements LoaderManager.LoaderC
@Override
public boolean onLongClick(View v) {
ShortcutDialogFagment shortcutDialogFagment = new ShortcutDialogFagment(appInfo);
shortcutDialogFagment.setTitil("温馨提示");
shortcutDialogFagment.setTips("是否将应用放入更多应用");
shortcutDialogFagment.setTitile("温馨提示");
shortcutDialogFagment.setTips("是否把这个应用移到“更多应用”里?");
shortcutDialogFagment.setOnClickListener(new ShortcutDialogFagment.OnClickListener() {
@Override
public void onPositiveClick() {

View File

@@ -1,5 +1,7 @@
package com.ttstd.dialer.adapter;
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
import android.graphics.drawable.PictureDrawable;
import android.view.LayoutInflater;
import android.view.View;
@@ -16,13 +18,12 @@ import com.qweather.sdk.response.weather.WeatherHourly;
import com.ttstd.dialer.R;
import com.ttstd.dialer.glide.GlideApp;
import com.ttstd.dialer.glide.svg.SvgSoftwareLayerSetter;
import com.ttstd.dialer.utils.GlideUtils;
import com.ttstd.dialer.utils.Logger;
import com.ttstd.dialer.utils.TimeUtils;
import java.util.List;
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
public class HourlyWeatherAdapter extends RecyclerView.Adapter<HourlyWeatherAdapter.HourlyWeather> {
private static final String TAG = "HourlyWeatherAdapter";
@@ -57,7 +58,7 @@ public class HourlyWeatherAdapter extends RecyclerView.Adapter<HourlyWeatherAdap
String icon = weatherHourly.getIcon();
// 获取资源ID
String fileName = "qweather_" + icon; // 替换为你的文件名
int resId = mContext.getResources().getIdentifier(fileName, "raw", mContext.getPackageName());
int resId = mContext.getResources().getIdentifier(fileName, "drawable", mContext.getPackageName());
if (resId != 0) {
// 使用Glide加载假设已配置SVG支持
// Glide.with(mContext)
@@ -65,7 +66,8 @@ public class HourlyWeatherAdapter extends RecyclerView.Adapter<HourlyWeatherAdap
// .load(resId) // 加载raw资源ID
// .diskCacheStrategy(DiskCacheStrategy.NONE) // raw资源通常不缓存
// .into(holder.iv_icon);
requestBuilder.load(resId).into(holder.iv_icon);
// requestBuilder.load(resId).into(holder.iv_icon);
GlideUtils.loadImageSafe(mContext, resId, holder.iv_icon, R.drawable.ic_not_applicable);
} else {
// 处理错误
Logger.e("GlideLoad", "Raw resource not found: " + fileName);

View File

@@ -93,7 +93,7 @@ public class MoreAppAdapter extends RecyclerView.Adapter<MoreAppAdapter.AppHolde
@Override
public boolean onLongClick(View v) {
ShortcutDialogFagment shortcutDialogFagment = new ShortcutDialogFagment(appInfo);
shortcutDialogFagment.setTitil("温馨提示");
shortcutDialogFagment.setTitile("温馨提示");
shortcutDialogFagment.setTips("是否将应用放在桌面显示");
shortcutDialogFagment.setOnClickListener(new ShortcutDialogFagment.OnClickListener() {

View File

@@ -1,5 +1,7 @@
package com.ttstd.dialer.adapter;
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
import android.graphics.drawable.PictureDrawable;
import android.view.LayoutInflater;
import android.view.View;
@@ -18,12 +20,11 @@ import com.ttstd.dialer.config.CommonConfig;
import com.ttstd.dialer.glide.GlideApp;
import com.ttstd.dialer.glide.svg.SvgSoftwareLayerSetter;
import com.ttstd.dialer.utils.DateUtil;
import com.ttstd.dialer.utils.GlideUtils;
import com.ttstd.dialer.utils.Logger;
import java.util.List;
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
public class WeatherAdapter extends RecyclerView.Adapter<WeatherAdapter.WeatherHolder> {
private static final String TAG = "WeatherAdapter";
@@ -71,7 +72,7 @@ public class WeatherAdapter extends RecyclerView.Adapter<WeatherAdapter.WeatherH
String iconDay = weatherDaily.getIconDay();
// 获取资源ID
String fileName = "qweather_" + iconDay; // 替换为你的文件名
int resId = mContext.getResources().getIdentifier(fileName, "raw", mContext.getPackageName());
int resId = mContext.getResources().getIdentifier(fileName, "drawable", mContext.getPackageName());
if (resId != 0) {
// 使用Glide加载假设已配置SVG支持
// Glide.with(mContext)
@@ -79,7 +80,7 @@ public class WeatherAdapter extends RecyclerView.Adapter<WeatherAdapter.WeatherH
// .load(resId) // 加载raw资源ID
// .diskCacheStrategy(DiskCacheStrategy.NONE) // raw资源通常不缓存
// .into(holder.iv_icon);
requestBuilder.load(resId).into(holder.iv_icon);
GlideUtils.loadImageSafe(mContext, resId, holder.iv_icon, R.drawable.ic_not_applicable);
} else {
// 处理错误
Logger.e("GlideLoad", "Raw resource not found: " + fileName);

View File

@@ -0,0 +1,72 @@
package com.ttstd.dialer.alarmclock;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.provider.Settings;
import cn.jpush.android.service.AlarmReceiver;
public class AlarmManagerHelper {
public static final int ALARM_REQUEST_CODE = 1001;
/**
* 设置一个精准闹钟(兼容 Android 5.0 到 Android 16
*
* @param context 上下文
* @param triggerAtMillis 触发的时间戳(毫秒)
*/
public static void setExactAlarm(Context context, long triggerAtMillis) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
if (alarmManager == null) return;
// 检查 Android 12+ 的精准闹钟权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (!alarmManager.canScheduleExactAlarms()) {
// 如果没有权限,跳转到系统设置页让用户授权
Intent intent = new Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
return;
}
}
Intent intent = new Intent(context, AlarmReceiver.class);
// 使用 FLAG_IMMUTABLE 或 FLAG_MUTABLE 增强 Android 12+ 安全性
int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ?
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE :
PendingIntent.FLAG_UPDATE_CURRENT;
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, ALARM_REQUEST_CODE, intent, flags);
// 核心兼容性定时逻辑
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Android 6.0+ 支持低功耗休眠模式Doze Mode下的精准唤醒
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// Android 4.4 - 5.1 精准唤醒
alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent);
} else {
// Android 4.4 以下
alarmManager.set(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent);
}
}
/**
* 取消闹钟
*/
public static void cancelAlarm(Context context) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
if (alarmManager != null) {
Intent intent = new Intent(context, AlarmReceiver.class);
int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ?
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE :
PendingIntent.FLAG_UPDATE_CURRENT;
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, ALARM_REQUEST_CODE, intent, flags);
alarmManager.cancel(pendingIntent);
}
}
}

View File

@@ -0,0 +1,34 @@
package com.ttstd.dialer.alarmclock;
import java.util.ArrayList;
import java.util.Calendar;
public class AlarmRepeatConfig {
public static final int REPEAT_ONCE = 0; // 只响一次
public static final int REPEAT_EVERYDAY = 1; // 每天
public static final int REPEAT_WEEKDAYS = 2; // 周一至周五
public static final int REPEAT_CUSTOM = 3; // 自定义星期几(比如只选周六日)
private int repeatType;
private ArrayList<Integer> customDays; // 存储 Calendar.MONDAY (2) 到 Calendar.SATURDAY (7)
public AlarmRepeatConfig(int repeatType) {
this.repeatType = repeatType;
this.customDays = new ArrayList<>();
if (repeatType == REPEAT_WEEKDAYS) {
customDays.add(Calendar.MONDAY);
customDays.add(Calendar.TUESDAY);
customDays.add(Calendar.WEDNESDAY);
customDays.add(Calendar.THURSDAY);
customDays.add(Calendar.FRIDAY);
}
}
public int getRepeatType() {
return repeatType;
}
public ArrayList<Integer> getCustomDays() {
return customDays;
}
}

View File

@@ -0,0 +1,69 @@
package com.ttstd.dialer.alarmclock;
import java.util.ArrayList;
import java.util.Calendar;
public class AlarmTimeCalculator {
/**
* 根据重复配置,计算下一次闹钟应该响铃的绝对时间(毫秒)
*
* @param hour 闹钟设定的小时
* @param minute 闹钟设定的分钟
* @param config 重复配置
* @return 下一次响铃的时间戳
*/
public static long calculateNextAlarmTime(int hour, int minute, AlarmRepeatConfig config) {
Calendar now = Calendar.getInstance();
Calendar target = Calendar.getInstance();
target.set(Calendar.HOUR_OF_DAY, hour);
target.set(Calendar.MINUTE, minute);
target.set(Calendar.SECOND, 0);
target.set(Calendar.MILLISECOND, 0);
// 情况 1只响一次
if (config.getRepeatType() == AlarmRepeatConfig.REPEAT_ONCE) {
// 如果设定的时间在今天已经过去了,就定在明天
if (target.getTimeInMillis() <= now.getTimeInMillis()) {
target.add(Calendar.DAY_OF_MONTH, 1);
}
return target.getTimeInMillis();
}
// 情况 2每天
if (config.getRepeatType() == AlarmRepeatConfig.REPEAT_EVERYDAY) {
if (target.getTimeInMillis() <= now.getTimeInMillis()) {
target.add(Calendar.DAY_OF_MONTH, 1);
}
return target.getTimeInMillis();
}
// 情况 3 & 4周一至周五 或 自定义星期
if (config.getRepeatType() == AlarmRepeatConfig.REPEAT_WEEKDAYS ||
config.getRepeatType() == AlarmRepeatConfig.REPEAT_CUSTOM) {
ArrayList<Integer> targetDays = config.getCustomDays();
// 从今天开始往后最多循环7天找到最近的符合星期要求的日子
for (int i = 0; i < 7; i++) {
int currentDayOfWeek = target.get(Calendar.DAY_OF_WEEK);
// 如果 target 的日子在今天或今天之后,且其星期在用户选中的列表中
if (targetDays.contains(currentDayOfWeek)) {
// 如果刚好是今天,但时间已经过去了,则不能算今天,继续往后找
if (i == 0 && target.getTimeInMillis() <= now.getTimeInMillis()) {
target.add(Calendar.DAY_OF_MONTH, 1);
continue;
}
// 找到了最近的匹配日期
return target.getTimeInMillis();
}
// 否则,日期加一天,继续匹配
target.add(Calendar.DAY_OF_MONTH, 1);
}
}
return target.getTimeInMillis();
}
}

View File

@@ -3,6 +3,8 @@ package com.ttstd.dialer.base;
import android.annotation.SuppressLint;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
@@ -12,16 +14,19 @@ import androidx.multidex.MultiDex;
import com.alibaba.android.arouter.launcher.ARouter;
import com.arialyy.aria.core.Aria;
import com.hjq.toast.Toaster;
import com.kongzue.dialogx.DialogX;
import com.tencent.bugly.crashreport.CrashReport;
import com.tencent.mmkv.MMKV;
import com.ttstd.dialer.BuildConfig;
import com.ttstd.dialer.config.CommonConfig;
import com.ttstd.dialer.config.SystemIntentAction;
import com.ttstd.dialer.manager.AppManager;
import com.ttstd.dialer.manager.MapManager;
import com.ttstd.dialer.manager.WeatherManager;
import com.ttstd.dialer.mdm.DeviceManagerService;
import com.ttstd.dialer.network.OkHttpManager;
import com.ttstd.dialer.push.PushExecutor;
import com.ttstd.dialer.receiver.AppChangedReceiver;
import com.ttstd.dialer.utils.Logger;
import com.ttstd.dialer.utils.NativeUtils;
import com.ttstd.dialer.utils.SystemUtils;
@@ -69,6 +74,22 @@ public class BaseApplication extends Application {
init();
}
@Override
public void onTerminate() {
super.onTerminate();
unregisterReceivers();
}
@Override
public void onLowMemory() {
super.onLowMemory();
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
}
private void init() {
Logger.e(TAG, "init: ");
Logger.e(TAG, "init: getNonce = " + NativeUtils.getNonce());
@@ -93,6 +114,7 @@ public class BaseApplication extends Application {
// 初始化 Toast 框架
Toaster.init(this);
DialogX.init(this);
Logger.e(TAG, "slowInit: ");
Aria.init(this);
@@ -107,6 +129,7 @@ public class BaseApplication extends Application {
WeatherManager.init(this);
IconCacheManager.init(this);
registerReceivers();
}
}
@@ -160,5 +183,38 @@ public class BaseApplication extends Application {
});
}
private void registerReceivers() {
registerAppChangedReceive();
}
@Deprecated
private void unregisterReceivers() {
if (mAppChangedReceiver != null) {
unregisterReceiver(mAppChangedReceiver);
}
}
private AppChangedReceiver mAppChangedReceiver;
private void registerAppChangedReceive() {
if (null == mAppChangedReceiver) {
mAppChangedReceiver = new AppChangedReceiver();
}
IntentFilter filter = new IntentFilter();
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
filter.addAction(Intent.ACTION_PACKAGE_INSTALL);
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
filter.addAction(Intent.ACTION_MY_PACKAGE_REPLACED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addAction(SystemIntentAction.ACTION_PACKAGE_ENABLE_ROLLBACK);
filter.addAction(SystemIntentAction.ACTION_CANCEL_ENABLE_ROLLBACK);
filter.addAction(SystemIntentAction.ACTION_ROLLBACK_COMMITTED);
filter.addDataScheme("package");
registerReceiver(mAppChangedReceiver, filter);
}
}

View File

@@ -0,0 +1,5 @@
package com.ttstd.dialer.config;
public class LiveDataAction {
public static final String ACTION_UPDATE_APPS = "update_apps";
}

View File

@@ -0,0 +1,11 @@
package com.ttstd.dialer.config;
import android.annotation.SystemApi;
public class SystemIntentAction {
public static final String ACTION_PACKAGE_ENABLE_ROLLBACK = "android.intent.action.PACKAGE_ENABLE_ROLLBACK";
public static final String ACTION_CANCEL_ENABLE_ROLLBACK = "android.intent.action.CANCEL_ENABLE_ROLLBACK";
@SystemApi
public static final String ACTION_ROLLBACK_COMMITTED = "android.intent.action.ROLLBACK_COMMITTED";
}

View File

@@ -12,21 +12,21 @@ import java.util.List;
public interface AppDao {
// 基本查询:获取总行数
@Query("SELECT COUNT(*) FROM app_list")
Integer getTotalCount();
int getTotalCount();
// 查询:根据特定列的非空值计数
@Query("SELECT COUNT(id) FROM app_list")
Integer getCountById();
int getCountById();
@Query("SELECT * FROM app_list WHERE package_name = :packageName AND class_name = :className")
AppInfo getAppInfo(String packageName, String className);
@Query("SELECT id FROM app_list WHERE package_name = :packageName AND class_name = :className")
Integer getIdByPackageAndClass(String packageName, String className);
int getIdByPackageAndClass(String packageName, String className);
// 检查数据是否存在(返回布尔值)
@Query("SELECT COUNT(*) FROM app_list WHERE package_name = :pkgName AND class_name = :clsName")
Integer checkAppInfoExists(String pkgName, String clsName);
int checkAppInfoExists(String pkgName, String clsName);
@Insert
long insert(AppInfo appInfo);
@@ -35,16 +35,16 @@ public interface AppDao {
long[] insert(List<AppInfo> appInfos);
@Update
Integer update(AppInfo appInfo);
int update(AppInfo appInfo);
@Delete
Integer delete(AppInfo appInfo);
int delete(AppInfo appInfo);
@Query("DELETE FROM app_list WHERE id = :id")
Integer deleteById(Integer id);
int deleteById(int id);
@Query("DELETE FROM app_list")
Integer deleteAll();
int deleteAll();
@Query("SELECT * FROM app_list ORDER BY position ASC")
List<AppInfo> getAllApp();
@@ -59,7 +59,7 @@ public interface AppDao {
List<AppInfo> getInsideApp();
@Query("SELECT * FROM app_list WHERE id = :id")
AppInfo getAppById(Integer id);
AppInfo getAppById(int id);
@Query("SELECT * FROM app_list WHERE label LIKE :searchQuery OR package_name LIKE :searchQuery")
List<AppInfo> searchApp(String searchQuery);

View File

@@ -0,0 +1,186 @@
package com.ttstd.dialer.fragment.dialog.shortcut;
import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.TextUtils;
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.ttstd.dialer.R;
import com.ttstd.dialer.base.mvvm.fragment.BaseMvvmDialogFragment;
import com.ttstd.dialer.databinding.FragmentDialogMoveAppBinding;
import com.ttstd.dialer.db.app.AppInfo;
import com.ttstd.dialer.utils.Logger;
public class MoveAppDialogFagment extends BaseMvvmDialogFragment<ShortcutViewModel, FragmentDialogMoveAppBinding> {
private static final String TAG = "ShortcutDialogFagment";
private Activity mContext;
private PackageManager mPackageManager;
private AppInfo mAppInfo;
private String mTitile;
private String mTips;
private String mNegativeText;
private String mPositiveText;
public MoveAppDialogFagment() {
}
public MoveAppDialogFagment(AppInfo appInfo) {
mAppInfo = appInfo;
}
public void setTitile(String titile) {
mTitile = titile;
}
public void setTips(String tips) {
mTips = tips;
}
public void setNegativeText(String negativeText) {
mNegativeText = negativeText;
}
public void setPositiveText(String positiveText) {
mPositiveText = positiveText;
}
public interface OnClickListener {
void onPositiveClick();
void onNegativeClick();
}
private OnClickListener mOnClickListener;
public void setOnClickListener(OnClickListener onClickListener) {
mOnClickListener = onClickListener;
}
@Override
protected int getLayoutId() {
return R.layout.fragment_dialog_move_app;
}
@Override
protected void initDataBinding() {
mContext = getActivity();
mPackageManager = mContext.getPackageManager();
mViewModel.setContext(mContext);
mViewModel.setVDBinding(mViewDataBinding);
mViewModel.setLifecycle(getLifecycleSubject());
mViewDataBinding.setClick(new BtnClick());
}
@Override
protected void initView(Bundle bundle) {
}
@Override
protected void initData(Bundle savedInstanceState) {
if (TextUtils.isEmpty(mTitile)) {
mViewDataBinding.tvTitle.setText("提示");
} else {
mViewDataBinding.tvTitle.setText(mTitile);
}
if (TextUtils.isEmpty(mTips)) {
mViewDataBinding.tvMessage.setText("");
mViewDataBinding.tvMessage.setVisibility(View.GONE);
} else {
mViewDataBinding.tvMessage.setText(mTips);
mViewDataBinding.tvMessage.setVisibility(View.VISIBLE);
}
if (TextUtils.isEmpty(mNegativeText)) {
mViewDataBinding.btnCancel.setText("取消");
} else {
mViewDataBinding.btnCancel.setText(mNegativeText);
}
if (TextUtils.isEmpty(mPositiveText)) {
mViewDataBinding.btnConfirm.setText("确定");
} else {
mViewDataBinding.btnConfirm.setText(mPositiveText);
}
if (mAppInfo != null) {
try {
ActivityInfo info = mPackageManager.getActivityInfo(mAppInfo.getComponentName(), 0);
mViewDataBinding.tvAppName.setText(info.loadLabel(mPackageManager));
Drawable rawIcon = info.loadIcon(mPackageManager);
mViewDataBinding.ivAppIcon.setImageDrawable(rawIcon);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
}
@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.MATCH_PARENT;
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());
}
}
@Override
public void fetchData() {
}
public class BtnClick {
public void onPositive(View view) {
if (mOnClickListener != null) {
mOnClickListener.onPositiveClick();
}
}
public void onNegative(View view) {
if (mOnClickListener != null) {
mOnClickListener.onNegativeClick();
}
}
}
}

View File

@@ -30,7 +30,7 @@ public class ShortcutDialogFagment extends BaseMvvmDialogFragment<ShortcutViewMo
private PackageManager mPackageManager;
private AppInfo mAppInfo;
private String mTitil;
private String mTitile;
private String mTips;
private String mNegativeText;
private String mPositiveText;
@@ -42,8 +42,8 @@ public class ShortcutDialogFagment extends BaseMvvmDialogFragment<ShortcutViewMo
mAppInfo = appInfo;
}
public void setTitil(String titil) {
mTitil = titil;
public void setTitile(String titile) {
mTitile = titile;
}
public void setTips(String tips) {
@@ -92,10 +92,10 @@ public class ShortcutDialogFagment extends BaseMvvmDialogFragment<ShortcutViewMo
@Override
protected void initData(Bundle savedInstanceState) {
if (TextUtils.isEmpty(mTitil)) {
if (TextUtils.isEmpty(mTitile)) {
mViewDataBinding.tvTitle.setText("提示");
} else {
mViewDataBinding.tvTitle.setText(mTitil);
mViewDataBinding.tvTitle.setText(mTitile);
}
if (TextUtils.isEmpty(mTips)) {

View File

@@ -24,6 +24,7 @@ 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.WeatherManager;
import com.ttstd.dialer.manager.WeatherUpdateManager;
import com.ttstd.dialer.utils.ApkUtils;
import com.ttstd.dialer.utils.DateUtil;
@@ -110,6 +111,7 @@ public class HomeFragment extends BaseMvvmFragment<HomeViewModel, FragmentHomeBi
mFestivalUtils = new LunarCalendarFestivalUtils();
weatherUpdateManager = WeatherUpdateManager.Companion.getInstance();
observeWeatherUpdates();
WeatherManager.getInstance().refreshweather();
setTime();
}
@@ -163,6 +165,7 @@ public class HomeFragment extends BaseMvvmFragment<HomeViewModel, FragmentHomeBi
if (weatherNowResponse != null) {
Logger.e(TAG, "observeWeatherUpdates", "weatherNowResponse = " + weatherNowResponse);
mViewDataBinding.tvWeather.setText(weatherNowResponse.getNow().getText());
mViewDataBinding.tvTempCurrent.setText(weatherNowResponse.getNow().getTemp() + "°");
}
return null;
@@ -180,10 +183,11 @@ public class HomeFragment extends BaseMvvmFragment<HomeViewModel, FragmentHomeBi
Logger.e(TAG, "observeWeatherUpdates", "weatherDailyResponse = " + weatherDailyResponse);
WeatherDaily weatherDaily = weatherDailyResponse.getDaily().get(0);
mViewDataBinding.tvTemp.setText(weatherDaily.getTempMin() + "°/" + weatherDaily.getTempMax() + "°");
String iconDay = weatherDaily.getIconDay();
// 获取资源ID
String fileName = "qweather_" + iconDay; // 替换为你的文件名
int resId = mContext.getResources().getIdentifier(fileName, "raw", mContext.getPackageName());
int resId = mContext.getResources().getIdentifier(fileName, "drawable", mContext.getPackageName());
if (resId != 0) {
mViewDataBinding.ivQweatherIcon.setImageDrawable(mContext.getDrawable(resId));
} else {

View File

@@ -0,0 +1,67 @@
package com.ttstd.dialer.livedata;
import androidx.annotation.NonNull;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
public final class LiveDataBus {
private static final class Holder {
static final LiveDataBus INSTANCE = new LiveDataBus();
}
public static LiveDataBus get() {
return Holder.INSTANCE;
}
// 每个事件类型 → 一个唯一的 key
private final Map<String, MutableLiveData<Object>> bus = new HashMap<>();
@SuppressWarnings("unchecked")
private <T> MutableLiveData<T> liveData(String key) {
synchronized (bus) {
MutableLiveData<Object> ld = bus.get(key);
if (ld == null) {
// singleLiveEvent 风格:只消费一次
ld = new SingleLiveData<>();
bus.put(key, ld);
}
return (MutableLiveData<T>) ld;
}
}
public <T> void send(String key, T value) {
liveData(key).postValue(value);
}
public <T> LiveData<T> on(String key) {
return liveData(key);
}
// ===== 只消费一次的 SingleLiveData =====
private static class SingleLiveData<T> extends MutableLiveData<T> {
private final AtomicBoolean pending = new AtomicBoolean(false);
@Override
public void observe(@NonNull LifecycleOwner owner,
@NonNull Observer<? super T> observer) {
super.observe(owner, t -> {
if (pending.compareAndSet(true, false)) {
observer.onChanged(t);
}
});
}
@Override
public void setValue(T value) {
pending.set(true);
super.setValue(value);
}
}
}

View File

@@ -15,9 +15,11 @@ import android.util.Log;
import com.tencent.mmkv.MMKV;
import com.ttstd.dialer.bean.ApkInstalledInfo;
import com.ttstd.dialer.config.CommonConfig;
import com.ttstd.dialer.config.LiveDataAction;
import com.ttstd.dialer.db.app.AppInfo;
import com.ttstd.dialer.db.app.AppRepository;
import com.ttstd.dialer.gson.GsonUtils;
import com.ttstd.dialer.livedata.LiveDataBus;
import com.ttstd.dialer.utils.ApkUtils;
import com.ttstd.dialer.utils.HashUtils;
import com.ttstd.dialer.utils.Logger;
@@ -398,23 +400,73 @@ public class AppManager {
}
public void updateApp(String packageName) {
List<ResolveInfo> resolveInfos = ApkUtils.getResolveInfoByPackageName(mContext, packageName);
if (resolveInfos.isEmpty()) return;
boolean isInstalled = ApkUtils.isInstalled(mContext, packageName);
resolveInfos.stream()
.map(this::resolveInfoToDesktopApp)
.filter(Objects::nonNull)
.filter(app -> !isAppExists(app))
.forEach(app -> {
app.setPosition(mAppRepository.getTotalCount());
try {
mAppRepository.insert(app);
} catch (Exception e) {
Logger.e(TAG, "更新应用插入失败: " + app.getPackageName(), e);
if (isInstalled) {
Logger.i(TAG, "应用已安装: " + packageName + ", 开始更新数据库");
List<ResolveInfo> resolveInfos = ApkUtils.getResolveInfoByPackageName(mContext, packageName);
if (!resolveInfos.isEmpty()) {
resolveInfos.stream()
.map(this::resolveInfoToDesktopApp)
.filter(Objects::nonNull)
.forEach(app -> {
try {
int existingId = mAppRepository.getIdByPackageAndClass(app.getPackageName(), app.getClassName());
if (existingId > 0) {
// app.setId(existingId);
// mAppRepository.update(app);
// Logger.i(TAG, "更新应用信息: " + app.getPackageName());
} else {
app.setOutside(1);
app.setPosition(mAppRepository.getTotalCount());
mAppRepository.insert(app);
Logger.i(TAG, "新增应用信息: " + app.getPackageName());
}
} catch (Exception e) {
Logger.e(TAG, "处理应用失败: " + app.getPackageName(), e);
}
});
} else {
Logger.w(TAG, "应用已安装但无可启动的 Launcher Activity");
}
} else {
Logger.i(TAG, "应用未安装或被禁用: " + packageName + ", 开始删除数据库数据");
try {
List<AppInfo> allApps = mAppRepository.getAllApp();
if (allApps != null) {
List<Integer> idsToDelete = allApps.stream()
.filter(app -> packageName.equals(app.getPackageName()))
.map(AppInfo::getId)
.filter(id -> id > 0)
.collect(Collectors.toList());
if (!idsToDelete.isEmpty()) {
idsToDelete.forEach(id -> {
try {
mAppRepository.deleteById(id);
Logger.i(TAG, "删除应用数据库记录, ID: " + id);
} catch (Exception e) {
Logger.e(TAG, "删除应用记录失败, ID: " + id, e);
}
});
Logger.i(TAG, "成功删除 " + idsToDelete.size() + " 条记录");
} else {
Logger.i(TAG, "数据库中未找到该应用的记录");
}
});
}
} catch (Exception e) {
Logger.e(TAG, "删除应用记录时发生异常", e);
}
}
LiveDataBus.get().send(LiveDataAction.ACTION_UPDATE_APPS, TAG);
}
// 重构getAllApp方法复用现有处理逻辑
public void refreshAllApps() {
CompletableFuture.runAsync(() -> {

View File

@@ -77,7 +77,6 @@ public class WeatherManager {
WeatherNowResponse weatherNowResponse = getWeatherNowCache();
if (weatherNowResponse != null) {
mWeatherUpdateManager.publishWeatherNowUpdate(weatherNowResponse);
}
WeatherHourlyResponse weatherHourlyResponse = getWeather24hCache();
if (weatherHourlyResponse != null) {

View File

@@ -101,4 +101,4 @@ class WeatherUpdateManager {
.onEach { observer(it) }
.launchIn(scope)
}
}
}

View File

@@ -0,0 +1,259 @@
package com.ttstd.dialer.network;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class OperationResult {
/**
* 操作状态枚举
*/
public enum Status {
SUCCESS, // 成功
NETWORK_ERROR, // 网络错误
SERVER_ERROR, // 服务器错误
LOCAL_ERROR, // 本地错误
SYNC_PARTIAL_SUCCESS // 部分成功(如:网络失败但本地成功)
}
private Status status;
private String errorCode;
private String errorMessage;
// 云端ID在线同步返回
private Long cloudId;
// 本地ID本地数据库返回
private Long localId;
// 数据来源标识
private DataSource dataSource;
// 是否需要重试
private boolean needRetry;
// 原始数据对象(可选)
private Object rawData;
public enum DataSource {
CLOUD, // 来自云端
LOCAL, // 来自本地
BOTH, // 两者都有
UNKNOWN // 未知
}
private OperationResult() {
}
// ==================== 静态工厂方法 ====================
/**
* 创建成功的结果(云端)
*/
public static OperationResult successFromCloud(Long cloudId) {
OperationResult result = new OperationResult();
result.status = Status.SUCCESS;
result.cloudId = cloudId;
result.dataSource = DataSource.CLOUD;
result.needRetry = false;
return result;
}
/**
* 创建成功的结果(本地)
*/
public static OperationResult successFromLocal(Long localId) {
OperationResult result = new OperationResult();
result.status = Status.SUCCESS;
result.localId = localId;
result.dataSource = DataSource.LOCAL;
result.needRetry = false;
return result;
}
/**
* 创建成功的结果(云端+本地)
*/
public static OperationResult successBoth(Long cloudId, Long localId) {
OperationResult result = new OperationResult();
result.status = Status.SUCCESS;
result.cloudId = cloudId;
result.localId = localId;
result.dataSource = DataSource.BOTH;
result.needRetry = false;
return result;
}
/**
* 创建部分成功的结果(网络失败但本地成功)
*/
public static OperationResult partialSuccess(Long localId, String errorMsg) {
OperationResult result = new OperationResult();
result.status = Status.SYNC_PARTIAL_SUCCESS;
result.localId = localId;
result.errorMessage = errorMsg;
result.dataSource = DataSource.LOCAL;
result.needRetry = true;
return result;
}
/**
* 创建网络错误的结果
*/
public static OperationResult networkError(String errorMsg) {
OperationResult result = new OperationResult();
result.status = Status.NETWORK_ERROR;
result.errorCode = "NETWORK_ERROR";
result.errorMessage = errorMsg;
result.dataSource = DataSource.UNKNOWN;
result.needRetry = true;
return result;
}
/**
* 创建服务器错误的结果
*/
public static OperationResult serverError(String errorCode, String errorMsg) {
OperationResult result = new OperationResult();
result.status = Status.SERVER_ERROR;
result.errorCode = errorCode;
result.errorMessage = errorMsg;
result.dataSource = DataSource.UNKNOWN;
result.needRetry = true;
return result;
}
/**
* 创建本地错误的结果
*/
public static OperationResult localError(String errorMsg) {
OperationResult result = new OperationResult();
result.status = Status.LOCAL_ERROR;
result.errorCode = "LOCAL_ERROR";
result.errorMessage = errorMsg;
result.dataSource = DataSource.UNKNOWN;
result.needRetry = false;
return result;
}
// ==================== Getter/Setter ====================
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
public String getErrorCode() {
return errorCode;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public Long getCloudId() {
return cloudId;
}
public void setCloudId(Long cloudId) {
this.cloudId = cloudId;
}
public Long getLocalId() {
return localId;
}
public void setLocalId(Long localId) {
this.localId = localId;
}
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public boolean isNeedRetry() {
return needRetry;
}
public void setNeedRetry(boolean needRetry) {
this.needRetry = needRetry;
}
@Nullable
public Object getRawData() {
return rawData;
}
public void setRawData(@Nullable Object rawData) {
this.rawData = rawData;
}
// ==================== 便捷判断方法 ====================
/**
* 是否完全成功
*/
public boolean isSuccess() {
return status == Status.SUCCESS;
}
/**
* 是否有错误
*/
public boolean hasError() {
return status != Status.SUCCESS;
}
/**
* 是否来自云端
*/
public boolean isFromCloud() {
return dataSource == DataSource.CLOUD || dataSource == DataSource.BOTH;
}
/**
* 是否来自本地
*/
public boolean isFromLocal() {
return dataSource == DataSource.LOCAL || dataSource == DataSource.BOTH;
}
/**
* 获取有效的ID优先云端其次本地
*/
public Long getEffectiveId() {
if (cloudId != null) {
return cloudId;
}
return localId;
}
@NonNull
@Override
public String toString() {
return "OperationResult{" +
"status=" + status +
", errorCode='" + errorCode + '\'' +
", errorMessage='" + errorMessage + '\'' +
", cloudId=" + cloudId +
", localId=" + localId +
", dataSource=" + dataSource +
", needRetry=" + needRetry +
'}';
}
}

View File

@@ -0,0 +1,90 @@
package com.ttstd.dialer.receiver;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import androidx.core.app.NotificationCompat;
import com.ttstd.dialer.activity.alarm.AlarmAlertActivity;
import com.ttstd.dialer.alarmclock.AlarmManagerHelper;
import com.ttstd.dialer.alarmclock.AlarmRepeatConfig;
import com.ttstd.dialer.alarmclock.AlarmTimeCalculator;
public class AlarmReceiver extends BroadcastReceiver {
private static final String CHANNEL_ID = "alarm_channel";
@Override
public void onReceive(Context context, Intent intent) {
// 1. 执行原有的响铃和弹出通知/界面的逻辑
// (调用上一次回答中创建的通知栏或全屏拉起逻辑)
showAlarmNotification(context);
// 2. 核心:动态轮转,实现重复闹钟
scheduleNextRepeatAlarm(context);
}
private void scheduleNextRepeatAlarm(Context context) {
// 从 SharedPreferences 中取出用户之前保存的闹钟时间和重复设置
// 实际开发中此处可以替换为 SQLite 数据库
SharedPreferences sp = context.getSharedPreferences("AlarmPrefs", Context.MODE_PRIVATE);
int hour = sp.getInt("alarm_hour", 8);
int minute = sp.getInt("alarm_minute", 0);
int repeatType = sp.getInt("repeat_type", AlarmRepeatConfig.REPEAT_ONCE);
// 如果是“只响一次”,响完就结束了,不需要再设置
if (repeatType == AlarmRepeatConfig.REPEAT_ONCE) {
return;
}
// 如果是 每天 或 周一至周五
AlarmRepeatConfig config = new AlarmRepeatConfig(repeatType);
// 重新计算下一次的时间(由于此时已经过了当前闹钟点,计算出的必定是未来的时间)
long nextTriggerTime = AlarmTimeCalculator.calculateNextAlarmTime(hour, minute, config);
// 再次调用第一步写好的精准闹钟设置工具,埋下新的定时炸弹
AlarmManagerHelper.setExactAlarm(context, nextTriggerTime);
}
private void showAlarmNotification(Context context) {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager == null) return;
// 1. 创建通知渠道 (Android 8.0+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID, "闹钟响铃", NotificationManager.IMPORTANCE_HIGH);
channel.setDescription("用于展示闹钟响铃界面");
channel.enableLights(true);
channel.setBypassDnd(true); // 绕过免打扰
notificationManager.createNotificationChannel(channel);
}
// 2. 构建点击通知或全屏弹出的 Intent
Intent alertIntent = new Intent(context, AlarmAlertActivity.class);
alertIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ?
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE :
PendingIntent.FLAG_UPDATE_CURRENT;
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, alertIntent, flags);
// 3. 构建高优先级通知(兼容 Android 10+ 后台全屏拉起)
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(android.R.drawable.ic_lock_idle_alarm)
.setContentTitle("闹钟响了")
.setContentText("时间到了,快起床!")
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(NotificationCompat.CATEGORY_ALARM)
.setAutoCancel(true)
.setFullScreenIntent(pendingIntent, true); // 核心:锁屏时直接拉起 Activity
notificationManager.notify(1, builder.build());
}
}

View File

@@ -4,16 +4,24 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import android.util.Log;
import com.tencent.mmkv.MMKV;
import com.ttstd.dialer.config.CommonConfig;
import com.ttstd.dialer.manager.AppManager;
import com.ttstd.dialer.utils.Logger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class AppChangedReceiver extends BroadcastReceiver {
private static final String TAG = "ApkInstallReceiver";
private static final String TAG = "AppChangedReceiver";
private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
// 创建一个单线程池用于处理应用变更事件
private static final ExecutorService sExecutor = Executors.newSingleThreadExecutor();
@Override
public void onReceive(final Context context, Intent intent) {
String action = intent.getAction();
@@ -35,6 +43,13 @@ public class AppChangedReceiver extends BroadcastReceiver {
default:
break;
}
sExecutor.execute(() -> {
try {
AppManager.getInstance().updateApp(packageName);
} catch (Exception e) {
Log.e(TAG, "onReceive: updateApp " + e.getMessage());
}
});
}
}

View File

@@ -1,5 +1,6 @@
package com.ttstd.dialer.utils;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -94,7 +95,7 @@ public class ApkUtils {
try {
context.startActivity(intent);
return true;
} catch (Exception e) {
} catch (ActivityNotFoundException e) {
Logger.e(TAG, "openApp: " + e.getMessage());
}
return false;

View File

@@ -154,6 +154,21 @@ public class GlideUtils {
.into(imageView);
}
public static void loadImageSafe(@Nullable Context context, int id,
@NonNull ImageView imageView,
@DrawableRes int errorId) {
RequestBuilder<Drawable> requestManager = getSafeRequestManager(context);
if (requestManager == null) {
Logger.w(TAG, "Skip loading image due to invalid context");
return;
}
requestManager
.load(id)
.error(errorId)
.into(imageView);
}
public static void loadImageSafe(@Nullable Context context, @Nullable String url,
@NonNull ImageView imageView,
Drawable drawable) {

View File

@@ -62,7 +62,7 @@ public class ClearEditText extends AppCompatEditText {
}
private void updateClearButton() {
Drawable endDrawable = length() > 0 ? clearDrawable : null;
Drawable endDrawable = (length() > 0 && hasFocus()) ? clearDrawable : null;
setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, endDrawable, null);
}

View File

@@ -0,0 +1,117 @@
package com.ttstd.dialer.view;
import android.content.Context;
import android.widget.ImageView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.luck.picture.lib.engine.ImageEngine;
import com.luck.picture.lib.utils.ActivityCompatHelper;
import com.ttstd.dialer.R;
/**
* @authorluck
* @date2019-11-13 17:02
* @describeGlide加载引擎
*/
public class GlideEngine implements ImageEngine {
/**
* 加载图片
*
* @param context 上下文
* @param url 资源url
* @param imageView 图片承载控件
*/
@Override
public void loadImage(Context context, String url, ImageView imageView) {
if (!ActivityCompatHelper.assertValidRequest(context)) {
return;
}
Glide.with(context)
.load(url)
.into(imageView);
}
@Override
public void loadImage(Context context, ImageView imageView, String url, int maxWidth, int maxHeight) {
if (!ActivityCompatHelper.assertValidRequest(context)) {
return;
}
Glide.with(context)
.load(url)
.override(maxWidth, maxHeight)
.into(imageView);
}
/**
* 加载相册目录封面
*
* @param context 上下文
* @param url 图片路径
* @param imageView 承载图片ImageView
*/
@Override
public void loadAlbumCover(Context context, String url, ImageView imageView) {
if (!ActivityCompatHelper.assertValidRequest(context)) {
return;
}
Glide.with(context)
.asBitmap()
.load(url)
.override(180, 180)
.sizeMultiplier(0.5f)
.transform(new CenterCrop(), new RoundedCorners(8))
.placeholder(R.drawable.ps_image_placeholder)
.into(imageView);
}
/**
* 加载图片列表图片
*
* @param context 上下文
* @param url 图片路径
* @param imageView 承载图片ImageView
*/
@Override
public void loadGridImage(Context context, String url, ImageView imageView) {
if (!ActivityCompatHelper.assertValidRequest(context)) {
return;
}
Glide.with(context)
.load(url)
.override(200, 200)
.centerCrop()
.placeholder(R.drawable.ps_image_placeholder)
.into(imageView);
}
@Override
public void pauseRequests(Context context) {
if (!ActivityCompatHelper.assertValidRequest(context)) {
return;
}
Glide.with(context).pauseRequests();
}
@Override
public void resumeRequests(Context context) {
if (!ActivityCompatHelper.assertValidRequest(context)) {
return;
}
Glide.with(context).resumeRequests();
}
private GlideEngine() {
}
private static final class InstanceHolder {
static final GlideEngine instance = new GlideEngine();
}
public static GlideEngine createGlideEngine() {
return InstanceHolder.instance;
}
}

View File

@@ -14,7 +14,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.ttstd.dialer.R;
import org.jetbrains.annotations.NotNull;
@@ -74,6 +73,10 @@ public class SettingItem extends ConstraintLayout {
requestLayout();
}
public boolean isChecked() {
return tb.isToggleOn();
}
public SettingItem(@NonNull @NotNull Context context) {
super(context);
init(context, null);
@@ -215,6 +218,12 @@ public class SettingItem extends ConstraintLayout {
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
tb.setEnabled(enabled);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#FAFBFC" />
<stroke
android:width="1dp"
android:color="#E5E7EB" />
<corners android:radius="18dp" />
</shape>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 按下状态:略微加深 -->
<item android:state_pressed="true">
<shape android:shape="rectangle">
<!-- 上下渐变 -->
<gradient android:angle="270" android:startColor="#E8EBF0" android:endColor="#F5F7FA" />
<!-- 浅色边框 -->
<stroke android:width="1dp" android:color="#E8EBF0" />
<corners android:radius="28dp" />
</shape>
</item>
<!-- 默认状态 -->
<item>
<shape android:shape="rectangle">
<!-- 上亮下浅灰,接近图中白色按钮 -->
<gradient android:angle="270" android:startColor="#FFFFFF" android:endColor="#F1F3F7" />
<!-- 浅色边框 -->
<stroke android:width="1dp" android:color="#E8EBF0" />
<corners android:radius="28dp" />
</shape>
</item>
</selector>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 按下状态:蓝色加深 -->
<item android:state_pressed="true">
<shape android:shape="rectangle">
<!-- 上下渐变 -->
<gradient android:angle="270" android:startColor="#1D5FEF" android:endColor="#0E4FD8" />
<!-- 浅色蓝边框 -->
<stroke android:width="1dp" android:color="#5B95FF" />
<corners android:radius="28dp" />
</shape>
</item>
<!-- 默认状态 -->
<item>
<shape android:shape="rectangle">
<!-- 上亮下深,接近图中蓝色按钮 -->
<gradient android:angle="270" android:startColor="#3F8BFF" android:endColor="#176CFF" />
<!-- 浅色边框 -->
<stroke android:width="1dp" android:color="#76A9FF" />
<corners android:radius="28dp" />
</shape>
</item>
</selector>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#FFFFFF" />
<corners android:radius="28dp" />
</shape>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#EEF5FF" />
<stroke
android:width="1dp"
android:color="#DCEBFF" />
</shape>

View File

@@ -5,8 +5,8 @@
android:viewportHeight="1024">
<path
android:pathData="M105,480a8,8 0,0 1,8 -8h799a8,8 0,0 1,8 8v64a8,8 0,0 1,-8 8H113a8,8 0,0 1,-8 -8v-64z"
android:fillColor="#323338"/>
android:fillColor="#ffffff" />
<path
android:pathData="M480,920a8,8 0,0 1,-8 -8V112a8,8 0,0 1,8 -8h64a8,8 0,0 1,8 8v800a8,8 0,0 1,-8 8h-64z"
android:fillColor="#323338"/>
android:fillColor="#ffffff" />
</vector>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<size
android:width="100dp"
android:height="100dp" />
<solid android:color="@color/ps_color_light_grey" />
</shape>
</item>
<item>
<bitmap
android:gravity="center"
android:src="@drawable/ps_ic_placeholder" />
</item>
</layer-list>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF3333"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="闹钟响铃中..."
android:textColor="#FFFFFF"
android:textSize="30sp" />
<Button
android:id="@+id/btn_stop_alarm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:text="关闭闹钟" />
</LinearLayout>

View File

@@ -78,6 +78,9 @@
android:layout_height="@dimen/dp_100"
android:layout_gravity="center"
android:layout_marginTop="@dimen/dp_32"
android:adjustViewBounds="true"
android:onClick="@{click::openSelector}"
android:scaleType="centerCrop"
android:src="@drawable/ic_default_avatar"
app:is_circle="true"
app:layout_constraintBottom_toBottomOf="parent"
@@ -250,6 +253,44 @@
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dp_56"
android:layout_marginTop="@dimen/dp_8">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_pinned"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingStart="@dimen/dp_8"
android:text="联系人置顶"
android:textColor="@color/black"
android:textSize="@dimen/sp_20"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/sb_pinned"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.suke.widget.SwitchButton
android:id="@+id/sb_pinned"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:sb_checked="true"
app:sb_effect_duration="200"
app:sb_show_indicator="false" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -91,8 +91,8 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="@dimen/dp_100"
android:layout_height="@dimen/dp_100"
app:layout_constraintBottom_toBottomOf="parent"
android:onClick="@{click::openCallSettings}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
@@ -161,6 +161,13 @@
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:text="关于"
android:layout_gravity="center"
android:onClick="@{click::aboutUs}"
android:textColor="@color/black"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@@ -0,0 +1,162 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".fragment.dialog.shortcut.MoveAppDialogFagment">
<data>
<variable
name="click"
type="com.ttstd.dialer.fragment.dialog.shortcut.MoveAppDialogFagment.BtnClick" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="@dimen/dp_320"
android:layout_height="wrap_content"
android:background="@drawable/bg_dialog_card"
android:orientation="vertical"
android:paddingLeft="28dp"
android:paddingTop="32dp"
android:paddingRight="28dp"
android:paddingBottom="28dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<!-- 顶部提示图标 -->
<FrameLayout
android:layout_width="74dp"
android:layout_height="74dp"
android:layout_gravity="center_horizontal"
android:background="@drawable/bg_tip_icon_circle">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="!"
android:textColor="#2F7BFF"
android:textSize="40sp"
android:textStyle="bold" />
</FrameLayout>
<!-- 标题 -->
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="22dp"
android:gravity="center"
android:text="温馨提示"
android:textColor="#1F2430"
android:textSize="28sp"
android:textStyle="bold" />
<!-- 主提示 -->
<TextView
android:id="@+id/tv_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="14dp"
android:gravity="center"
android:text="是否将应用放入更多应用"
android:textColor="#2B2F38"
android:textSize="20sp" />
<!-- 说明文字 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="center"
android:lineSpacingExtra="4dp"
android:text="将应用放入「更多应用」,可在应用抽屉中查看和管理,避免桌面空间不足。"
android:textColor="#8A909D"
android:textSize="15sp" />
<!-- 应用信息区域 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="88dp"
android:layout_marginTop="28dp"
android:background="@drawable/bg_app_info_card"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingLeft="20dp"
android:paddingRight="20dp">
<ImageView
android:id="@+id/iv_app_icon"
android:layout_width="48dp"
android:layout_height="48dp"
android:scaleType="fitCenter"
android:src="@mipmap/ic_launcher" />
<View
android:layout_width="1dp"
android:layout_height="38dp"
android:layout_marginLeft="18dp"
android:layout_marginRight="18dp"
android:background="#E5E7EB" />
<TextView
android:id="@+id/tv_app_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="end"
android:singleLine="true"
android:text="IndexableRecyclerView"
android:textColor="#2B2F38"
android:textSize="19sp"
android:textStyle="bold" />
</LinearLayout>
<!-- 按钮区域 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="54dp"
android:layout_marginTop="26dp"
android:orientation="horizontal">
<TextView
android:id="@+id/btnCancel"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginRight="12dp"
android:layout_weight="1"
android:background="@drawable/bg_btn_cancel"
android:gravity="center"
android:text="取消"
android:textColor="#4B5563"
android:textSize="18sp"
android:onClick="@{click::onNegative}"
android:textStyle="bold" />
<TextView
android:id="@+id/btnConfirm"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginLeft="12dp"
android:layout_weight="1"
android:background="@drawable/bg_btn_confirm"
android:gravity="center"
android:text="确定"
android:onClick="@{click::onPositive}"
android:textColor="#FFFFFF"
android:textSize="18sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -17,7 +17,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="@dimen/dp_300"
android:layout_height="@dimen/dp_240"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:background="@drawable/default_dialog_background"
android:orientation="vertical"
@@ -28,9 +28,8 @@
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dp_16"
android:gravity="center"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
@@ -42,6 +41,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/dp_24"
android:gravity="center_vertical"
android:textColor="@color/black"
android:textSize="@dimen/sp_18"
@@ -54,108 +54,168 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/dp_8"
android:layout_marginTop="@dimen/dp_16"
android:gravity="center_vertical"
android:textColor="@color/black"
android:textSize="@dimen/sp_16"
android:visibility="visible"
tools:text="消息" />
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@+id/linearLayout2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linearLayout">
<com.shehuan.niv.NiceImageView
android:id="@+id/iv_icon"
android:layout_width="@dimen/dp_40"
android:layout_height="@dimen/dp_40"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/dp_16"
app:layout_constraintBottom_toTopOf="@+id/linearLayout2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@mipmap/ic_launcher" />
app:layout_constraintTop_toBottomOf="@+id/linearLayout">
<ImageView
android:id="@+id/iv_app_icon"
android:layout_width="@dimen/dp_16"
android:layout_height="@dimen/dp_16"
android:layout_marginStart="@dimen/dp_26"
android:layout_marginTop="@dimen/dp_26"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
app:layout_constraintStart_toStartOf="@+id/iv_icon"
app:layout_constraintTop_toTopOf="parent"
tools:src="@mipmap/ic_launcher" />
<com.shehuan.niv.NiceImageView
android:id="@+id/iv_icon"
android:layout_width="@dimen/dp_64"
android:layout_height="@dimen/dp_64"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@mipmap/ic_launcher" />
<TextView
android:id="@+id/tv_app_name"
<ImageView
android:id="@+id/iv_app_icon"
android:layout_width="@dimen/dp_16"
android:layout_height="@dimen/dp_16"
android:layout_marginStart="@dimen/dp_52"
android:layout_marginTop="@dimen/dp_52"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
app:layout_constraintStart_toStartOf="@+id/iv_icon"
app:layout_constraintTop_toTopOf="parent"
tools:src="@mipmap/ic_launcher" />
<TextView
android:id="@+id/tv_app_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_20"
android:layout_marginTop="@dimen/dp_8"
android:layout_marginEnd="@dimen/dp_20"
android:gravity="center"
android:lineSpacingExtra="@dimen/dp_3"
android:maxLines="1"
android:textColor="#676767"
android:textSize="@dimen/sp_18"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/iv_icon"
tools:text="app" />
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:id="@+id/linearLayout2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_20"
android:layout_marginTop="@dimen/dp_16"
android:layout_marginEnd="@dimen/dp_20"
android:gravity="center"
android:lineSpacingExtra="@dimen/dp_3"
android:maxLines="1"
android:textColor="#676767"
android:textSize="@dimen/sp_14"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/iv_icon"
tools:text="app" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/linearLayout2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_24"
android:layout_marginEnd="@dimen/dp_24"
android:layout_marginBottom="@dimen/dp_20"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:id="@+id/tv_negative"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/default_negative_background"
android:gravity="center"
android:onClick="@{click::onNegative}"
android:singleLine="true"
android:textColor="@color/white"
android:textSize="@dimen/sp_14"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="取消" />
<TextView
android:id="@+id/tv_positive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/default_positive_background"
android:gravity="center"
android:onClick="@{click::onPositive}"
android:singleLine="true"
android:textColor="@color/white"
android:textSize="@dimen/sp_14"
android:layout_marginStart="@dimen/dp_24"
android:layout_marginEnd="@dimen/dp_24"
android:layout_marginTop="@dimen/dp_20"
android:layout_marginBottom="@dimen/dp_20"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="确定" />
app:layout_constraintStart_toStartOf="parent">
<!-- 编辑按钮 -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dp_44"
android:background="@drawable/tv_edit_background"
android:onClick="@{click::onPositive}">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/imageView"
android:layout_width="@dimen/dp_24"
android:layout_height="@dimen/dp_24"
android:src="@drawable/ic_edit"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_positive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_16"
android:maxLines="1"
android:singleLine="true"
android:text="确定"
android:textColor="#1E88E5"
android:textSize="@dimen/sp_15"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- 删除按钮 -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dp_44"
android:layout_marginTop="@dimen/dp_12"
android:background="@drawable/tv_delete_background"
android:onClick="@{click::onNegative}">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/iv_delete"
android:layout_width="@dimen/dp_24"
android:layout_height="@dimen/dp_24"
android:src="@drawable/ic_delete"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_negative"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_16"
android:maxLines="1"
android:singleLine="true"
android:text="取消"
android:textColor="#D32F2F"
android:textSize="@dimen/sp_15"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/iv_delete"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -183,7 +183,7 @@
android:layout_height="@dimen/dp_68"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:src="@drawable/qweather_100"
android:src="@drawable/ic_not_applicable"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"

View File

@@ -1,2 +1,2 @@
rootProject.name='拨号助手'
rootProject.name = '拨号助手'
include ':app', ':niceimageview', ':iconloader'