build: 电脑损坏,不知道改了啥
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
package com.ttstd.dialer.activity.app;
|
package com.ttstd.dialer.activity.app;
|
||||||
|
|
||||||
|
import android.view.DragEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import androidx.lifecycle.Observer;
|
import androidx.lifecycle.Observer;
|
||||||
@@ -8,6 +9,7 @@ import androidx.recyclerview.widget.GridLayoutManager;
|
|||||||
|
|
||||||
import com.tencent.mmkv.MMKV;
|
import com.tencent.mmkv.MMKV;
|
||||||
import com.ttstd.dialer.R;
|
import com.ttstd.dialer.R;
|
||||||
|
import com.ttstd.dialer.adapter.AppGridAdapter;
|
||||||
import com.ttstd.dialer.adapter.MoreAppAdapter;
|
import com.ttstd.dialer.adapter.MoreAppAdapter;
|
||||||
import com.ttstd.dialer.base.mvvm.BaseMvvmActivity;
|
import com.ttstd.dialer.base.mvvm.BaseMvvmActivity;
|
||||||
import com.ttstd.dialer.config.CommonConfig;
|
import com.ttstd.dialer.config.CommonConfig;
|
||||||
@@ -15,6 +17,7 @@ import com.ttstd.dialer.config.LiveDataAction;
|
|||||||
import com.ttstd.dialer.databinding.ActivityAppListBinding;
|
import com.ttstd.dialer.databinding.ActivityAppListBinding;
|
||||||
import com.ttstd.dialer.db.app.AppInfo;
|
import com.ttstd.dialer.db.app.AppInfo;
|
||||||
import com.ttstd.dialer.livedata.LiveDataBus;
|
import com.ttstd.dialer.livedata.LiveDataBus;
|
||||||
|
import com.ttstd.dialer.utils.Logger;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -24,6 +27,7 @@ public class AppListActivity extends BaseMvvmActivity<AppListViewModel, Activity
|
|||||||
private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
|
private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
|
||||||
|
|
||||||
private MoreAppAdapter mMoreAppAdapter;
|
private MoreAppAdapter mMoreAppAdapter;
|
||||||
|
private AppGridAdapter mAppGridAdapter;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -49,27 +53,95 @@ public class AppListActivity extends BaseMvvmActivity<AppListViewModel, Activity
|
|||||||
mViewDataBinding.setClick(new BtnClick());
|
mViewDataBinding.setClick(new BtnClick());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initView() {
|
protected void initView() {
|
||||||
|
mAppGridAdapter = new AppGridAdapter(this, LoaderManager.getInstance(this));
|
||||||
|
mViewDataBinding.gvApp.setAdapter(mAppGridAdapter);
|
||||||
|
|
||||||
|
|
||||||
mMoreAppAdapter = new MoreAppAdapter(LoaderManager.getInstance(this));
|
mMoreAppAdapter = new MoreAppAdapter(LoaderManager.getInstance(this));
|
||||||
mMoreAppAdapter.setShortcutCallback(new MoreAppAdapter.ShortcutCallback() {
|
|
||||||
@Override
|
|
||||||
public void setAppOutside(AppInfo appInfo) {
|
|
||||||
appInfo.setOutside(1);
|
|
||||||
mViewModel.updateAppInfo(appInfo);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
mViewDataBinding.recyclerView.setLayoutManager(new GridLayoutManager(this, 3));
|
mViewDataBinding.recyclerView.setLayoutManager(new GridLayoutManager(this, 3));
|
||||||
mViewDataBinding.recyclerView.setAdapter(mMoreAppAdapter);
|
mViewDataBinding.recyclerView.setAdapter(mMoreAppAdapter);
|
||||||
|
|
||||||
|
mViewDataBinding.recyclerView.setOnDragListener(new View.OnDragListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onDrag(View v, DragEvent event) {
|
||||||
|
switch (event.getAction()) {
|
||||||
|
case DragEvent.ACTION_DRAG_STARTED:
|
||||||
|
return true;
|
||||||
|
case DragEvent.ACTION_DRAG_ENTERED:
|
||||||
|
v.setAlpha(0.7f);
|
||||||
|
break;
|
||||||
|
case DragEvent.ACTION_DRAG_EXITED:
|
||||||
|
case DragEvent.ACTION_DRAG_ENDED:
|
||||||
|
v.setAlpha(1.0f);
|
||||||
|
break;
|
||||||
|
case DragEvent.ACTION_DROP:
|
||||||
|
AppInfo appInfo = (AppInfo) event.getLocalState();
|
||||||
|
if (appInfo != null && appInfo.getHotseat() == 1) {
|
||||||
|
appInfo.setHotseat(0);
|
||||||
|
appInfo.setOutside(0);
|
||||||
|
appInfo.setPosition(mMoreAppAdapter.getItemCount());
|
||||||
|
mViewModel.updateAppInfo(appInfo);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mViewDataBinding.clHotseat.setOnDragListener(new View.OnDragListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onDrag(View v, DragEvent event) {
|
||||||
|
switch (event.getAction()) {
|
||||||
|
case DragEvent.ACTION_DRAG_STARTED:
|
||||||
|
return true;
|
||||||
|
case DragEvent.ACTION_DRAG_ENTERED:
|
||||||
|
v.setAlpha(0.7f);
|
||||||
|
break;
|
||||||
|
case DragEvent.ACTION_DRAG_EXITED:
|
||||||
|
case DragEvent.ACTION_DRAG_ENDED:
|
||||||
|
v.setAlpha(1.0f);
|
||||||
|
break;
|
||||||
|
case DragEvent.ACTION_DROP:
|
||||||
|
AppInfo appInfo = (AppInfo) event.getLocalState();
|
||||||
|
if (appInfo != null && appInfo.getHotseat() == 0) {
|
||||||
|
// 检查数量限制
|
||||||
|
if (mAppGridAdapter.getCount() >= 5) {
|
||||||
|
android.widget.Toast.makeText(AppListActivity.this, "快捷栏最多只能添加5个应用", android.widget.Toast.LENGTH_SHORT).show();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
appInfo.setHotseat(1);
|
||||||
|
appInfo.setOutside(1);
|
||||||
|
appInfo.setHotseatPosition(mAppGridAdapter.getCount());
|
||||||
|
mViewModel.updateAppInfo(appInfo);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initData() {
|
protected void initData() {
|
||||||
LiveDataBus.get().<String>on(LiveDataAction.ACTION_UPDATE_APPS)
|
LiveDataBus.get().<String>on(LiveDataAction.ACTION_UPDATE_APPS)
|
||||||
.observe(this, event -> {
|
.observe(this, event -> {
|
||||||
mViewModel.getDbAppList();
|
mViewModel.getDbAppList();
|
||||||
|
mViewModel.getHotseatApp();
|
||||||
});
|
});
|
||||||
|
mViewModel.mHotseatAppData.observe(this, new Observer<List<AppInfo>>() {
|
||||||
|
@Override
|
||||||
|
public void onChanged(List<AppInfo> appInfos) {
|
||||||
|
mViewDataBinding.gvApp.setNumColumns(appInfos.size());
|
||||||
|
mAppGridAdapter.setAppInfos(appInfos);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mViewModel.getHotseatApp();
|
||||||
|
|
||||||
mViewModel.mDesktopSortAppData.observe(this, new Observer<List<AppInfo>>() {
|
mViewModel.mDesktopSortAppData.observe(this, new Observer<List<AppInfo>>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -84,7 +156,7 @@ public class AppListActivity extends BaseMvvmActivity<AppListViewModel, Activity
|
|||||||
@Override
|
@Override
|
||||||
public void onChanged(Integer integer) {
|
public void onChanged(Integer integer) {
|
||||||
if (integer > 0) {
|
if (integer > 0) {
|
||||||
|
mViewModel.getHotseatApp();
|
||||||
}
|
}
|
||||||
mViewModel.getDbAppList();
|
mViewModel.getDbAppList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,41 @@ public class AppListViewModel extends BaseViewModel<ActivityAppListBinding, Acti
|
|||||||
mAppRepository = new AppRepository(context);
|
mAppRepository = new AppRepository(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MutableLiveData<List<AppInfo>> mHotseatAppData = new MutableLiveData<>();
|
||||||
|
|
||||||
|
public void getHotseatApp() {
|
||||||
|
Observable.fromCallable(new Callable<List<AppInfo>>() {
|
||||||
|
@Override
|
||||||
|
public List<AppInfo> call() throws Exception {
|
||||||
|
return mAppRepository.getHotseatApp();
|
||||||
|
}
|
||||||
|
}).compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(new Observer<List<AppInfo>>() {
|
||||||
|
@Override
|
||||||
|
public void onSubscribe(@NonNull Disposable d) {
|
||||||
|
Logger.e("getHotseatApp", "onSubscribe: ");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNext(@NonNull List<AppInfo> appInfos) {
|
||||||
|
Logger.e("getHotseatApp", "onNext: " + appInfos);
|
||||||
|
mHotseatAppData.setValue(appInfos);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(@NonNull Throwable e) {
|
||||||
|
Logger.e("getHotseatApp", "onError: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComplete() {
|
||||||
|
Logger.e("getHotseatApp", "onComplete: ");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public MutableLiveData<List<AppInfo>> mDesktopSortAppData = new MutableLiveData<>();
|
public MutableLiveData<List<AppInfo>> mDesktopSortAppData = new MutableLiveData<>();
|
||||||
|
|
||||||
public void getDbAppList() {
|
public void getDbAppList() {
|
||||||
|
|||||||
@@ -77,15 +77,7 @@ public class ContactListActivity extends BaseMvvmActivity<ContactListViewModel,
|
|||||||
mContactInfoAdapter.setItemMoveCallback(new ContactInfoAdapter.ItemMoveCallback() {
|
mContactInfoAdapter.setItemMoveCallback(new ContactInfoAdapter.ItemMoveCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemMove(int fromPosition, int toPosition) {
|
public void onItemMove(int fromPosition, int toPosition) {
|
||||||
Logger.e(TAG, "onItemMove: ");
|
Logger.e(TAG, "onItemMove: from " + fromPosition + " to " + toPosition);
|
||||||
ContactInfo fromContactInfo = mContactInfos.get(fromPosition);
|
|
||||||
int fromContactPosition = fromContactInfo.getPosition();
|
|
||||||
ContactInfo toContactInfo = mContactInfos.get(toPosition);
|
|
||||||
int toContactPosition = toContactInfo.getPosition();
|
|
||||||
fromContactInfo.setPosition(toContactPosition);
|
|
||||||
mViewModel.updateItemPosition(fromContactInfo);
|
|
||||||
toContactInfo.setPosition(fromContactPosition);
|
|
||||||
mViewModel.updateItemPosition(toContactInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -93,6 +85,17 @@ public class ContactListActivity extends BaseMvvmActivity<ContactListViewModel,
|
|||||||
Logger.e(TAG, "onItemRemoved: ");
|
Logger.e(TAG, "onItemRemoved: ");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemMoveFinished() {
|
||||||
|
Logger.e(TAG, "onItemMoveFinished: ");
|
||||||
|
if (mContactInfos != null) {
|
||||||
|
for (int i = 0; i < mContactInfos.size(); i++) {
|
||||||
|
mContactInfos.get(i).setPosition(i + 1);
|
||||||
|
}
|
||||||
|
mViewModel.updateContacts(mContactInfos);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
mContactInfoAdapter.setOnClickListener(new ContactInfoAdapter.ClickListener() {
|
mContactInfoAdapter.setOnClickListener(new ContactInfoAdapter.ClickListener() {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -131,6 +131,39 @@ public class ContactListViewModel extends BaseViewModel<ActivityContactListBindi
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateContacts(List<ContactInfo> contactInfos) {
|
||||||
|
Logger.e(TAG, "updateContacts: " + contactInfos.size());
|
||||||
|
Observable.create(new ObservableOnSubscribe<Boolean>() {
|
||||||
|
@Override
|
||||||
|
public void subscribe(@NonNull ObservableEmitter<Boolean> emitter) throws Throwable {
|
||||||
|
mRepository.update(contactInfos);
|
||||||
|
emitter.onNext(true);
|
||||||
|
emitter.onComplete();
|
||||||
|
}
|
||||||
|
}).compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY))
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(new Observer<Boolean>() {
|
||||||
|
@Override
|
||||||
|
public void onSubscribe(@NonNull Disposable d) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNext(@NonNull Boolean aBoolean) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(@NonNull Throwable e) {
|
||||||
|
Logger.e("updateContacts", "onError: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComplete() {
|
||||||
|
Logger.e("updateContacts", "onComplete: ");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public MutableLiveData<Boolean> mDeleteLiveData = new MutableLiveData<>();
|
public MutableLiveData<Boolean> mDeleteLiveData = new MutableLiveData<>();
|
||||||
|
|
||||||
public void deleteContact(long id) {
|
public void deleteContact(long id) {
|
||||||
|
|||||||
@@ -79,6 +79,11 @@ public class ContactTestActivity extends BaseMvvmActivity<ContactTestViewModel,
|
|||||||
Logger.e(TAG, "mOnlineContactInfoAdapter onItemRemoved: ");
|
Logger.e(TAG, "mOnlineContactInfoAdapter onItemRemoved: ");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemMoveFinished() {
|
||||||
|
|
||||||
|
}
|
||||||
});
|
});
|
||||||
mOnlineContactInfoAdapter.setOnClickListener(new ContactInfoAdapter.ClickListener() {
|
mOnlineContactInfoAdapter.setOnClickListener(new ContactInfoAdapter.ClickListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -126,6 +131,11 @@ public class ContactTestActivity extends BaseMvvmActivity<ContactTestViewModel,
|
|||||||
Logger.e(TAG, "mLocalContactInfoAdapter onItemRemoved: ");
|
Logger.e(TAG, "mLocalContactInfoAdapter onItemRemoved: ");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemMoveFinished() {
|
||||||
|
|
||||||
|
}
|
||||||
});
|
});
|
||||||
mLocalContactInfoAdapter.setOnClickListener(new ContactInfoAdapter.ClickListener() {
|
mLocalContactInfoAdapter.setOnClickListener(new ContactInfoAdapter.ClickListener() {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -188,12 +188,6 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
|
|||||||
|
|
||||||
mAppGridAdapter = new AppGridAdapter(this, LoaderManager.getInstance(this));
|
mAppGridAdapter = new AppGridAdapter(this, LoaderManager.getInstance(this));
|
||||||
mViewDataBinding.gvApp.setAdapter(mAppGridAdapter);
|
mViewDataBinding.gvApp.setAdapter(mAppGridAdapter);
|
||||||
mAppGridAdapter.setShortcutCallback(new AppGridAdapter.ShortcutCallback() {
|
|
||||||
@Override
|
|
||||||
public void setAppInside(AppInfo appInfo) {
|
|
||||||
// 处理回调
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -266,6 +260,8 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
|
|||||||
//修补autozie fragment item大小不一致
|
//修补autozie fragment item大小不一致
|
||||||
Logger.e(TAG, "onResume: ");
|
Logger.e(TAG, "onResume: ");
|
||||||
mViewModel.getOutsideApp();
|
mViewModel.getOutsideApp();
|
||||||
|
mViewModel.getHotseatApp();
|
||||||
|
|
||||||
if (wallpaperScrollHelper != null) {
|
if (wallpaperScrollHelper != null) {
|
||||||
wallpaperScrollHelper.onResume();
|
wallpaperScrollHelper.onResume();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
package com.ttstd.dialer.adapter;
|
package com.ttstd.dialer.adapter;
|
||||||
|
|
||||||
|
import android.content.ClipData;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Point;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.view.DragEvent;
|
||||||
|
import android.view.HapticFeedbackConstants;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -21,7 +27,6 @@ import com.tencent.mmkv.MMKV;
|
|||||||
import com.ttstd.dialer.R;
|
import com.ttstd.dialer.R;
|
||||||
import com.ttstd.dialer.config.CommonConfig;
|
import com.ttstd.dialer.config.CommonConfig;
|
||||||
import com.ttstd.dialer.db.app.AppInfo;
|
import com.ttstd.dialer.db.app.AppInfo;
|
||||||
import com.ttstd.dialer.fragment.dialog.shortcut.ShortcutDialogFagment;
|
|
||||||
import com.ttstd.dialer.utils.ApkUtils;
|
import com.ttstd.dialer.utils.ApkUtils;
|
||||||
import com.ttstd.dialer.utils.Logger;
|
import com.ttstd.dialer.utils.Logger;
|
||||||
import com.ttstd.iconloader.IconCacheManager;
|
import com.ttstd.iconloader.IconCacheManager;
|
||||||
@@ -51,16 +56,6 @@ public class AppGridAdapter extends BaseAdapter implements LoaderManager.LoaderC
|
|||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface ShortcutCallback {
|
|
||||||
void setAppInside(AppInfo appInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ShortcutCallback mShortcutCallback;
|
|
||||||
|
|
||||||
public void setShortcutCallback(ShortcutCallback shortcutCallback) {
|
|
||||||
mShortcutCallback = shortcutCallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getCount() {
|
public int getCount() {
|
||||||
return mAppInfos == null ? 0 : mAppInfos.size();
|
return mAppInfos == null ? 0 : mAppInfos.size();
|
||||||
@@ -109,23 +104,54 @@ public class AppGridAdapter extends BaseAdapter implements LoaderManager.LoaderC
|
|||||||
holder.root.setOnLongClickListener(new View.OnLongClickListener() {
|
holder.root.setOnLongClickListener(new View.OnLongClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onLongClick(View v) {
|
public boolean onLongClick(View v) {
|
||||||
ShortcutDialogFagment shortcutDialogFagment = new ShortcutDialogFagment(appInfo);
|
if (!v.isAttachedToWindow()) {
|
||||||
shortcutDialogFagment.setTitile("温馨提示");
|
Logger.e(TAG, "View is not attached to window");
|
||||||
shortcutDialogFagment.setTips("是否把这个应用移到“更多应用”里?");
|
return false;
|
||||||
shortcutDialogFagment.setOnClickListener(new ShortcutDialogFagment.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onPositiveClick() {
|
|
||||||
if (mShortcutCallback != null)
|
|
||||||
mShortcutCallback.setAppInside(appInfo);
|
|
||||||
shortcutDialogFagment.dismiss();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
// 添加触感反馈
|
||||||
public void onNegativeClick() {
|
v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||||
shortcutDialogFagment.dismiss();
|
|
||||||
|
ClipData data = ClipData.newPlainText("appInfo", appInfo.getPackageName());
|
||||||
|
View.DragShadowBuilder shadowBuilder = new LauncherDragShadowBuilder(v);
|
||||||
|
|
||||||
|
// 使用 post 确保在当前事件处理完成后再开始拖拽,提高稳定性并减少崩溃
|
||||||
|
v.post(() -> {
|
||||||
|
boolean started = false;
|
||||||
|
try {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
started = v.startDragAndDrop(data, shadowBuilder, appInfo, View.DRAG_FLAG_OPAQUE);
|
||||||
|
} else {
|
||||||
|
started = v.startDrag(data, shadowBuilder, appInfo, 0);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.e(TAG, "startDragAndDrop error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.e(TAG, "startDragAndDrop result: " + started);
|
||||||
|
if (started) {
|
||||||
|
v.setAlpha(0.5f);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
shortcutDialogFagment.show(mContext.getSupportFragmentManager(), "ShortcutDialogFagment");
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
holder.root.setOnDragListener(new View.OnDragListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onDrag(View v, DragEvent event) {
|
||||||
|
switch (event.getAction()) {
|
||||||
|
case DragEvent.ACTION_DRAG_STARTED:
|
||||||
|
if (event.getLocalState() == appInfo) {
|
||||||
|
v.setAlpha(0.5f);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
case DragEvent.ACTION_DRAG_ENDED:
|
||||||
|
v.setAlpha(1.0f);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -133,6 +159,34 @@ public class AppGridAdapter extends BaseAdapter implements LoaderManager.LoaderC
|
|||||||
return convertView;
|
return convertView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class LauncherDragShadowBuilder extends View.DragShadowBuilder {
|
||||||
|
public LauncherDragShadowBuilder(View view) {
|
||||||
|
super(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProvideShadowMetrics(Point outShadowSize, Point outShadowTouchPoint) {
|
||||||
|
float scale = 1.2f;
|
||||||
|
int width = (int) (getView().getWidth() * scale);
|
||||||
|
int height = (int) (getView().getHeight() * scale);
|
||||||
|
outShadowSize.set(width, height);
|
||||||
|
outShadowTouchPoint.set(width / 2, height / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDrawShadow(Canvas canvas) {
|
||||||
|
float scale = 1.2f;
|
||||||
|
canvas.save();
|
||||||
|
canvas.scale(scale, scale);
|
||||||
|
// 确保在绘制阴影时视图是不透明的
|
||||||
|
float originalAlpha = getView().getAlpha();
|
||||||
|
getView().setAlpha(1.0f);
|
||||||
|
getView().draw(canvas);
|
||||||
|
getView().setAlpha(originalAlpha);
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ public class ContactInfoAdapter extends RecyclerView.Adapter<ContactInfoAdapter.
|
|||||||
void onItemMove(int fromPosition, int toPosition);
|
void onItemMove(int fromPosition, int toPosition);
|
||||||
|
|
||||||
void onItemRemoved(int position);
|
void onItemRemoved(int position);
|
||||||
|
|
||||||
|
void onItemMoveFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface ClickListener {
|
public interface ClickListener {
|
||||||
@@ -145,6 +147,12 @@ public class ContactInfoAdapter extends RecyclerView.Adapter<ContactInfoAdapter.
|
|||||||
notifyItemRemoved(position);
|
notifyItemRemoved(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onItemMoveFinished() {
|
||||||
|
if (mItemMoveCallback != null) {
|
||||||
|
mItemMoveCallback.onItemMoveFinished();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class ContactInfoHolder extends RecyclerView.ViewHolder {
|
public class ContactInfoHolder extends RecyclerView.ViewHolder {
|
||||||
ConstraintLayout root, clOperation;
|
ConstraintLayout root, clOperation;
|
||||||
NiceImageView nv_avatar;
|
NiceImageView nv_avatar;
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
package com.ttstd.dialer.adapter;
|
package com.ttstd.dialer.adapter;
|
||||||
|
|
||||||
|
import android.content.ClipData;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Point;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.view.DragEvent;
|
||||||
|
import android.view.HapticFeedbackConstants;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -21,7 +27,6 @@ import com.tencent.mmkv.MMKV;
|
|||||||
import com.ttstd.dialer.R;
|
import com.ttstd.dialer.R;
|
||||||
import com.ttstd.dialer.config.CommonConfig;
|
import com.ttstd.dialer.config.CommonConfig;
|
||||||
import com.ttstd.dialer.db.app.AppInfo;
|
import com.ttstd.dialer.db.app.AppInfo;
|
||||||
import com.ttstd.dialer.fragment.dialog.shortcut.ShortcutDialogFagment;
|
|
||||||
import com.ttstd.dialer.utils.ApkUtils;
|
import com.ttstd.dialer.utils.ApkUtils;
|
||||||
import com.ttstd.dialer.utils.Logger;
|
import com.ttstd.dialer.utils.Logger;
|
||||||
import com.ttstd.iconloader.IconCacheManager;
|
import com.ttstd.iconloader.IconCacheManager;
|
||||||
@@ -55,16 +60,6 @@ public class MoreAppAdapter extends RecyclerView.Adapter<MoreAppAdapter.AppHolde
|
|||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface ShortcutCallback {
|
|
||||||
void setAppOutside(AppInfo appInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ShortcutCallback mShortcutCallback;
|
|
||||||
|
|
||||||
public void setShortcutCallback(ShortcutCallback shortcutCallback) {
|
|
||||||
mShortcutCallback = shortcutCallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
@@ -73,9 +68,11 @@ public class MoreAppAdapter extends RecyclerView.Adapter<MoreAppAdapter.AppHolde
|
|||||||
return new AppHolder(LayoutInflater.from(mContext).inflate(R.layout.item_more_app, parent, false));
|
return new AppHolder(LayoutInflater.from(mContext).inflate(R.layout.item_more_app, parent, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull @NotNull AppHolder holder, int position) {
|
public void onBindViewHolder(@NonNull @NotNull AppHolder holder, int position) {
|
||||||
AppInfo appInfo = mAppInfos.get(position);
|
AppInfo appInfo = mAppInfos.get(position);
|
||||||
|
holder.root.setAlpha(1.0f);
|
||||||
Drawable drawable = mIconCacheManager.getIcon(appInfo.getComponentName().flattenToShortString());
|
Drawable drawable = mIconCacheManager.getIcon(appInfo.getComponentName().flattenToShortString());
|
||||||
if (drawable != null) {
|
if (drawable != null) {
|
||||||
holder.iv_icon.setImageDrawable(drawable);
|
holder.iv_icon.setImageDrawable(drawable);
|
||||||
@@ -92,29 +89,89 @@ public class MoreAppAdapter extends RecyclerView.Adapter<MoreAppAdapter.AppHolde
|
|||||||
holder.root.setOnLongClickListener(new View.OnLongClickListener() {
|
holder.root.setOnLongClickListener(new View.OnLongClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onLongClick(View v) {
|
public boolean onLongClick(View v) {
|
||||||
ShortcutDialogFagment shortcutDialogFagment = new ShortcutDialogFagment(appInfo);
|
if (!v.isAttachedToWindow()) {
|
||||||
shortcutDialogFagment.setTitile("温馨提示");
|
Logger.e(TAG, "View is not attached to window");
|
||||||
shortcutDialogFagment.setTips("是否将应用放在桌面显示");
|
return false;
|
||||||
|
|
||||||
shortcutDialogFagment.setOnClickListener(new ShortcutDialogFagment.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onPositiveClick() {
|
|
||||||
if (mShortcutCallback != null)
|
|
||||||
mShortcutCallback.setAppOutside(appInfo);
|
|
||||||
shortcutDialogFagment.dismiss();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
// 添加触感反馈
|
||||||
public void onNegativeClick() {
|
v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||||
shortcutDialogFagment.dismiss();
|
|
||||||
|
ClipData data = ClipData.newPlainText("appInfo", appInfo.getPackageName());
|
||||||
|
View.DragShadowBuilder shadowBuilder = new LauncherDragShadowBuilder(v);
|
||||||
|
|
||||||
|
// 使用 post 确保在当前事件处理完成后再开始拖拽,提高稳定性并减少崩溃
|
||||||
|
v.post(() -> {
|
||||||
|
boolean started = false;
|
||||||
|
try {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
// 修复:移除 DRAG_FLAG_GLOBAL 以解决某些设备上的 IllegalStateException
|
||||||
|
// 增加 DRAG_FLAG_OPAQUE 提升性能
|
||||||
|
started = v.startDragAndDrop(data, shadowBuilder, appInfo, View.DRAG_FLAG_OPAQUE);
|
||||||
|
} else {
|
||||||
|
started = v.startDrag(data, shadowBuilder, appInfo, 0);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.e(TAG, "startDragAndDrop error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.e(TAG, "startDragAndDrop result: " + started);
|
||||||
|
if (started) {
|
||||||
|
v.setAlpha(0.5f);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
shortcutDialogFagment.show(mContext.getSupportFragmentManager(), "ShortcutDialogFagment");
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
holder.root.setOnDragListener(new View.OnDragListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onDrag(View v, DragEvent event) {
|
||||||
|
switch (event.getAction()) {
|
||||||
|
case DragEvent.ACTION_DRAG_STARTED:
|
||||||
|
if (event.getLocalState() == appInfo) {
|
||||||
|
v.setAlpha(0.5f);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
case DragEvent.ACTION_DRAG_ENDED:
|
||||||
|
v.setAlpha(1.0f);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class LauncherDragShadowBuilder extends View.DragShadowBuilder {
|
||||||
|
public LauncherDragShadowBuilder(View view) {
|
||||||
|
super(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProvideShadowMetrics(Point outShadowSize, Point outShadowTouchPoint) {
|
||||||
|
float scale = 1.2f;
|
||||||
|
int width = (int) (getView().getWidth() * scale);
|
||||||
|
int height = (int) (getView().getHeight() * scale);
|
||||||
|
outShadowSize.set(width, height);
|
||||||
|
outShadowTouchPoint.set(width / 2, height / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDrawShadow(Canvas canvas) {
|
||||||
|
float scale = 1.2f;
|
||||||
|
canvas.save();
|
||||||
|
canvas.scale(scale, scale);
|
||||||
|
// 确保在绘制阴影时视图是不透明的
|
||||||
|
float originalAlpha = getView().getAlpha();
|
||||||
|
getView().setAlpha(1.0f);
|
||||||
|
getView().draw(canvas);
|
||||||
|
getView().setAlpha(originalAlpha);
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getItemCount() {
|
||||||
return mAppInfos == null ? 0 : mAppInfos.size();
|
return mAppInfos == null ? 0 : mAppInfos.size();
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ public interface ContactDao {
|
|||||||
@Update
|
@Update
|
||||||
int update(ContactInfo contactInfo);
|
int update(ContactInfo contactInfo);
|
||||||
|
|
||||||
|
@Update
|
||||||
|
void update(List<ContactInfo> contactInfos);
|
||||||
|
|
||||||
@Delete
|
@Delete
|
||||||
int delete(ContactInfo contactInfo);
|
int delete(ContactInfo contactInfo);
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,10 @@ public class ContactRepository {
|
|||||||
return mContactDao.update(contactInfo);
|
return mContactDao.update(contactInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void update(List<ContactInfo> contactInfos) {
|
||||||
|
mContactDao.update(contactInfos);
|
||||||
|
}
|
||||||
|
|
||||||
// 删除联系人
|
// 删除联系人
|
||||||
public int delete(ContactInfo contactInfo) {
|
public int delete(ContactInfo contactInfo) {
|
||||||
return mContactDao.delete(contactInfo);
|
return mContactDao.delete(contactInfo);
|
||||||
|
|||||||
@@ -56,14 +56,14 @@ public class AppManager {
|
|||||||
private static final int TOTAL_STEPS_NORMAL = 3; // 正常流程总步骤
|
private static final int TOTAL_STEPS_NORMAL = 3; // 正常流程总步骤
|
||||||
private static final int TOTAL_STEPS_FIRST = 2; // 首次初始化总步骤
|
private static final int TOTAL_STEPS_FIRST = 2; // 首次初始化总步骤
|
||||||
|
|
||||||
private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
|
MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
private static volatile AppManager INSTANCE;
|
private static volatile AppManager INSTANCE;
|
||||||
private Context mContext;
|
Context mContext;
|
||||||
private PackageManager mPackageManager;
|
PackageManager mPackageManager;
|
||||||
|
|
||||||
private AppRepository mAppRepository;
|
AppRepository mAppRepository;
|
||||||
|
|
||||||
private Set<ProgressCallback> mProgressCallbacks = new CopyOnWriteArraySet<>();
|
private Set<ProgressCallback> mProgressCallbacks = new CopyOnWriteArraySet<>();
|
||||||
|
|
||||||
@@ -142,48 +142,218 @@ public class AppManager {
|
|||||||
|
|
||||||
// 通知进度回调
|
// 通知进度回调
|
||||||
private void notifyProgress(String step, int current, int total) {
|
private void notifyProgress(String step, int current, int total) {
|
||||||
mProgressCallbacks.forEach(callback -> callback.onProgress(step, current, total));
|
for (ProgressCallback callback : mProgressCallbacks) {
|
||||||
|
callback.onProgress(step, current, total);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 通知完成回调
|
// 通知完成回调
|
||||||
private void notifyCompleted(boolean success, String message) {
|
private void notifyCompleted(boolean success, String message) {
|
||||||
mProgressCallbacks.forEach(callback -> callback.onCompleted(success, message));
|
for (ProgressCallback callback : mProgressCallbacks) {
|
||||||
|
callback.onCompleted(success, message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 异步执行应用列表处理流程
|
// 异步执行应用列表处理流程
|
||||||
public void executeAppListProcessing() {
|
public void executeAppListProcessing() {
|
||||||
CompletableFuture
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
.supplyAsync(this::getAllDesktopSortApps, ASYNC_EXECUTOR)
|
V26Helper.executeAppListProcessing(this);
|
||||||
.thenComposeAsync(desktopApps -> {
|
|
||||||
// 根据应用列表是否为空,选择不同处理流程
|
|
||||||
if (desktopApps.isEmpty()) {
|
|
||||||
// 首次初始化流程:首次数据加载 -> 更新位置
|
|
||||||
return processFirstTimeApps()
|
|
||||||
.thenComposeAsync(unused -> {
|
|
||||||
notifyProgress(STEP_UPDATE_POSITION, 2, TOTAL_STEPS_FIRST);
|
|
||||||
return updatePosition();
|
|
||||||
}, ASYNC_EXECUTOR);
|
|
||||||
} else {
|
} else {
|
||||||
// 正常流程:移除未安装 -> 添加新应用 -> 更新位置
|
executeAppListProcessingLegacy();
|
||||||
return processUninstalledApps(desktopApps)
|
}
|
||||||
.thenComposeAsync(unused -> {
|
}
|
||||||
notifyProgress(STEP_ADD_NEW_APPS, 2, TOTAL_STEPS_NORMAL);
|
|
||||||
return processNewApps();
|
private void executeAppListProcessingLegacy() {
|
||||||
}, ASYNC_EXECUTOR)
|
ASYNC_EXECUTOR.execute(new Runnable() {
|
||||||
.thenComposeAsync(unused -> {
|
@Override
|
||||||
notifyProgress(STEP_UPDATE_POSITION, 3, TOTAL_STEPS_NORMAL);
|
public void run() {
|
||||||
return updatePosition();
|
try {
|
||||||
}, ASYNC_EXECUTOR);
|
List<AppInfo> desktopApps = getAllDesktopSortApps();
|
||||||
|
if (desktopApps.isEmpty()) {
|
||||||
|
processFirstTimeAppsSync();
|
||||||
|
notifyProgress(STEP_UPDATE_POSITION, 2, TOTAL_STEPS_FIRST);
|
||||||
|
updatePositionSync();
|
||||||
|
} else {
|
||||||
|
processUninstalledAppsSync(desktopApps);
|
||||||
|
notifyProgress(STEP_ADD_NEW_APPS, 2, TOTAL_STEPS_NORMAL);
|
||||||
|
processNewAppsSync();
|
||||||
|
notifyProgress(STEP_UPDATE_POSITION, 3, TOTAL_STEPS_NORMAL);
|
||||||
|
updatePositionSync();
|
||||||
|
}
|
||||||
|
notifyCompleted(true, "应用列表处理完成");
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.e(TAG, "异步处理失败", e);
|
||||||
|
notifyCompleted(false, "处理失败: " + e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, ASYNC_EXECUTOR)
|
|
||||||
.thenRun(() -> notifyCompleted(true, "应用列表处理完成"))
|
|
||||||
.exceptionally(throwable -> {
|
|
||||||
Logger.e(TAG, "异步处理失败", throwable);
|
|
||||||
notifyCompleted(false, "处理失败: " + throwable.getMessage());
|
|
||||||
return null;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 同步版本的处理逻辑,用于低版本适配
|
||||||
|
private void processFirstTimeAppsSync() {
|
||||||
|
notifyProgress(STEP_FIRST_INIT, 1, TOTAL_STEPS_FIRST);
|
||||||
|
Logger.i(TAG, "进入首次应用数据初始化流程(Legacy)");
|
||||||
|
|
||||||
|
try {
|
||||||
|
List<ResolveInfo> allLauncherApps = ApkUtils.getAllLauncherResolveInfo(mContext);
|
||||||
|
if (allLauncherApps.isEmpty()) {
|
||||||
|
Logger.w(TAG, "未获取到任何可启动应用,无法完成首次初始化");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<AppInfo> allFirstApps = new ArrayList<>();
|
||||||
|
for (ResolveInfo ri : allLauncherApps) {
|
||||||
|
if (ri != null) {
|
||||||
|
AppInfo appInfo = resolveInfoToDesktopApp(ri);
|
||||||
|
if (appInfo != null) {
|
||||||
|
allFirstApps.add(appInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(allFirstApps, new Comparator<AppInfo>() {
|
||||||
|
@Override
|
||||||
|
public int compare(AppInfo o1, AppInfo o2) {
|
||||||
|
boolean isSystem1 = ApkUtils.isSystemApp(mContext, o1.getPackageName());
|
||||||
|
boolean isSystem2 = ApkUtils.isSystemApp(mContext, o2.getPackageName());
|
||||||
|
if (isSystem1 != isSystem2) {
|
||||||
|
return isSystem2 ? 1 : -1;
|
||||||
|
}
|
||||||
|
return Collator.getInstance(Locale.CHINESE).compare(o1.getLabel(), o2.getLabel());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
int hotseatCounter = 0;
|
||||||
|
for (int i = 0; i < allFirstApps.size(); i++) {
|
||||||
|
AppInfo appInfo = allFirstApps.get(i);
|
||||||
|
if (DEFAULT_APP_PACKAGES.contains(appInfo.getPackageName())) {
|
||||||
|
appInfo.setOutside(1);
|
||||||
|
} else {
|
||||||
|
appInfo.setOutside(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEFAULT_DIALER_PACKAGE.contains(appInfo.getPackageName())) {
|
||||||
|
appInfo.setHotseat(1);
|
||||||
|
appInfo.setHotseatPosition(hotseatCounter++);
|
||||||
|
} else if (DEFAULT_CONTACT_PACKAGE.contains(appInfo.getPackageName())) {
|
||||||
|
appInfo.setHotseat(1);
|
||||||
|
appInfo.setHotseatPosition(hotseatCounter++);
|
||||||
|
} else if (DEFAULT_MESSAGE_PACKAGE.contains(appInfo.getPackageName())) {
|
||||||
|
appInfo.setHotseat(1);
|
||||||
|
appInfo.setHotseatPosition(hotseatCounter++);
|
||||||
|
} else if (DEFAULT_SETTINGS_PACKAGE.contains(appInfo.getPackageName())) {
|
||||||
|
appInfo.setHotseat(1);
|
||||||
|
appInfo.setHotseatPosition(hotseatCounter++);
|
||||||
|
} else {
|
||||||
|
appInfo.setHotseat(0);
|
||||||
|
appInfo.setHotseatPosition(0);
|
||||||
|
}
|
||||||
|
appInfo.setPosition(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (AppInfo app : allFirstApps) {
|
||||||
|
try {
|
||||||
|
mAppRepository.insert(app);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.e(TAG, "首次初始化插入应用失败: " + app.getPackageName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Logger.i(TAG, "首次初始化完成(Legacy),插入默认应用: " + allFirstApps.size() + " 个");
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.e(TAG, "首次应用数据初始化失败", e);
|
||||||
|
throw new RuntimeException("首次初始化失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processUninstalledAppsSync(List<AppInfo> appInfos) {
|
||||||
|
notifyProgress(STEP_REMOVE_UNINSTALLED, 1, TOTAL_STEPS_NORMAL);
|
||||||
|
if (appInfos.isEmpty()) {
|
||||||
|
Logger.w(TAG, "桌面应用列表为空,跳过删除处理");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (AppInfo app : appInfos) {
|
||||||
|
if (app != null && !ApkUtils.isInstalled(mContext, app.getPackageName())) {
|
||||||
|
try {
|
||||||
|
int id = mAppRepository.getIdByPackageAndClass(app.getPackageName(), app.getClassName());
|
||||||
|
if (id > 0) {
|
||||||
|
mAppRepository.deleteById(id);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.e(TAG, "处理未安装应用失败: " + app.getPackageName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processNewAppsSync() {
|
||||||
|
try {
|
||||||
|
List<ResolveInfo> resolveInfos = ApkUtils.getAllLauncherResolveInfo(mContext);
|
||||||
|
if (resolveInfos.isEmpty()) {
|
||||||
|
Logger.w(TAG, "未获取到启动器应用列表,跳过新应用处理");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<AppInfo> newApps = new ArrayList<>();
|
||||||
|
for (ResolveInfo ri : resolveInfos) {
|
||||||
|
if (ri != null) {
|
||||||
|
AppInfo app = resolveInfoToDesktopApp(ri);
|
||||||
|
if (app != null && ApkUtils.isInstalled(mContext, app.getPackageName()) && !isAppExists(app)) {
|
||||||
|
newApps.add(app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(newApps, new Comparator<AppInfo>() {
|
||||||
|
@Override
|
||||||
|
public int compare(AppInfo o1, AppInfo o2) {
|
||||||
|
return Collator.getInstance(Locale.CHINESE).compare(o1.getLabel(), o2.getLabel());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!newApps.isEmpty()) {
|
||||||
|
int totalCount = mAppRepository.getTotalCount();
|
||||||
|
for (int i = 0; i < newApps.size(); i++) {
|
||||||
|
AppInfo app = newApps.get(i);
|
||||||
|
app.setPosition(totalCount + i);
|
||||||
|
try {
|
||||||
|
mAppRepository.insert(app);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.e(TAG, "插入新应用失败: " + app.getPackageName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.e(TAG, "处理新应用时发生异常", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePositionSync() {
|
||||||
|
try {
|
||||||
|
List<AppInfo> appInfos = getAllDesktopSortApps();
|
||||||
|
if (appInfos.isEmpty()) return;
|
||||||
|
|
||||||
|
Collections.sort(appInfos, new Comparator<AppInfo>() {
|
||||||
|
@Override
|
||||||
|
public int compare(AppInfo o1, AppInfo o2) {
|
||||||
|
return Integer.compare(o1.getPosition(), o2.getPosition());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (int i = 0; i < appInfos.size(); i++) {
|
||||||
|
AppInfo app = appInfos.get(i);
|
||||||
|
app.setPosition(i);
|
||||||
|
try {
|
||||||
|
mAppRepository.update(app);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.e(TAG, "更新应用位置失败: " + app.getPackageName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.e(TAG, "更新应用位置时发生异常", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 第一步:获取所有桌面应用(空值安全处理)
|
// 第一步:获取所有桌面应用(空值安全处理)
|
||||||
private List<AppInfo> getAllDesktopSortApps() {
|
private List<AppInfo> getAllDesktopSortApps() {
|
||||||
try {
|
try {
|
||||||
@@ -195,15 +365,279 @@ public class AppManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 首次初始化应用数据(针对首次获取为空的场景)
|
// 工具方法:ResolveInfo转换为DesktopSortApp
|
||||||
private CompletableFuture<Void> processFirstTimeApps() {
|
AppInfo resolveInfoToDesktopApp(ResolveInfo resolveInfo) {
|
||||||
return CompletableFuture.runAsync(() -> {
|
ComponentName component = new ComponentName(
|
||||||
notifyProgress(STEP_FIRST_INIT, 1, TOTAL_STEPS_FIRST);
|
resolveInfo.activityInfo.packageName,
|
||||||
Logger.i(TAG, "进入首次应用数据初始化流程");
|
resolveInfo.activityInfo.name
|
||||||
|
);
|
||||||
|
return new AppInfo(mContext, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 工具方法:检查应用是否已存在
|
||||||
|
boolean isAppExists(AppInfo app) {
|
||||||
|
try {
|
||||||
|
return mAppRepository.checkAppInfoExists(app.getPackageName(), app.getClassName()) > 0;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.e(TAG, "检查应用存在性失败: " + app.getPackageName(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 工具方法:获取应用排序器(统一排序逻辑)
|
||||||
|
Comparator<AppInfo> getAppComparator() {
|
||||||
|
return new Comparator<AppInfo>() {
|
||||||
|
@Override
|
||||||
|
public int compare(AppInfo o1, AppInfo o2) {
|
||||||
|
return Collator.getInstance(Locale.CHINESE).compare(o1.getLabel(), o2.getLabel());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateApp(String packageName) {
|
||||||
|
boolean isInstalled = ApkUtils.isInstalled(mContext, packageName);
|
||||||
|
|
||||||
|
if (isInstalled) {
|
||||||
|
Logger.i(TAG, "应用已安装: " + packageName + ", 开始更新数据库");
|
||||||
|
|
||||||
|
List<ResolveInfo> resolveInfos = ApkUtils.getResolveInfoByPackageName(mContext, packageName);
|
||||||
|
|
||||||
|
if (!resolveInfos.isEmpty()) {
|
||||||
|
for (ResolveInfo ri : resolveInfos) {
|
||||||
|
AppInfo app = resolveInfoToDesktopApp(ri);
|
||||||
|
if (app != null) {
|
||||||
|
try {
|
||||||
|
int existingId = mAppRepository.getIdByPackageAndClass(app.getPackageName(), app.getClassName());
|
||||||
|
|
||||||
|
if (existingId <= 0) {
|
||||||
|
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 {
|
try {
|
||||||
// 获取所有可启动应用
|
List<AppInfo> allApps = mAppRepository.getAllApp();
|
||||||
List<ResolveInfo> allLauncherApps = ApkUtils.getAllLauncherResolveInfo(mContext);
|
if (allApps != null) {
|
||||||
|
List<Integer> idsToDelete = new ArrayList<>();
|
||||||
|
for (AppInfo app : allApps) {
|
||||||
|
if (packageName.equals(app.getPackageName())) {
|
||||||
|
int id = app.getId();
|
||||||
|
if (id > 0) {
|
||||||
|
idsToDelete.add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!idsToDelete.isEmpty()) {
|
||||||
|
for (Integer id : idsToDelete) {
|
||||||
|
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() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
V26Helper.refreshAllApps(this);
|
||||||
|
} else {
|
||||||
|
ASYNC_EXECUTOR.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
List<AppInfo> allApps = getAllDesktopSortApps();
|
||||||
|
if (allApps.isEmpty()) {
|
||||||
|
processFirstTimeAppsSync();
|
||||||
|
} else {
|
||||||
|
processUninstalledAppsSync(allApps);
|
||||||
|
processNewAppsSync();
|
||||||
|
}
|
||||||
|
updatePositionSync();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDefaultAppList() {
|
||||||
|
List<ResolveInfo> resolveInfos = ApkUtils.getAllLauncherResolveInfo(mContext);
|
||||||
|
|
||||||
|
List<AppInfo> defaultApps = new LinkedList<>();
|
||||||
|
for (ResolveInfo ri : resolveInfos) {
|
||||||
|
if (DEFAULT_APP_PACKAGES.contains(ri.activityInfo.packageName)) {
|
||||||
|
ComponentName component = new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name);
|
||||||
|
defaultApps.add(new AppInfo(mContext, component));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(defaultApps, new Comparator<AppInfo>() {
|
||||||
|
@Override
|
||||||
|
public int compare(AppInfo o1, AppInfo o2) {
|
||||||
|
return Collator.getInstance(Locale.CHINESE).compare(o1.getLabel(), o2.getLabel());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Collections.sort(defaultApps, new Comparator<AppInfo>() {
|
||||||
|
@Override
|
||||||
|
public int compare(AppInfo a, AppInfo b) {
|
||||||
|
boolean isSystemA = ApkUtils.isSystemApp(mContext, a.getPackageName());
|
||||||
|
boolean isSystemB = ApkUtils.isSystemApp(mContext, b.getPackageName());
|
||||||
|
if (isSystemA != isSystemB) {
|
||||||
|
return isSystemB ? 1 : -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (int i = 0; i < defaultApps.size(); i++) {
|
||||||
|
defaultApps.get(i).setPosition(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GsonUtils.toJSONString(defaultApps);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Set<String> WHITE_SYSTEM_PACKAGES = new HashSet<String>() {{
|
||||||
|
// this.add("com.android.luancher3");
|
||||||
|
}};
|
||||||
|
|
||||||
|
public List<ApkInstalledInfo> getApkInstallInfos() {
|
||||||
|
List<PackageInfo> installedApps = mPackageManager.getInstalledPackages(0);
|
||||||
|
List<ApkInstalledInfo> apkInstalledInfos = new ArrayList<>();
|
||||||
|
|
||||||
|
for (PackageInfo packageInfo : installedApps) {
|
||||||
|
String packageName = packageInfo.packageName;
|
||||||
|
if (!ApkUtils.isSystemApp(mContext, packageName) || WHITE_SYSTEM_PACKAGES.contains(packageName)) {
|
||||||
|
ApkInstalledInfo apkInstalledInfo = new ApkInstalledInfo();
|
||||||
|
apkInstalledInfo.setPackageName(packageInfo.packageName);
|
||||||
|
apkInstalledInfo.setAppName(packageInfo.applicationInfo.loadLabel(mPackageManager).toString());
|
||||||
|
apkInstalledInfo.setVersionName(packageInfo.versionName);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
apkInstalledInfo.setVersionCode(packageInfo.getLongVersionCode());
|
||||||
|
} else {
|
||||||
|
apkInstalledInfo.setVersionCode(packageInfo.versionCode);
|
||||||
|
}
|
||||||
|
apkInstalledInfo.setInstallTime(packageInfo.firstInstallTime);
|
||||||
|
apkInstalledInfo.setLastUpdateTime(packageInfo.lastUpdateTime);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
StorageStatsHelper.fillStats(mContext, packageInfo, apkInstalledInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
File publicSourceFile = new File(packageInfo.applicationInfo.publicSourceDir);
|
||||||
|
if (publicSourceFile.exists()) {
|
||||||
|
apkInstalledInfo.setMd5(HashUtils.getFileMd5(publicSourceFile));
|
||||||
|
}
|
||||||
|
apkInstalledInfo.setSystemApp(ApkUtils.isSystemApp(mContext, packageInfo.packageName));
|
||||||
|
apkInstalledInfos.add(apkInstalledInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return apkInstalledInfos;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class StorageStatsHelper {
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
static void fillStats(Context context, PackageInfo packageInfo, ApkInstalledInfo apkInstalledInfo) {
|
||||||
|
try {
|
||||||
|
StorageStatsManager ssm = (StorageStatsManager) context.getSystemService(Context.STORAGE_STATS_SERVICE);
|
||||||
|
if (ssm != null) {
|
||||||
|
StorageStats stats = ssm.queryStatsForPackage(
|
||||||
|
packageInfo.applicationInfo.storageUuid,
|
||||||
|
packageInfo.packageName,
|
||||||
|
android.os.Process.myUserHandle()
|
||||||
|
);
|
||||||
|
apkInstalledInfo.setApkSize(stats.getAppBytes());
|
||||||
|
apkInstalledInfo.setDataSize(stats.getDataBytes());
|
||||||
|
apkInstalledInfo.setCacheSize(stats.getCacheBytes());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "getApkInstallInfos stats error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class V26Helper {
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
static void executeAppListProcessing(final AppManager manager) {
|
||||||
|
CompletableFuture.supplyAsync(new java.util.function.Supplier<List<AppInfo>>() {
|
||||||
|
@Override
|
||||||
|
public List<AppInfo> get() {
|
||||||
|
return manager.getAllDesktopSortApps();
|
||||||
|
}
|
||||||
|
}, ASYNC_EXECUTOR).thenComposeAsync(new java.util.function.Function<List<AppInfo>, CompletableFuture<Void>>() {
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Void> apply(final List<AppInfo> desktopApps) {
|
||||||
|
if (desktopApps.isEmpty()) {
|
||||||
|
return processFirstTimeApps(manager).thenComposeAsync(new java.util.function.Function<Void, CompletableFuture<Void>>() {
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Void> apply(Void unused) {
|
||||||
|
manager.notifyProgress(STEP_UPDATE_POSITION, 2, TOTAL_STEPS_FIRST);
|
||||||
|
return updatePosition(manager);
|
||||||
|
}
|
||||||
|
}, ASYNC_EXECUTOR);
|
||||||
|
} else {
|
||||||
|
return processUninstalledApps(manager, desktopApps).thenComposeAsync(new java.util.function.Function<Void, CompletableFuture<Void>>() {
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Void> apply(Void unused) {
|
||||||
|
manager.notifyProgress(STEP_ADD_NEW_APPS, 2, TOTAL_STEPS_NORMAL);
|
||||||
|
return processNewApps(manager);
|
||||||
|
}
|
||||||
|
}, ASYNC_EXECUTOR).thenComposeAsync(new java.util.function.Function<Void, CompletableFuture<Void>>() {
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Void> apply(Void unused) {
|
||||||
|
manager.notifyProgress(STEP_UPDATE_POSITION, 3, TOTAL_STEPS_NORMAL);
|
||||||
|
return updatePosition(manager);
|
||||||
|
}
|
||||||
|
}, ASYNC_EXECUTOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, ASYNC_EXECUTOR).thenRun(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
manager.notifyCompleted(true, "应用列表处理完成");
|
||||||
|
}
|
||||||
|
}).exceptionally(new java.util.function.Function<Throwable, Void>() {
|
||||||
|
@Override
|
||||||
|
public Void apply(Throwable throwable) {
|
||||||
|
Logger.e(TAG, "异步处理失败", throwable);
|
||||||
|
manager.notifyCompleted(false, "处理失败: " + throwable.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
static CompletableFuture<Void> processFirstTimeApps(final AppManager manager) {
|
||||||
|
return CompletableFuture.runAsync(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
manager.notifyProgress(STEP_FIRST_INIT, 1, TOTAL_STEPS_FIRST);
|
||||||
|
Logger.i(TAG, "进入首次应用数据初始化流程");
|
||||||
|
try {
|
||||||
|
List<ResolveInfo> allLauncherApps = ApkUtils.getAllLauncherResolveInfo(manager.mContext);
|
||||||
if (allLauncherApps.isEmpty()) {
|
if (allLauncherApps.isEmpty()) {
|
||||||
Logger.w(TAG, "未获取到任何可启动应用,无法完成首次初始化");
|
Logger.w(TAG, "未获取到任何可启动应用,无法完成首次初始化");
|
||||||
return;
|
return;
|
||||||
@@ -211,17 +645,14 @@ public class AppManager {
|
|||||||
|
|
||||||
List<AppInfo> allFirstApps = allLauncherApps.stream()
|
List<AppInfo> allFirstApps = allLauncherApps.stream()
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.map(this::resolveInfoToDesktopApp)
|
.map(manager::resolveInfoToDesktopApp)
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.sorted((o1, o2) -> Boolean.compare(ApkUtils.isSystemApp(mContext, o2.getPackageName()), ApkUtils.isSystemApp(mContext, o1.getPackageName())))
|
.sorted((o1, o2) -> Boolean.compare(ApkUtils.isSystemApp(manager.mContext, o2.getPackageName()), ApkUtils.isSystemApp(manager.mContext, o1.getPackageName())))
|
||||||
.sorted(getAppComparator())
|
.sorted(manager.getAppComparator())
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
final int[] hotseatCounter = {0};
|
final int[] hotseatCounter = {0};
|
||||||
IntStream.range(0, allFirstApps.size())
|
IntStream.range(0, allFirstApps.size()).forEach(index -> {
|
||||||
.forEach(new IntConsumer() {
|
|
||||||
@Override
|
|
||||||
public void accept(int index) {
|
|
||||||
AppInfo appInfo = allFirstApps.get(index);
|
AppInfo appInfo = allFirstApps.get(index);
|
||||||
if (DEFAULT_APP_PACKAGES.contains(appInfo.getPackageName())) {
|
if (DEFAULT_APP_PACKAGES.contains(appInfo.getPackageName())) {
|
||||||
appInfo.setOutside(1);
|
appInfo.setOutside(1);
|
||||||
@@ -245,45 +676,40 @@ public class AppManager {
|
|||||||
appInfo.setHotseat(0);
|
appInfo.setHotseat(0);
|
||||||
appInfo.setHotseatPosition(0);
|
appInfo.setHotseatPosition(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
appInfo.setPosition(index);
|
appInfo.setPosition(index);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 批量插入首次数据
|
|
||||||
allFirstApps.forEach(app -> {
|
allFirstApps.forEach(app -> {
|
||||||
try {
|
try {
|
||||||
mAppRepository.insert(app);
|
manager.mAppRepository.insert(app);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.e(TAG, "首次初始化插入应用失败: " + app.getPackageName(), e);
|
Logger.e(TAG, "首次初始化插入应用失败: " + app.getPackageName(), e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Logger.i(TAG, "首次初始化完成,插入默认应用: " + allFirstApps.size() + " 个");
|
Logger.i(TAG, "首次初始化完成,插入默认应用: " + allFirstApps.size() + " 个");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.e(TAG, "首次应用数据初始化失败", e);
|
Logger.e(TAG, "首次应用数据初始化失败", e);
|
||||||
throw new RuntimeException("首次初始化失败", e); // 抛出异常触发回调
|
throw new RuntimeException("首次初始化失败", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, ASYNC_EXECUTOR);
|
}, ASYNC_EXECUTOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理未安装的应用(删除操作)
|
@SuppressLint("NewApi")
|
||||||
private CompletableFuture<Void> processUninstalledApps(List<AppInfo> appInfos) {
|
static CompletableFuture<Void> processUninstalledApps(final AppManager manager, final List<AppInfo> appInfos) {
|
||||||
return CompletableFuture.runAsync(() -> {
|
return CompletableFuture.runAsync(new Runnable() {
|
||||||
notifyProgress(STEP_REMOVE_UNINSTALLED, 1, TOTAL_STEPS_NORMAL);
|
@Override
|
||||||
if (appInfos.isEmpty()) {
|
public void run() {
|
||||||
Logger.w(TAG, "桌面应用列表为空,跳过删除处理");
|
manager.notifyProgress(STEP_REMOVE_UNINSTALLED, 1, TOTAL_STEPS_NORMAL);
|
||||||
return;
|
if (appInfos.isEmpty()) return;
|
||||||
}
|
|
||||||
|
|
||||||
List<Integer> ids = appInfos.stream()
|
List<Integer> ids = appInfos.stream()
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.filter(app -> !ApkUtils.isInstalled(mContext, app.getPackageName()))
|
.filter(app -> !ApkUtils.isInstalled(manager.mContext, app.getPackageName()))
|
||||||
.map(app -> {
|
.map(app -> {
|
||||||
try {
|
try {
|
||||||
return mAppRepository.getIdByPackageAndClass(app.getPackageName(), app.getClassName());
|
return manager.mAppRepository.getIdByPackageAndClass(app.getPackageName(), app.getClassName());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.e(TAG, "获取应用ID失败: " + app.getPackageName(), e);
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -293,271 +719,84 @@ public class AppManager {
|
|||||||
if (!ids.isEmpty()) {
|
if (!ids.isEmpty()) {
|
||||||
ids.forEach(id -> {
|
ids.forEach(id -> {
|
||||||
try {
|
try {
|
||||||
mAppRepository.deleteById(id);
|
manager.mAppRepository.deleteById(id);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {}
|
||||||
Logger.e(TAG, "删除应用失败, ID: " + id, e);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
Logger.i(TAG, "成功删除 " + ids.size() + " 个未安装应用");
|
}
|
||||||
}
|
}
|
||||||
}, ASYNC_EXECUTOR);
|
}, ASYNC_EXECUTOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理新安装的应用(插入操作)- 非首次场景
|
@SuppressLint("NewApi")
|
||||||
private CompletableFuture<Void> processNewApps() {
|
static CompletableFuture<Void> processNewApps(final AppManager manager) {
|
||||||
return CompletableFuture.runAsync(() -> {
|
return CompletableFuture.runAsync(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
try {
|
try {
|
||||||
List<ResolveInfo> resolveInfos = ApkUtils.getAllLauncherResolveInfo(mContext);
|
List<ResolveInfo> resolveInfos = ApkUtils.getAllLauncherResolveInfo(manager.mContext);
|
||||||
if (resolveInfos.isEmpty()) {
|
if (resolveInfos.isEmpty()) return;
|
||||||
Logger.w(TAG, "未获取到启动器应用列表,跳过新应用处理");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<AppInfo> newApps = resolveInfos.stream()
|
List<AppInfo> newApps = resolveInfos.stream()
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.map(this::resolveInfoToDesktopApp)
|
.map(manager::resolveInfoToDesktopApp)
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.filter(app -> ApkUtils.isInstalled(mContext, app.getPackageName()))
|
.filter(app -> ApkUtils.isInstalled(manager.mContext, app.getPackageName()))
|
||||||
.filter(app -> !isAppExists(app)) // 过滤已存在的应用
|
.filter(manager::isAppExists)
|
||||||
.sorted(getAppComparator())
|
.sorted(manager.getAppComparator())
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
if (!newApps.isEmpty()) {
|
if (!newApps.isEmpty()) {
|
||||||
newApps.forEach(app -> app.setPosition(mAppRepository.getTotalCount()));
|
int count = manager.mAppRepository.getTotalCount();
|
||||||
newApps.forEach(app -> {
|
for (int i = 0; i < newApps.size(); i++) {
|
||||||
try {
|
AppInfo app = newApps.get(i);
|
||||||
mAppRepository.insert(app);
|
app.setPosition(count + i);
|
||||||
} catch (Exception e) {
|
manager.mAppRepository.insert(app);
|
||||||
Logger.e(TAG, "插入新应用失败: " + app.getPackageName(), e);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
Logger.i(TAG, "成功插入 " + newApps.size() + " 个新应用");
|
|
||||||
} else {
|
|
||||||
Logger.i(TAG, "没有需要插入的新应用");
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {}
|
||||||
Logger.e(TAG, "处理新应用时发生异常", e);
|
|
||||||
throw new RuntimeException("新应用处理失败", e);
|
|
||||||
}
|
}
|
||||||
}, ASYNC_EXECUTOR);
|
}, ASYNC_EXECUTOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新应用位置
|
@SuppressLint("NewApi")
|
||||||
private CompletableFuture<Void> updatePosition() {
|
static CompletableFuture<Void> updatePosition(final AppManager manager) {
|
||||||
return CompletableFuture.runAsync(() -> {
|
return CompletableFuture.runAsync(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
try {
|
try {
|
||||||
List<AppInfo> appInfos = getAllDesktopSortApps();
|
List<AppInfo> appInfos = manager.getAllDesktopSortApps();
|
||||||
if (appInfos.isEmpty()) {
|
if (appInfos.isEmpty()) return;
|
||||||
Logger.w(TAG, "无应用数据,跳过位置更新");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<AppInfo> sortedApps = appInfos.stream()
|
List<AppInfo> sortedApps = appInfos.stream()
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.sorted(Comparator.comparingInt(AppInfo::getPosition))
|
.sorted(Comparator.comparingInt(AppInfo::getPosition))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
IntStream.range(0, sortedApps.size())
|
for (int i = 0; i < sortedApps.size(); i++) {
|
||||||
.forEach(index -> {
|
AppInfo app = sortedApps.get(i);
|
||||||
AppInfo app = sortedApps.get(index);
|
app.setPosition(i);
|
||||||
app.setPosition(index);
|
manager.mAppRepository.update(app);
|
||||||
try {
|
|
||||||
mAppRepository.update(app);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Logger.e(TAG, "更新应用位置失败: " + app.getPackageName(), e);
|
|
||||||
}
|
}
|
||||||
});
|
} catch (Exception e) {}
|
||||||
Logger.i(TAG, "成功更新 " + sortedApps.size() + " 个应用的位置");
|
|
||||||
} catch (Exception e) {
|
|
||||||
Logger.e(TAG, "更新应用位置时发生异常", e);
|
|
||||||
throw new RuntimeException("位置更新失败", e);
|
|
||||||
}
|
}
|
||||||
}, ASYNC_EXECUTOR);
|
}, ASYNC_EXECUTOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 工具方法:ResolveInfo转换为DesktopSortApp
|
@SuppressLint("NewApi")
|
||||||
private AppInfo resolveInfoToDesktopApp(ResolveInfo resolveInfo) {
|
static void refreshAllApps(final AppManager manager) {
|
||||||
ComponentName component = new ComponentName(
|
CompletableFuture.runAsync(new Runnable() {
|
||||||
resolveInfo.activityInfo.packageName,
|
@Override
|
||||||
resolveInfo.activityInfo.name
|
public void run() {
|
||||||
);
|
List<AppInfo> allApps = manager.getAllDesktopSortApps();
|
||||||
return new AppInfo(mContext, component);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 工具方法:检查应用是否已存在
|
|
||||||
private boolean isAppExists(AppInfo app) {
|
|
||||||
try {
|
|
||||||
return mAppRepository.checkAppInfoExists(app.getPackageName(), app.getClassName()) > 0;
|
|
||||||
} catch (Exception e) {
|
|
||||||
Logger.e(TAG, "检查应用存在性失败: " + app.getPackageName(), e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 工具方法:获取应用排序器(统一排序逻辑)
|
|
||||||
private Comparator<AppInfo> getAppComparator() {
|
|
||||||
return Comparator.comparing(AppInfo::getLabel, Collator.getInstance(Locale.CHINESE));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateApp(String packageName) {
|
|
||||||
boolean isInstalled = ApkUtils.isInstalled(mContext, packageName);
|
|
||||||
|
|
||||||
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(() -> {
|
|
||||||
List<AppInfo> allApps = getAllDesktopSortApps();
|
|
||||||
if (allApps.isEmpty()) {
|
if (allApps.isEmpty()) {
|
||||||
processFirstTimeApps().join();
|
processFirstTimeApps(manager).join();
|
||||||
} else {
|
} else {
|
||||||
processUninstalledApps(allApps).join();
|
processUninstalledApps(manager, allApps).join();
|
||||||
processNewApps().join();
|
processNewApps(manager).join();
|
||||||
|
}
|
||||||
|
updatePosition(manager).join();
|
||||||
}
|
}
|
||||||
updatePosition().join();
|
|
||||||
}, ASYNC_EXECUTOR);
|
}, ASYNC_EXECUTOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDefaultAppList() {
|
|
||||||
List<ResolveInfo> resolveInfos = ApkUtils.getAllLauncherResolveInfo(mContext);
|
|
||||||
|
|
||||||
List<AppInfo> defaultApps = resolveInfos.stream()
|
|
||||||
.filter(ri -> DEFAULT_APP_PACKAGES.contains(ri.activityInfo.packageName))
|
|
||||||
.sorted((o1, o2) -> Collator.getInstance(Locale.CHINESE)
|
|
||||||
.compare(o1.activityInfo.loadLabel(mPackageManager), o2.activityInfo.loadLabel(mPackageManager)))
|
|
||||||
.map(ri -> new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name))
|
|
||||||
.map(component -> new AppInfo(mContext, component))
|
|
||||||
.sorted((a, b) -> Boolean.compare(
|
|
||||||
ApkUtils.isSystemApp(mContext, b.getPackageName()),
|
|
||||||
ApkUtils.isSystemApp(mContext, a.getPackageName())))
|
|
||||||
.collect(Collectors.toCollection(LinkedList::new));
|
|
||||||
|
|
||||||
IntStream.range(0, defaultApps.size())
|
|
||||||
.forEach(i -> defaultApps.get(i).setPosition(i));
|
|
||||||
|
|
||||||
return GsonUtils.toJSONString(defaultApps);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Set<String> WHITE_SYSTEM_PACKAGES = new HashSet<String>() {{
|
|
||||||
// this.add("com.android.luancher3");
|
|
||||||
}};
|
|
||||||
|
|
||||||
public List<ApkInstalledInfo> getApkInstallInfos() {
|
|
||||||
StorageStatsManager ssm = mContext.getSystemService(StorageStatsManager.class);
|
|
||||||
// 获取所有已安装的应用
|
|
||||||
List<PackageInfo> installedApps = mPackageManager.getInstalledPackages(0);
|
|
||||||
// 遍历并筛选第三方应用
|
|
||||||
List<ApkInstalledInfo> apkInstalledInfos = installedApps.stream().filter(new Predicate<PackageInfo>() {
|
|
||||||
@Override
|
|
||||||
public boolean test(PackageInfo packageInfo) {
|
|
||||||
String packageName = packageInfo.packageName;
|
|
||||||
return !ApkUtils.isSystemApp(mContext, packageName) || WHITE_SYSTEM_PACKAGES.contains(packageName);
|
|
||||||
}
|
|
||||||
}).map(new Function<PackageInfo, ApkInstalledInfo>() {
|
|
||||||
@Override
|
|
||||||
public ApkInstalledInfo apply(PackageInfo packageInfo) {
|
|
||||||
ApkInstalledInfo apkInstalledInfo = new ApkInstalledInfo();
|
|
||||||
apkInstalledInfo.setPackageName(packageInfo.packageName);
|
|
||||||
apkInstalledInfo.setAppName(packageInfo.applicationInfo.loadLabel(mPackageManager).toString());
|
|
||||||
apkInstalledInfo.setVersionName(packageInfo.versionName);
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
||||||
apkInstalledInfo.setVersionCode(packageInfo.getLongVersionCode());
|
|
||||||
} else {
|
|
||||||
apkInstalledInfo.setVersionCode(packageInfo.versionCode);
|
|
||||||
}
|
|
||||||
apkInstalledInfo.setInstallTime(packageInfo.firstInstallTime);
|
|
||||||
apkInstalledInfo.setLastUpdateTime(packageInfo.lastUpdateTime);
|
|
||||||
|
|
||||||
try {
|
|
||||||
StorageStats stats = ssm.queryStatsForPackage(
|
|
||||||
UUID.fromString(packageInfo.applicationInfo.storageUuid.toString()),
|
|
||||||
mContext.getPackageName(),
|
|
||||||
UserHandle.of(UserHandle.myUserId())
|
|
||||||
);
|
|
||||||
|
|
||||||
long appSize = stats.getAppBytes(); // 应用大小
|
|
||||||
Log.e(TAG, "apply: appSize = " + appSize);
|
|
||||||
long dataSize = stats.getDataBytes(); // 用户数据
|
|
||||||
long cacheSize = stats.getCacheBytes(); // 缓存
|
|
||||||
|
|
||||||
apkInstalledInfo.setApkSize(appSize);
|
|
||||||
apkInstalledInfo.setDataSize(dataSize);
|
|
||||||
apkInstalledInfo.setCacheSize(cacheSize);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "getApkInstallInfos: " + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.e(TAG, "apply: " + packageInfo.applicationInfo.publicSourceDir);
|
|
||||||
Log.e(TAG, "apply: publicSourceDir = " + new File(packageInfo.applicationInfo.publicSourceDir).length());
|
|
||||||
Log.e(TAG, "apply: " + packageInfo.applicationInfo.dataDir);
|
|
||||||
apkInstalledInfo.setMd5(HashUtils.getFileMd5(new File(packageInfo.applicationInfo.publicSourceDir)));
|
|
||||||
apkInstalledInfo.setSystemApp(ApkUtils.isSystemApp(mContext, packageInfo.packageName));
|
|
||||||
return apkInstalledInfo;
|
|
||||||
}
|
|
||||||
}).collect(Collectors.toList());
|
|
||||||
return apkInstalledInfos;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,4 +51,10 @@ public class ItemTouchHelperCallback extends ItemTouchHelper.Callback {
|
|||||||
public boolean isItemViewSwipeEnabled() {
|
public boolean isItemViewSwipeEnabled() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
|
||||||
|
super.clearView(recyclerView, viewHolder);
|
||||||
|
mAdapter.onItemMoveFinished();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,13 +48,31 @@
|
|||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/cl_hotseat"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/dp_100"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
android:layout_marginHorizontal="@dimen/dp_8"
|
||||||
|
android:background="@drawable/main_hotseat_background"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/bar">
|
||||||
|
|
||||||
|
<GridView
|
||||||
|
android:id="@+id/gv_app"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:numColumns="auto_fit"
|
||||||
|
android:orientation="horizontal" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/recyclerView"
|
android:id="@+id/recyclerView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/bar" />
|
app:layout_constraintTop_toBottomOf="@+id/cl_hotseat" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,8 @@
|
|||||||
android:id="@+id/cl_hotseat"
|
android:id="@+id/cl_hotseat"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/dp_100"
|
android:layout_height="@dimen/dp_100"
|
||||||
|
android:layout_marginHorizontal="@dimen/dp_8"
|
||||||
|
android:background="@drawable/main_hotseat_background"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent">
|
app:layout_constraintStart_toStartOf="parent">
|
||||||
@@ -46,8 +48,6 @@
|
|||||||
android:id="@+id/gv_app"
|
android:id="@+id/gv_app"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginHorizontal="@dimen/dp_8"
|
|
||||||
android:background="@drawable/main_hotseat_background"
|
|
||||||
android:numColumns="auto_fit"
|
android:numColumns="auto_fit"
|
||||||
android:orientation="horizontal" />
|
android:orientation="horizontal" />
|
||||||
|
|
||||||
|
|||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip
|
||||||
|
|||||||
Reference in New Issue
Block a user