diff --git a/app/src/main/java/com/ttstd/dialer/activity/alarm/add/AlarmAddActivity.java b/app/src/main/java/com/ttstd/dialer/activity/alarm/add/AlarmAddActivity.java index 8f76d13..6f15a98 100644 --- a/app/src/main/java/com/ttstd/dialer/activity/alarm/add/AlarmAddActivity.java +++ b/app/src/main/java/com/ttstd/dialer/activity/alarm/add/AlarmAddActivity.java @@ -33,9 +33,6 @@ public class AlarmAddActivity extends BaseMvvmActivity= 5) { + if (mAppGridAdapter.getItemCount() >= 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; @@ -126,7 +154,6 @@ public class AppListActivity extends BaseMvvmActivityon(LiveDataAction.ACTION_UPDATE_APPS) @@ -137,7 +164,12 @@ public class AppListActivity extends BaseMvvmActivity>() { @Override public void onChanged(List appInfos) { - mViewDataBinding.gvApp.setNumColumns(appInfos.size()); + if (appInfos != null && !appInfos.isEmpty()) { + GridLayoutManager layoutManager = (GridLayoutManager) mViewDataBinding.gvApp.getLayoutManager(); + if (layoutManager != null) { + layoutManager.setSpanCount(Math.max(1, appInfos.size())); + } + } mAppGridAdapter.setAppInfos(appInfos); } }); @@ -159,8 +191,11 @@ public class AppListActivity extends BaseMvvmActivity> mAllAppData = new MutableLiveData<>(); + + public void getAllApp() { + Observable.fromCallable(new Callable>() { + @Override + public List call() throws Exception { + return mAppRepository.getAllApp(); + } + }).compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Observer>() { + @Override + public void onSubscribe(@NonNull Disposable d) { + Logger.e("getAllApp", "onSubscribe: "); + } + + @Override + public void onNext(@NonNull List appInfos) { + Logger.e("getAllApp", "onNext: " + appInfos); + mAllAppData.setValue(appInfos); + } + + @Override + public void onError(@NonNull Throwable e) { + Logger.e("getAllApp", "onError: " + e.getMessage()); + } + + @Override + public void onComplete() { + Logger.e("getAllApp", "onComplete: "); + } + }); + } + public MutableLiveData> mHotseatAppData = new MutableLiveData<>(); public void getHotseatApp() { @@ -72,11 +108,11 @@ public class AppListViewModel extends BaseViewModel>() { - @Override - public List call() throws Exception { - return mAppRepository.getInsideApp(); - } - }).compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY)) + @Override + public List call() throws Exception { + return mAppRepository.getInsideApp(); + } + }).compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer>() { @@ -105,13 +141,32 @@ public class AppListViewModel extends BaseViewModel mAppUpdateData = new MutableLiveData<>(); - public void updateAppInfo(AppInfo appInfo){ + public void updateAppInfo(AppInfo appInfo) { Observable.fromCallable(new Callable() { - @Override - public Integer call() throws Exception { - return mAppRepository.update(appInfo); - } - }).compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY)) + @Override + public Integer call() throws Exception { + if (appInfo.getOutside() == 0) { + appInfo.setHotseat(0); + appInfo.setPosition(mAppRepository.getMaxPosition() + 1); + appInfo.setOutsidePosition(0); + appInfo.setHotseatPosition(0); + } else if (appInfo.getOutside() == 1) { + if (appInfo.getHotseat() == 0) { + appInfo.setPosition(0); + appInfo.setOutsidePosition(mAppRepository.getMaxOutsidePosition() + 1); + appInfo.setHotseatPosition(0); + } else if (appInfo.getHotseat() == 1) { + appInfo.setPosition(0); + appInfo.setOutsidePosition(0); + if (appInfo.getHotseatPosition() <= 0) { + appInfo.setHotseatPosition(mAppRepository.getMaxHotseatPosition() + 1); + } + } + } + + return mAppRepository.update(appInfo); + } + }).compose(RxLifecycle.bindUntilEvent(getLifecycle(), ActivityEvent.DESTROY)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer() { @@ -124,6 +179,7 @@ public class AppListViewModel extends BaseViewModel mAppInfos; + private boolean mLongClickEnabled = true; + + public interface ShortcutCallback { + void setAppInside(AppInfo appInfo); + } + + private ShortcutCallback mShortcutCallback; + + public void setShortcutCallback(ShortcutCallback shortcutCallback) { + mShortcutCallback = shortcutCallback; + } + + public void setLongClickEnabled(boolean longClickEnabled) { + this.mLongClickEnabled = longClickEnabled; + notifyDataSetChanged(); + } public AppGridAdapter(FragmentActivity context, LoaderManager loaderManager) { mContext = context; mLoaderManager = loaderManager; } + public AppGridAdapter(FragmentActivity context, LoaderManager loaderManager, boolean longClickEnabled) { + mContext = context; + mLoaderManager = loaderManager; + mLongClickEnabled = longClickEnabled; + } + public void setAppInfos(List appInfos) { mAppInfos = appInfos; notifyDataSetChanged(); } + @NonNull + @NotNull @Override - public int getCount() { - return mAppInfos == null ? 0 : mAppInfos.size(); + public ViewHolder onCreateViewHolder(@NonNull @NotNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_grid_app, parent, false); + return new ViewHolder(view); } @Override - public Object getItem(int position) { - return mAppInfos == null ? null : mAppInfos.get(position); - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - ViewHolder holder; - if (convertView == null) { - convertView = LayoutInflater.from(mContext).inflate(R.layout.item_grid_app, parent, false); - holder = new ViewHolder(); - holder.root = convertView.findViewById(R.id.root); - holder.iv_icon = convertView.findViewById(R.id.iv_icon); - holder.tv_app_name = convertView.findViewById(R.id.tv_app_name); - convertView.setTag(holder); - } else { - holder = (ViewHolder) convertView.getTag(); - } - + public void onBindViewHolder(@NonNull @NotNull ViewHolder holder, int position) { AppInfo appInfo = mAppInfos.get(position); Drawable drawable = mIconCacheManager.getIcon(appInfo.getComponentName().flattenToShortString()); if (drawable != null) { @@ -101,42 +105,70 @@ public class AppGridAdapter extends BaseAdapter implements LoaderManager.LoaderC } }); - holder.root.setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - if (!v.isAttachedToWindow()) { - Logger.e(TAG, "View is not attached to window"); - return false; - } + if (mLongClickEnabled) { + holder.root.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.S + || Build.VERSION.SDK_INT == Build.VERSION_CODES.S_V2 + || Build.VERSION.SDK_INT == Build.VERSION_CODES.TIRAMISU + ) { + 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(); + } - // 添加触感反馈 - v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - - 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); + @Override + public void onNegativeClick() { + shortcutDialogFagment.dismiss(); + } + }); + shortcutDialogFagment.show(mContext.getSupportFragmentManager(), "ShortcutDialogFagment"); + return false; + } else { + if (!v.isAttachedToWindow()) { + Logger.e(TAG, "View is not attached to window"); + return false; } - } catch (Exception e) { - Logger.e(TAG, "startDragAndDrop error: " + e.getMessage()); - } - Logger.e(TAG, "startDragAndDrop result: " + started); - if (started) { - v.setAlpha(0.5f); - } - }); + // 添加触感反馈 + v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - return true; - } - }); + 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); + } + }); + + return true; + } + } + }); + } else { + holder.root.setOnLongClickListener(null); + } holder.root.setOnDragListener(new View.OnDragListener() { @Override @@ -155,8 +187,11 @@ public class AppGridAdapter extends BaseAdapter implements LoaderManager.LoaderC return false; } }); + } - return convertView; + @Override + public int getItemCount() { + return mAppInfos == null ? 0 : mAppInfos.size(); } private static class LauncherDragShadowBuilder extends View.DragShadowBuilder { @@ -178,7 +213,6 @@ public class AppGridAdapter extends BaseAdapter implements LoaderManager.LoaderC float scale = 1.2f; canvas.save(); canvas.scale(scale, scale); - // 确保在绘制阴影时视图是不透明的 float originalAlpha = getView().getAlpha(); getView().setAlpha(1.0f); getView().draw(canvas); @@ -199,7 +233,7 @@ public class AppGridAdapter extends BaseAdapter implements LoaderManager.LoaderC @Override public void onLoadFinished(@NonNull @NotNull Loader loader, Drawable data) { int position = loader.getId(); - notifyDataSetChanged(); + notifyItemChanged(position); } @Override @@ -207,9 +241,16 @@ public class AppGridAdapter extends BaseAdapter implements LoaderManager.LoaderC Logger.e(TAG, "onLoaderReset: "); } - static class ViewHolder { + public static class ViewHolder extends RecyclerView.ViewHolder { ConstraintLayout root; NiceImageView iv_icon; TextView tv_app_name; + + public ViewHolder(@NonNull @NotNull View itemView) { + super(itemView); + root = itemView.findViewById(R.id.root); + iv_icon = itemView.findViewById(R.id.iv_icon); + tv_app_name = itemView.findViewById(R.id.tv_app_name); + } } } diff --git a/app/src/main/java/com/ttstd/dialer/adapter/MoreAppAdapter.java b/app/src/main/java/com/ttstd/dialer/adapter/MoreAppAdapter.java index 5d1909d..08f10ff 100644 --- a/app/src/main/java/com/ttstd/dialer/adapter/MoreAppAdapter.java +++ b/app/src/main/java/com/ttstd/dialer/adapter/MoreAppAdapter.java @@ -27,6 +27,8 @@ 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.dock.DockAppDialogFagment; +import com.ttstd.dialer.fragment.dialog.shortcut.ShortcutDialogFagment; import com.ttstd.dialer.utils.ApkUtils; import com.ttstd.dialer.utils.Logger; import com.ttstd.iconloader.IconCacheManager; @@ -44,11 +46,33 @@ public class MoreAppAdapter extends RecyclerView.Adapter selectApps) { } @@ -86,44 +110,80 @@ public class MoreAppAdapter extends RecyclerView.Adapter { - 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); + @Override + public void onDockClick() { + if (mShortcutCallback != null) + mShortcutCallback.setAppHotSeat(appInfo); + dockAppDialogFagment.dismiss(); + } + + @Override + public void onNegativeClick() { + dockAppDialogFagment.dismiss(); + } + }); + dockAppDialogFagment.show(mContext.getSupportFragmentManager(), "ShortcutDialogFagment"); + return false; + } else { + if (!v.isAttachedToWindow()) { + Logger.e(TAG, "View is not attached to window"); + return false; } - } catch (Exception e) { - Logger.e(TAG, "startDragAndDrop error: " + e.getMessage()); - } - Logger.e(TAG, "startDragAndDrop result: " + started); - if (started) { - v.setAlpha(0.5f); - } - }); + // 添加触感反馈 + v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - return true; - } - }); + 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); + } + }); + return true; + } + } + }); + } else { + holder.root.setOnLongClickListener(null); + } holder.root.setOnDragListener(new View.OnDragListener() { @Override diff --git a/app/src/main/java/com/ttstd/dialer/base/mvp/BaseMvpActivity.java b/app/src/main/java/com/ttstd/dialer/base/mvp/BaseMvpActivity.java deleted file mode 100644 index 3c6ddd5..0000000 --- a/app/src/main/java/com/ttstd/dialer/base/mvp/BaseMvpActivity.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.ttstd.dialer.base.mvp; - -import android.os.Bundle; - -import androidx.annotation.CallSuper; -import androidx.annotation.Nullable; - -import com.ttstd.dialer.base.BaseTransparentActivity; - -@Deprecated -public abstract class BaseMvpActivity extends BaseTransparentActivity { - - public BaseMvpActivity() { - super(); - } - - @Override - @CallSuper - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(getLayoutId()); - initView(); - initData(); - } - - /** - * 初始化视图 - */ - protected abstract void initView(); - - /** - * 初始化数据 - */ - protected abstract void initData(); -} diff --git a/app/src/main/java/com/ttstd/dialer/base/mvp/BasePresenter.java b/app/src/main/java/com/ttstd/dialer/base/mvp/BasePresenter.java deleted file mode 100644 index acd0917..0000000 --- a/app/src/main/java/com/ttstd/dialer/base/mvp/BasePresenter.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.ttstd.dialer.base.mvp; - -@Deprecated -public interface BasePresenter { - void attachView(V view); - - void detachView(); -} \ No newline at end of file diff --git a/app/src/main/java/com/ttstd/dialer/base/mvp/BaseView.java b/app/src/main/java/com/ttstd/dialer/base/mvp/BaseView.java deleted file mode 100644 index 2c13af6..0000000 --- a/app/src/main/java/com/ttstd/dialer/base/mvp/BaseView.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.ttstd.dialer.base.mvp; - -@Deprecated -public interface BaseView { - -} \ No newline at end of file diff --git a/app/src/main/java/com/ttstd/dialer/base/mvvm/BaseMvvmActivity.java b/app/src/main/java/com/ttstd/dialer/base/mvvm/BaseMvvmActivity.java index 4b22dee..6b9db5b 100644 --- a/app/src/main/java/com/ttstd/dialer/base/mvvm/BaseMvvmActivity.java +++ b/app/src/main/java/com/ttstd/dialer/base/mvvm/BaseMvvmActivity.java @@ -34,6 +34,12 @@ public abstract class BaseMvvmActivity baseVm = (BaseViewModel) mViewModel; + baseVm.setContext(this); + baseVm.setVDBinding(mViewDataBinding); + baseVm.setLifecycle(getLifecycleSubject()); + } } initDataBinding(); initView(); diff --git a/app/src/main/java/com/ttstd/dialer/base/mvvm/fragment/BaseMvvmDialogFragment.java b/app/src/main/java/com/ttstd/dialer/base/mvvm/fragment/BaseMvvmDialogFragment.java index fd70b9e..bca7b21 100644 --- a/app/src/main/java/com/ttstd/dialer/base/mvvm/fragment/BaseMvvmDialogFragment.java +++ b/app/src/main/java/com/ttstd/dialer/base/mvvm/fragment/BaseMvvmDialogFragment.java @@ -1,14 +1,11 @@ package com.ttstd.dialer.base.mvvm.fragment; -import android.app.Activity; import android.content.Context; import android.content.res.Configuration; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.view.inputmethod.InputMethodManager; -import android.widget.EditText; import androidx.annotation.LayoutRes; import androidx.annotation.NonNull; @@ -19,35 +16,26 @@ import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; import com.ttstd.dialer.base.BaseDialogFragment; +import com.ttstd.dialer.base.mvvm.BaseViewModel; import com.ttstd.dialer.utils.Logger; import java.lang.ref.WeakReference; import java.lang.reflect.ParameterizedType; +/** + * 优化生命周期管理,去掉冗余的 Bundle 定义 + * @param ViewModel 类型 + * @param ViewDataBinding 类型 + */ public abstract class BaseMvvmDialogFragment extends BaseDialogFragment { - protected String mTag = this.getClass().getSimpleName(); - /** - * 是否顯示了 - */ + protected final String mTag = this.getClass().getSimpleName(); + protected boolean mIsVisible; - /** - * 是否準備好了-Created - */ protected boolean mHasPrepare; - protected VM mViewModel; protected VDB mViewDataBinding; - protected Class vmClass; - protected Bundle bundle; - protected Bundle savedInstanceState; - -// protected Context context; - - /** - * 上下文 - */ private WeakReference ctx; public Context getCtx() { @@ -60,70 +48,73 @@ public abstract class BaseMvvmDialogFragment(context); } - /** - * onCreate、onResume里不能调用 - * - * @return - */ - public boolean isAttached() { - boolean flag = getCtx() != null && isAdded(); - Logger.e(" >> isAttached >>", "flag = " + flag); - return flag; + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // ViewModel 初始化移至 onCreate + try { + Class vmClass = (Class) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]; + mViewModel = new ViewModelProvider(this).get(vmClass); + if (mViewModel instanceof BaseViewModel) { + BaseViewModel baseVm = (BaseViewModel) mViewModel; + baseVm.setContext(getContext()); + baseVm.setLifecycle(getLifecycleSubject()); + } + } catch (Exception e) { + Logger.e(mTag, "ViewModel initialization failed: " + e.getMessage()); + } } @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { mViewDataBinding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false); - - try { - vmClass = (Class) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]; - mViewModel = new ViewModelProvider(this).get(vmClass); - } catch (Exception e) { - Logger.e(mTag, "Failed to create ViewModel: " + e.getMessage()); - throw new RuntimeException("Failed to create ViewModel", e); + if (mViewModel instanceof BaseViewModel) { + ((BaseViewModel) mViewModel).setVDBinding(mViewDataBinding); } - return mViewDataBinding.getRoot(); } - @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - mViewDataBinding.setLifecycleOwner(getViewLifecycleOwner()); initDataBinding(); - initView(bundle = getArguments()); - initData(this.savedInstanceState = savedInstanceState); + initView(getArguments()); + initData(savedInstanceState); - if (mIsVisible) { + mHasPrepare = true; + if (mIsVisible || getUserVisibleHint()) { + mIsVisible = true; onEnter(); } - mHasPrepare = true; } @Override public void onDestroyView() { super.onDestroyView(); mHasPrepare = false; + // 及时解绑 Binding,防止内存泄漏 + if (mViewModel instanceof BaseViewModel) { + ((BaseViewModel) mViewModel).setVDBinding(null); + } mViewDataBinding = null; - mViewModel = null; } @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); - if (mIsVisible == getUserVisibleHint()) - return; - mIsVisible = getUserVisibleHint(); + if (mIsVisible == isVisibleToUser) return; + mIsVisible = isVisibleToUser; if (mIsVisible) { - if (!mHasPrepare) - return; - onEnter(); + if (mHasPrepare) { + onEnter(); + } } else { - onExit(); + if (mHasPrepare) { + onExit(); + } } } @@ -145,36 +136,13 @@ public abstract class BaseMvvmDialogFragment ViewModel 类型 + * @param ViewDataBinding 类型 */ public abstract class BaseMvvmFragment extends BaseFragment { - protected String mTag = this.getClass().getSimpleName(); + protected final String mTag = this.getClass().getSimpleName(); + /** - * 是否顯示了 + * 是否可见(用于懒加载逻辑) */ protected boolean mIsVisible; /** - * 是否準備好了-Created + * View 是否准备完毕 */ protected boolean mHasPrepare; - protected VM mViewModel; protected VDB mViewDataBinding; - protected Class vmClass; - // -// protected Toolbar toolbar; -// protected View statusBarView; - // - protected Bundle bundle;//来自getArguments() - protected Bundle savedInstanceState; -// protected Context context; - - /** - * 上下文 - */ private WeakReference ctx; public Context getCtx() { @@ -64,104 +51,85 @@ public abstract class BaseMvvmFragment(context); } - /** - * onCreate、onResume里不能调用 - * - * @return - */ - public boolean isAttached() { - boolean flag = getCtx() != null && isAdded(); - Logger.e(" >> isAttached >>", "flag = " + flag); - return flag; + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // ViewModel 初始化 + try { + Class vmClass = (Class) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]; + mViewModel = new ViewModelProvider(this).get(vmClass); + if (mViewModel instanceof BaseViewModel) { + BaseViewModel baseVm = (BaseViewModel) mViewModel; + baseVm.setContext(getContext()); + baseVm.setLifecycle(getLifecycleSubject()); + } + } catch (Exception e) { + Logger.e(mTag, "ViewModel initialization failed: " + e.getMessage()); + } } @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - //ViewDataBinding + // ViewDataBinding 初始化 mViewDataBinding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false); - - //ViewModel - vmClass = (Class) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]; - mViewModel = new ViewModelProvider(this).get(vmClass); - // + if (mViewModel instanceof BaseViewModel) { + ((BaseViewModel) mViewModel).setVDBinding(mViewDataBinding); + } return mViewDataBinding.getRoot(); } - @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mViewDataBinding.setLifecycleOwner(getViewLifecycleOwner()); - -// if (initStatusBarToolBar()) { -// toolbar = getToolbar(); -// } - //注册eventbus -// if (getClass().isAnnotationPresent(BindEventBus.class)) -// EventBusManager.register(this); - // - -// fitsLayoutOverlap(); + initDataBinding(); - initView(bundle = getArguments()); - // - initData(this.savedInstanceState = savedInstanceState); - // - if (mIsVisible) { + // 去掉重复的 bundle 和 savedInstanceState 定义,直接从参数/方法获取 + initView(getArguments()); + initData(savedInstanceState); + + mHasPrepare = true; + // 检查初始可见性 + if (mIsVisible || getUserVisibleHint()) { + mIsVisible = true; onEnter(); } - mHasPrepare = true; - // -// LiveDataBus.get().with(ConstantUtils.DATA_BUS_LOADING_FRAGMENT, Boolean.class).observe(getActivity(), bool -> { -// L.e(" >> LiveDataBus >> DATA_BUS_LOADING_FRAGMENT: %s", bool); -// if(bool) { -// showLoading(R.string.str_please_wait); -// } else { -// hideLoading(); -// } -// }); } @Override public void onDestroyView() { super.onDestroyView(); mHasPrepare = false; + // 及时解绑 Binding,防止内存泄漏 + if (mViewModel instanceof BaseViewModel) { + ((BaseViewModel) mViewModel).setVDBinding(null); + } mViewDataBinding = null; - //移除eventbus -// if (getClass().isAnnotationPresent(BindEventBus.class)) -// EventBusManager.unregister(this); - // } @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); - if (mIsVisible == getUserVisibleHint()) - return; - mIsVisible = getUserVisibleHint(); + if (mIsVisible == isVisibleToUser) return; + mIsVisible = isVisibleToUser; if (mIsVisible) { - if (!mHasPrepare) - return; - onEnter(); + if (mHasPrepare) { + onEnter(); + } } else { - onExit(); + if (mHasPrepare) { + onExit(); + } } } @LayoutRes protected abstract int getLayoutId(); -// protected abstract Toolbar getToolbar(); - -// protected View getStatusView() { -// return null; -// } - protected abstract void initDataBinding(); protected abstract void initView(Bundle bundle); @@ -171,105 +139,15 @@ public abstract class BaseMvvmFragment> hideLoading :: isShow: %s", isShow); -// if (isShow) -// mWaitDiaLogger.dismiss(); -// } catch (Exception e) { -// e.printStackTrace(); -// } -// } - - /** - * 進入界面 - */ protected void onEnter() { - } - /** - * 離開界面 - */ protected void onExit() { - } + public boolean isAttached() { + return getCtx() != null && isAdded(); + } } diff --git a/app/src/main/java/com/ttstd/dialer/db/app/AppDao.java b/app/src/main/java/com/ttstd/dialer/db/app/AppDao.java index fbbc8e2..8a5cd3c 100644 --- a/app/src/main/java/com/ttstd/dialer/db/app/AppDao.java +++ b/app/src/main/java/com/ttstd/dialer/db/app/AppDao.java @@ -37,6 +37,9 @@ public interface AppDao { @Update int update(AppInfo appInfo); + @Update + int update(List appInfos); + @Delete int delete(AppInfo appInfo); @@ -49,10 +52,10 @@ public interface AppDao { @Query("SELECT * FROM app_list ORDER BY position ASC") List getAllApp(); - @Query("SELECT * FROM app_list WHERE outside = 1 ORDER BY position ASC") + @Query("SELECT * FROM app_list WHERE outside = 1 ORDER BY outside_position ASC") List getOutsideApp(); - @Query("SELECT * FROM app_list WHERE outside = 1 AND hotseat = 0 ORDER BY position ASC") + @Query("SELECT * FROM app_list WHERE outside = 1 AND hotseat = 0 ORDER BY outside_position ASC") List getOutsideAppWithoutHotseat(); @Query("SELECT * FROM app_list WHERE outside = 0 ORDER BY position ASC") @@ -69,4 +72,13 @@ public interface AppDao { @Query("UPDATE app_list SET hotseat = :isHotseat WHERE id = :id") int updateHotseatStatus(int id, int isHotseat); + + @Query("SELECT MAX(position) FROM app_list WHERE outside = 0") + int getMaxPosition(); + + @Query("SELECT MAX(outside_position) FROM app_list WHERE outside = 1") + int getMaxOutsidePosition(); + + @Query("SELECT MAX(hotseat_position) FROM app_list WHERE hotseat = 1") + int getMaxHotseatPosition(); } diff --git a/app/src/main/java/com/ttstd/dialer/db/app/AppDatabase.java b/app/src/main/java/com/ttstd/dialer/db/app/AppDatabase.java index c5ebdc4..508cbeb 100644 --- a/app/src/main/java/com/ttstd/dialer/db/app/AppDatabase.java +++ b/app/src/main/java/com/ttstd/dialer/db/app/AppDatabase.java @@ -8,7 +8,7 @@ import androidx.room.RoomDatabase; import java.io.File; -@Database(entities = {AppInfo.class}, version = 1, exportSchema = false) +@Database(entities = {AppInfo.class}, version = 2, exportSchema = false) public abstract class AppDatabase extends RoomDatabase { public abstract AppDao appDao(); @@ -20,8 +20,8 @@ public abstract class AppDatabase extends RoomDatabase { synchronized (AppDatabase.class) { if (INSTANCE == null) { INSTANCE = Room.databaseBuilder(context.getApplicationContext(), - AppDatabase.class, context.getExternalFilesDir("db") + File.separator + "app" + File.separator + "app_db") -// .allowMainThreadQueries() // 为了简化示例,允许主线程查询 + AppDatabase.class, context.getExternalFilesDir("db") + File.separator + "app" + File.separator + "app_db") + .fallbackToDestructiveMigration() .build(); } } diff --git a/app/src/main/java/com/ttstd/dialer/db/app/AppInfo.java b/app/src/main/java/com/ttstd/dialer/db/app/AppInfo.java index 1c4173d..eb7d05e 100644 --- a/app/src/main/java/com/ttstd/dialer/db/app/AppInfo.java +++ b/app/src/main/java/com/ttstd/dialer/db/app/AppInfo.java @@ -20,7 +20,11 @@ import com.ttstd.dialer.utils.ApkUtils; import java.io.Serializable; import java.util.Objects; -@Entity(tableName = "app_list", indices = {@Index(value = "component_name", unique = true)}) +@Entity(tableName = "app_list", indices = { + @Index(value = "component_name", unique = true), + @Index(value = {"outside", "outside_position"}), + @Index(value = {"hotseat", "hotseat_position"}) +}) @TypeConverters({ComponentNameConverter.class}) public class AppInfo implements Serializable , Parcelable { private static final long serialVersionUID = 9113517079637096245L; @@ -43,6 +47,8 @@ public class AppInfo implements Serializable , Parcelable { private int hotseat; @ColumnInfo(name = "hotseat_position") private int hotseatPosition; + @ColumnInfo(name = "outside_position") + private int outsidePosition; public AppInfo() { @@ -57,6 +63,7 @@ public class AppInfo implements Serializable , Parcelable { this.outside = 0; this.hotseat = 0; this.hotseatPosition = 0; + this.outsidePosition = 0; } public AppInfo(Context context, ComponentName componentName, int pos) { @@ -68,6 +75,7 @@ public class AppInfo implements Serializable , Parcelable { this.outside = 0; this.hotseat = 0; this.hotseatPosition = 0; + this.outsidePosition = 0; } protected AppInfo(Parcel in) { @@ -78,7 +86,9 @@ public class AppInfo implements Serializable , Parcelable { mClassName = in.readString(); mPosition = in.readInt(); outside = in.readInt(); + hotseat = in.readInt(); hotseatPosition = in.readInt(); + outsidePosition = in.readInt(); } public static final Creator CREATOR = new Creator() { @@ -165,6 +175,14 @@ public class AppInfo implements Serializable , Parcelable { this.hotseatPosition = hotseatPosition; } + public int getOutsidePosition() { + return outsidePosition; + } + + public void setOutsidePosition(int outsidePosition) { + this.outsidePosition = outsidePosition; + } + @Override public boolean equals(@Nullable @org.jetbrains.annotations.Nullable Object obj) { if (obj instanceof AppInfo) { @@ -204,5 +222,6 @@ public class AppInfo implements Serializable , Parcelable { dest.writeInt(outside); dest.writeInt(hotseat); dest.writeInt(hotseatPosition); + dest.writeInt(outsidePosition); } } diff --git a/app/src/main/java/com/ttstd/dialer/db/app/AppRepository.java b/app/src/main/java/com/ttstd/dialer/db/app/AppRepository.java index fdc822b..003159c 100644 --- a/app/src/main/java/com/ttstd/dialer/db/app/AppRepository.java +++ b/app/src/main/java/com/ttstd/dialer/db/app/AppRepository.java @@ -81,6 +81,10 @@ public class AppRepository { return mAppDao.update(appInfo); } + public int update(List appInfos) { + return mAppDao.update(appInfos); + } + // 删除APP public int delete(AppInfo appInfo) { return mAppDao.delete(appInfo); @@ -95,4 +99,16 @@ public class AppRepository { public int deleteAll() { return mAppDao.deleteAll(); } + + public int getMaxPosition() { + return mAppDao.getMaxPosition(); + } + + public int getMaxOutsidePosition() { + return mAppDao.getMaxOutsidePosition(); + } + + public int getMaxHotseatPosition() { + return mAppDao.getMaxHotseatPosition(); + } } diff --git a/app/src/main/java/com/ttstd/dialer/fragment/app/AppFragment.java b/app/src/main/java/com/ttstd/dialer/fragment/app/AppFragment.java index b7c6740..a4e44a3 100644 --- a/app/src/main/java/com/ttstd/dialer/fragment/app/AppFragment.java +++ b/app/src/main/java/com/ttstd/dialer/fragment/app/AppFragment.java @@ -66,9 +66,6 @@ public class AppFragment extends BaseMvvmFragment() { @Override public Integer call() throws Exception { + // 根据状态自动设置位置为最大值 + if (appInfo.getOutside() == 0) { + appInfo.setPosition(mAppRepository.getMaxPosition() + 1); + } else if (appInfo.getOutside() == 1) { + appInfo.setOutsidePosition(mAppRepository.getMaxOutsidePosition() + 1); + } + if (appInfo.getHotseat() == 1) { + appInfo.setOutside(1); + appInfo.setHotseatPosition(mAppRepository.getMaxHotseatPosition() + 1); + } return mAppRepository.update(appInfo); } }).compose(RxLifecycle.bindUntilEvent(getLifecycle(), FragmentEvent.DESTROY)) diff --git a/app/src/main/java/com/ttstd/dialer/fragment/contact/ContactFragment.java b/app/src/main/java/com/ttstd/dialer/fragment/contact/ContactFragment.java index 2016e74..8774f7a 100644 --- a/app/src/main/java/com/ttstd/dialer/fragment/contact/ContactFragment.java +++ b/app/src/main/java/com/ttstd/dialer/fragment/contact/ContactFragment.java @@ -37,9 +37,6 @@ public class ContactFragment extends BaseMvvmFragment { private static final String TAG = "EditDialogFragment"; - private Activity mContext; + private ContactInfo mContactInfo; public interface ContactOperationListener { @@ -51,10 +51,6 @@ public class ContactDeleteDialogFragment extends BaseMvvmDialogFragment { private static final String TAG = "EditDialogFragment"; - private Activity mContext; + private ContactInfo mContactInfo; public interface ContactOperationListener { @@ -51,10 +51,6 @@ public class EditDialogFragment extends BaseMvvmDialogFragment { + private static final String TAG = "ShortcutDialogFagment"; + + private PackageManager mPackageManager; + + private AppInfo mAppInfo; + private String mTitile; + private String mTips; + private String mNegativeText; + private String mPositiveText; + + public DockAppDialogFagment() { + } + + public DockAppDialogFagment(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 onDesktopClick(); + void onDockClick(); + + void onNegativeClick(); + } + + private OnClickListener mOnClickListener; + + public void setOnClickListener(OnClickListener onClickListener) { + mOnClickListener = onClickListener; + } + + @Override + protected int getLayoutId() { + return R.layout.fragment_dialog_dock_app; + } + + @Override + protected void initDataBinding() { + //requireContext():这是官方推荐的做法。 + // 由于 initDataBinding 和 initData 都是在 Fragment 的视图生命周期内执行的 + // (此时 Fragment 肯定已经 onAttach),使用 requireContext() 是绝对安全的,且代码最简洁。 + //移除冗余检查:既然 mPackageManager 在初始化时通过 requireContext() 赋值, + // 它在整个视图生命周期内都保证非空,因此 if (mPackageManager != null) 的判断可以完全去掉。 + mPackageManager = requireContext().getPackageManager(); + 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.tvNegative.setText("取消"); + }else { + mViewDataBinding.tvNegative.setText(mNegativeText); + } + + if (TextUtils.isEmpty(mPositiveText)){ + mViewDataBinding.tvPositive.setText("确定"); + }else { + mViewDataBinding.tvPositive.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.ivIcon.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.onDesktopClick(); + } + } + + public void onHotSeat(View view) { + if (mOnClickListener != null) { + mOnClickListener.onDockClick(); + } + } + + public void onNegative(View view) { + if (mOnClickListener != null) { + mOnClickListener.onNegativeClick(); + } + } + + } +} diff --git a/app/src/main/java/com/ttstd/dialer/fragment/dialog/dock/DockAppViewModel.java b/app/src/main/java/com/ttstd/dialer/fragment/dialog/dock/DockAppViewModel.java new file mode 100644 index 0000000..36af56a --- /dev/null +++ b/app/src/main/java/com/ttstd/dialer/fragment/dialog/dock/DockAppViewModel.java @@ -0,0 +1,10 @@ +package com.ttstd.dialer.fragment.dialog.dock; + +import com.trello.rxlifecycle4.android.FragmentEvent; +import com.ttstd.dialer.base.mvvm.BaseViewModel; +import com.ttstd.dialer.databinding.FragmentDialogDockAppBinding; +import com.ttstd.dialer.databinding.FragmentDialogShortcutBinding; + +public class DockAppViewModel extends BaseViewModel { + +} diff --git a/app/src/main/java/com/ttstd/dialer/fragment/dialog/shortcut/MoveAppDialogFagment.java b/app/src/main/java/com/ttstd/dialer/fragment/dialog/move/MoveAppDialogFagment.java similarity index 92% rename from app/src/main/java/com/ttstd/dialer/fragment/dialog/shortcut/MoveAppDialogFagment.java rename to app/src/main/java/com/ttstd/dialer/fragment/dialog/move/MoveAppDialogFagment.java index 7e789a9..fb60270 100644 --- a/app/src/main/java/com/ttstd/dialer/fragment/dialog/shortcut/MoveAppDialogFagment.java +++ b/app/src/main/java/com/ttstd/dialer/fragment/dialog/move/MoveAppDialogFagment.java @@ -1,4 +1,4 @@ -package com.ttstd.dialer.fragment.dialog.shortcut; +package com.ttstd.dialer.fragment.dialog.move; import android.app.Activity; import android.content.pm.ActivityInfo; @@ -23,10 +23,9 @@ import com.ttstd.dialer.databinding.FragmentDialogMoveAppBinding; import com.ttstd.dialer.db.app.AppInfo; import com.ttstd.dialer.utils.Logger; -public class MoveAppDialogFagment extends BaseMvvmDialogFragment { - private static final String TAG = "ShortcutDialogFagment"; +public class MoveAppDialogFagment extends BaseMvvmDialogFragment { + private static final String TAG = "MoveAppDialogFagment"; - private Activity mContext; private PackageManager mPackageManager; private AppInfo mAppInfo; @@ -77,11 +76,7 @@ public class MoveAppDialogFagment extends BaseMvvmDialogFragment{ + +} diff --git a/app/src/main/java/com/ttstd/dialer/fragment/dialog/shortcut/ShortcutDialogFagment.java b/app/src/main/java/com/ttstd/dialer/fragment/dialog/shortcut/ShortcutDialogFagment.java index 69908fe..2633065 100644 --- a/app/src/main/java/com/ttstd/dialer/fragment/dialog/shortcut/ShortcutDialogFagment.java +++ b/app/src/main/java/com/ttstd/dialer/fragment/dialog/shortcut/ShortcutDialogFagment.java @@ -1,6 +1,6 @@ package com.ttstd.dialer.fragment.dialog.shortcut; -import android.app.Activity; +import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.graphics.Color; @@ -26,7 +26,6 @@ import com.ttstd.dialer.utils.Logger; public class ShortcutDialogFagment extends BaseMvvmDialogFragment { private static final String TAG = "ShortcutDialogFagment"; - private Activity mContext; private PackageManager mPackageManager; private AppInfo mAppInfo; @@ -77,11 +76,12 @@ public class ShortcutDialogFagment extends BaseMvvmDialogFragment mProgressCallbacks = new CopyOnWriteArraySet<>(); + private final Set mExcludedPackages = new CopyOnWriteArraySet<>(); + + public void addExcludedPackage(String packageName) { + if (packageName != null) { + mExcludedPackages.add(packageName); + refreshAllApps(); + } + } + + public void removeExcludedPackage(String packageName) { + if (packageName != null) { + mExcludedPackages.remove(packageName); + refreshAllApps(); + } + } + + private boolean isExcluded(String packageName) { + return mContext.getPackageName().equals(packageName) || mExcludedPackages.contains(packageName); + } private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4)); @@ -180,6 +199,7 @@ public class AppManager { notifyProgress(STEP_UPDATE_POSITION, 3, TOTAL_STEPS_NORMAL); updatePositionSync(); } + reorderAppPositionsSync(); notifyCompleted(true, "应用列表处理完成"); } catch (Exception e) { Logger.e(TAG, "异步处理失败", e); @@ -203,7 +223,7 @@ public class AppManager { List allFirstApps = new ArrayList<>(); for (ResolveInfo ri : allLauncherApps) { - if (ri != null) { + if (ri != null && !isExcluded(ri.activityInfo.packageName)) { AppInfo appInfo = resolveInfoToDesktopApp(ri); if (appInfo != null) { allFirstApps.add(appInfo); @@ -223,10 +243,17 @@ public class AppManager { } }); + int outsideCounter = 0; for (int i = 0; i < allFirstApps.size(); i++) { AppInfo appInfo = allFirstApps.get(i); appInfo.setPosition(i); - appInfo.setOutside(DEFAULT_APP_PACKAGES.contains(appInfo.getPackageName()) ? 1 : 0); + if (DEFAULT_APP_PACKAGES.contains(appInfo.getPackageName())) { + appInfo.setOutside(1); + appInfo.setOutsidePosition(outsideCounter++); + } else { + appInfo.setOutside(0); + appInfo.setOutsidePosition(0); + } appInfo.setHotseat(0); appInfo.setHotseatPosition(0); } @@ -271,7 +298,7 @@ public class AppManager { } for (AppInfo app : appInfos) { - if (app != null && !ApkUtils.isInstalled(mContext, app.getPackageName())) { + if (app != null && (!ApkUtils.isInstalled(mContext, app.getPackageName()) || isExcluded(app.getPackageName()))) { try { int id = mAppRepository.getIdByPackageAndClass(app.getPackageName(), app.getClassName()); if (id > 0) { @@ -294,7 +321,7 @@ public class AppManager { List newApps = new ArrayList<>(); for (ResolveInfo ri : resolveInfos) { - if (ri != null) { + if (ri != null && !isExcluded(ri.activityInfo.packageName)) { AppInfo app = resolveInfoToDesktopApp(ri); if (app != null && ApkUtils.isInstalled(mContext, app.getPackageName()) && !isAppExists(app)) { newApps.add(app); @@ -338,9 +365,15 @@ public class AppManager { } }); + int outsideCounter = 0; for (int i = 0; i < appInfos.size(); i++) { AppInfo app = appInfos.get(i); app.setPosition(i); + if (app.getOutside() == 1) { + app.setOutsidePosition(outsideCounter++); + } else { + app.setOutsidePosition(0); + } try { mAppRepository.update(app); } catch (Exception e) { @@ -393,7 +426,7 @@ public class AppManager { } public void updateApp(String packageName) { - boolean isInstalled = ApkUtils.isInstalled(mContext, packageName); + boolean isInstalled = ApkUtils.isInstalled(mContext, packageName) && !isExcluded(packageName); if (isInstalled) { Logger.i(TAG, "应用已安装: " + packageName + ", 开始更新数据库"); @@ -410,6 +443,7 @@ public class AppManager { if (existingId <= 0) { app.setOutside(1); app.setPosition(mAppRepository.getTotalCount()); + app.setOutsidePosition(mAppRepository.getOutsideApp().size()); mAppRepository.insert(app); Logger.i(TAG, "新增应用信息: " + app.getPackageName()); } @@ -481,6 +515,92 @@ public class AppManager { } } + /** + * 获取数据库所有应用列表,并根据条件重新设置排序位置(从1开始) + * 1. outside == 0 用 position 排序,重设 position 从1开始 + * 2. outside == 1 用 outside_position 排序,重设 outside_position 从1开始 + * 3. hotseat == 1 用 hotseat_position 排序,重设 hotseat_position 从1开始 + */ + public void reorderAppPositions() { + ASYNC_EXECUTOR.execute(this::reorderAppPositionsSync); + } + + public void reorderAppPositionsSync() { + try { + List allApps = mAppRepository.getAllApp(); + if (allApps == null || allApps.isEmpty()) return; + + // 1. 处理 inside 应用 (outside == 0) + List insideApps = new ArrayList<>(); + for (AppInfo app : allApps) { + if (app.getOutside() == 0) { + insideApps.add(app); + } + } + Collections.sort(insideApps, new Comparator() { + @Override + public int compare(AppInfo o1, AppInfo o2) { + return Integer.compare(o1.getPosition(), o2.getPosition()); + } + }); + for (int i = 0; i < insideApps.size(); i++) { + AppInfo app = insideApps.get(i); + app.setPosition(i + 1); + app.setOutsidePosition(0); + app.setHotseatPosition(0); + } + + // 2. 处理 outside 应用 (outside == 1) + List outsideApps = new ArrayList<>(); + for (AppInfo app : allApps) { + if (app.getOutside() == 1 && app.getHotseat() == 0) { + outsideApps.add(app); + } + } + Collections.sort(outsideApps, new Comparator() { + @Override + public int compare(AppInfo o1, AppInfo o2) { + return Integer.compare(o1.getOutsidePosition(), o2.getOutsidePosition()); + } + }); + for (int i = 0; i < outsideApps.size(); i++) { + AppInfo app = outsideApps.get(i); + app.setPosition(0); + app.setOutsidePosition(i + 1); + app.setHotseatPosition(0); + } + + // 3. 处理 hotseat 应用 (hotseat == 1) + List hotseatApps = new ArrayList<>(); + for (AppInfo app : allApps) { + if (app.getOutside() == 1 && app.getHotseat() == 1) { + hotseatApps.add(app); + } + } + Collections.sort(hotseatApps, new Comparator() { + @Override + public int compare(AppInfo o1, AppInfo o2) { + return Integer.compare(o1.getHotseatPosition(), o2.getHotseatPosition()); + } + }); + for (int i = 0; i < hotseatApps.size(); i++) { + AppInfo app = hotseatApps.get(i); + app.setPosition(0); + app.setOutsidePosition(0); + app.setHotseatPosition(i + 1); + } + + // 更新数据库 + mAppRepository.update(allApps); + + Logger.i(TAG, "重置所有应用排序位置完成"); + // 通知 UI 更新 + LiveDataBus.get().send(LiveDataAction.ACTION_UPDATE_APPS, TAG); + } catch (Exception e) { + Logger.e(TAG, "重置应用排序位置失败", e); + } + } + public String getDefaultAppList() { List resolveInfos = ApkUtils.getAllLauncherResolveInfo(mContext); @@ -615,6 +735,7 @@ public class AppManager { }, ASYNC_EXECUTOR).thenRun(new Runnable() { @Override public void run() { + manager.reorderAppPositionsSync(); manager.notifyCompleted(true, "应用列表处理完成"); } }).exceptionally(new java.util.function.Function() { @@ -643,16 +764,24 @@ public class AppManager { List allFirstApps = allLauncherApps.stream() .filter(Objects::nonNull) + .filter(ri -> !manager.isExcluded(ri.activityInfo.packageName)) .map(manager::resolveInfoToDesktopApp) .filter(Objects::nonNull) .sorted((o1, o2) -> Boolean.compare(ApkUtils.isSystemApp(manager.mContext, o2.getPackageName()), ApkUtils.isSystemApp(manager.mContext, o1.getPackageName()))) .sorted(manager.getAppComparator()) .collect(Collectors.toList()); + final int[] outsideCounter = {0}; IntStream.range(0, allFirstApps.size()).forEach(index -> { AppInfo appInfo = allFirstApps.get(index); appInfo.setPosition(index); - appInfo.setOutside(DEFAULT_APP_PACKAGES.contains(appInfo.getPackageName()) ? 1 : 0); + if (DEFAULT_APP_PACKAGES.contains(appInfo.getPackageName())) { + appInfo.setOutside(1); + appInfo.setOutsidePosition(outsideCounter[0]++); + } else { + appInfo.setOutside(0); + appInfo.setOutsidePosition(0); + } appInfo.setHotseat(0); appInfo.setHotseatPosition(0); }); @@ -701,7 +830,7 @@ public class AppManager { List ids = appInfos.stream() .filter(Objects::nonNull) - .filter(app -> !ApkUtils.isInstalled(manager.mContext, app.getPackageName())) + .filter(app -> !ApkUtils.isInstalled(manager.mContext, app.getPackageName()) || manager.isExcluded(app.getPackageName())) .map(app -> { try { return manager.mAppRepository.getIdByPackageAndClass(app.getPackageName(), app.getClassName()); @@ -716,7 +845,8 @@ public class AppManager { ids.forEach(id -> { try { manager.mAppRepository.deleteById(id); - } catch (Exception e) {} + } catch (Exception e) { + } }); } } @@ -734,10 +864,11 @@ public class AppManager { List newApps = resolveInfos.stream() .filter(Objects::nonNull) + .filter(ri -> !manager.isExcluded(ri.activityInfo.packageName)) .map(manager::resolveInfoToDesktopApp) .filter(Objects::nonNull) .filter(app -> ApkUtils.isInstalled(manager.mContext, app.getPackageName())) - .filter(manager::isAppExists) + .filter(app -> !manager.isAppExists(app)) .sorted(manager.getAppComparator()) .collect(Collectors.toList()); @@ -749,7 +880,8 @@ public class AppManager { manager.mAppRepository.insert(app); } } - } catch (Exception e) {} + } catch (Exception e) { + } } }, ASYNC_EXECUTOR); } @@ -768,12 +900,19 @@ public class AppManager { .sorted(Comparator.comparingInt(AppInfo::getPosition)) .collect(Collectors.toList()); + final int[] outsideCounter = {0}; for (int i = 0; i < sortedApps.size(); i++) { AppInfo app = sortedApps.get(i); app.setPosition(i); + if (app.getOutside() == 1) { + app.setOutsidePosition(outsideCounter[0]++); + } else { + app.setOutsidePosition(0); + } manager.mAppRepository.update(app); } - } catch (Exception e) {} + } catch (Exception e) { + } } }, ASYNC_EXECUTOR); } diff --git a/app/src/main/res/layout/activity_app_list.xml b/app/src/main/res/layout/activity_app_list.xml index 6a1c248..5bb99f2 100644 --- a/app/src/main/res/layout/activity_app_list.xml +++ b/app/src/main/res/layout/activity_app_list.xml @@ -48,6 +48,14 @@ + + + + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toBottomOf="@+id/recyclerView"> - + tools:listitem="@layout/item_grid_app" /> - diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 5225a24..0c6b5c6 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -44,12 +44,11 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"> - + tools:listitem="@layout/item_grid_app" /> diff --git a/app/src/main/res/layout/fragment_dialog_dock_app.xml b/app/src/main/res/layout/fragment_dialog_dock_app.xml new file mode 100644 index 0000000..9245589 --- /dev/null +++ b/app/src/main/res/layout/fragment_dialog_dock_app.xml @@ -0,0 +1,264 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_dialog_move_app.xml b/app/src/main/res/layout/fragment_dialog_move_app.xml index e5f6b20..962e2c1 100644 --- a/app/src/main/res/layout/fragment_dialog_move_app.xml +++ b/app/src/main/res/layout/fragment_dialog_move_app.xml @@ -2,13 +2,13 @@ + tools:context=".fragment.dialog.move.MoveAppDialogFagment"> + type="com.ttstd.dialer.fragment.dialog.move.MoveAppDialogFagment.BtnClick" /> - #2196F3 - #3F51B5 - #03A9F4 + #D32F2F + #B71C1C + #FFC107 #99bd2f25 #BD2F25 - #ECECEC + #F5E6CC #000000 #000000 diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 4b8b8e6..2ac49f1 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -1,13 +1,13 @@ - 20sp + 24sp 8dp 32dp - 80dp - 18sp - 15sp + 90dp + 22sp + 18sp 2dp 16dp diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index dd087b7..4997b57 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -13,14 +13,15 @@ @color/colorPrimary @color/colorPrimaryDark @color/colorAccent + @color/default_background_color @color/default_background_color -