Skip to content

Commit f9c17c0

Browse files
committed
[Profile] Refactor profile to prepare for filter-based profile
Signed-off-by: Muntashir Al-Islam <muntashirakon@riseup.net>
1 parent 296757e commit f9c17c0

24 files changed

Lines changed: 1395 additions & 905 deletions

app/src/main/AndroidManifest.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,11 @@
692692
android:label="@string/profiles" />
693693
<activity
694694
android:name=".profiles.AppsProfileActivity"
695+
android:enableOnBackInvokedCallback="true"
696+
android:exported="false" />
697+
<activity
698+
android:name=".profiles.AppsFilterProfileActivity"
699+
android:enableOnBackInvokedCallback="true"
695700
android:exported="false" />
696701
<activity
697702
android:name=".sysconfig.SysConfigActivity"

app/src/main/java/io/github/muntashirakon/AppManager/main/MainViewModel.java

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import android.content.pm.ApplicationInfo;
99
import android.content.pm.PackageInfo;
1010
import android.content.pm.PackageManager;
11-
import android.os.Handler;
1211
import android.os.RemoteException;
1312
import android.os.UserHandleHidden;
1413
import android.text.TextUtils;
@@ -57,6 +56,7 @@
5756
import io.github.muntashirakon.AppManager.misc.ListOptions;
5857
import io.github.muntashirakon.AppManager.profiles.ProfileManager;
5958
import io.github.muntashirakon.AppManager.profiles.struct.AppsProfile;
59+
import io.github.muntashirakon.AppManager.profiles.struct.BaseProfile;
6060
import io.github.muntashirakon.AppManager.settings.Prefs;
6161
import io.github.muntashirakon.AppManager.types.PackageChangeReceiver;
6262
import io.github.muntashirakon.AppManager.types.UserPackagePair;
@@ -405,37 +405,43 @@ private void filterItemsByQuery(@NonNull List<ApplicationItem> applicationItems)
405405
private void filterItemsByFlags() {
406406
synchronized (mApplicationItems) {
407407
List<ApplicationItem> candidateApplicationItems = new ArrayList<>();
408+
boolean profileApplied = false;
408409
if (mFilterProfileName != null) {
409410
String profileId = ProfileManager.getProfileIdCompat(mFilterProfileName);
410411
Path profilePath = ProfileManager.findProfilePathById(profileId);
411412
try {
412-
AppsProfile profile = AppsProfile.fromPath(profilePath);
413-
List<Integer> indexes = new ArrayList<>();
414-
for (String packageName : profile.packages) {
415-
if (ThreadUtils.isInterrupted()) {
416-
return;
413+
BaseProfile profile = BaseProfile.fromPath(profilePath);
414+
if (profile instanceof AppsProfile) {
415+
AppsProfile appsProfile = (AppsProfile) profile;
416+
List<Integer> indexes = new ArrayList<>();
417+
for (String packageName : appsProfile.packages) {
418+
if (ThreadUtils.isInterrupted()) {
419+
return;
420+
}
421+
ApplicationItem item = new ApplicationItem();
422+
item.packageName = packageName;
423+
int index = mApplicationItems.indexOf(item);
424+
if (index != -1) {
425+
indexes.add(index);
426+
}
417427
}
418-
ApplicationItem item = new ApplicationItem();
419-
item.packageName = packageName;
420-
int index = mApplicationItems.indexOf(item);
421-
if (index != -1) {
422-
indexes.add(index);
423-
}
424-
}
425-
Collections.sort(indexes);
426-
for (int index : indexes) {
427-
if (ThreadUtils.isInterrupted()) {
428-
return;
429-
}
430-
ApplicationItem item = mApplicationItems.get(index);
431-
if (isAmongSelectedUsers(item)) {
432-
candidateApplicationItems.add(item);
428+
Collections.sort(indexes);
429+
for (int index : indexes) {
430+
if (ThreadUtils.isInterrupted()) {
431+
return;
432+
}
433+
ApplicationItem item = mApplicationItems.get(index);
434+
if (isAmongSelectedUsers(item)) {
435+
candidateApplicationItems.add(item);
436+
}
433437
}
438+
profileApplied = true;
434439
}
435440
} catch (IOException | JSONException e) {
436441
e.printStackTrace();
437442
}
438-
} else {
443+
}
444+
if (!profileApplied) {
439445
for (ApplicationItem item : mApplicationItems) {
440446
if (ThreadUtils.isInterrupted()) {
441447
return;

app/src/main/java/io/github/muntashirakon/AppManager/profiles/AddToProfileDialogFragment.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public static AddToProfileDialogFragment getInstance(@NonNull String[] packages)
4949
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
5050
String[] packages = requireArguments().getStringArray(ARG_PKGS);
5151
// TODO: 16/9/23 Migrate to bottom sheet dialog and use loader before retrieving the profiles
52-
List<AppsProfile> profiles = ExUtils.requireNonNullElse(ProfileManager::getProfiles, Collections.emptyList());
52+
List<AppsProfile> profiles = ExUtils.requireNonNullElse(() -> ProfileManager.getProfiles(AppsProfile.PROFILE_TYPE_APPS), Collections.emptyList());
5353
List<CharSequence> profileNames = new ArrayList<>(profiles.size());
5454
for (AppsProfile profile : profiles) {
5555
profileNames.add(new SpannableStringBuilder(profile.name).append("\n")
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
// SPDX-License-Identifier: GPL-3.0-or-later
2+
3+
package io.github.muntashirakon.AppManager.profiles;
4+
5+
import static io.github.muntashirakon.AppManager.profiles.ProfileApplierActivity.ST_ADVANCED;
6+
import static io.github.muntashirakon.AppManager.profiles.ProfileApplierActivity.ST_SIMPLE;
7+
8+
import android.content.Intent;
9+
import android.graphics.drawable.Drawable;
10+
import android.os.Bundle;
11+
import android.text.TextUtils;
12+
import android.view.Menu;
13+
import android.view.MenuItem;
14+
import android.view.View;
15+
16+
import androidx.activity.OnBackPressedCallback;
17+
import androidx.annotation.CallSuper;
18+
import androidx.annotation.NonNull;
19+
import androidx.annotation.Nullable;
20+
import androidx.core.content.ContextCompat;
21+
import androidx.fragment.app.Fragment;
22+
import androidx.fragment.app.FragmentActivity;
23+
import androidx.lifecycle.ViewModelProvider;
24+
import androidx.viewpager2.adapter.FragmentStateAdapter;
25+
import androidx.viewpager2.widget.ViewPager2;
26+
27+
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
28+
import com.google.android.material.floatingactionbutton.FloatingActionButton;
29+
import com.google.android.material.navigation.NavigationBarView;
30+
import com.google.android.material.progressindicator.LinearProgressIndicator;
31+
32+
import java.util.Objects;
33+
34+
import io.github.muntashirakon.AppManager.BaseActivity;
35+
import io.github.muntashirakon.AppManager.R;
36+
import io.github.muntashirakon.AppManager.shortcut.CreateShortcutDialogFragment;
37+
import io.github.muntashirakon.AppManager.utils.UIUtils;
38+
import io.github.muntashirakon.dialog.SearchableSingleChoiceDialogBuilder;
39+
import io.github.muntashirakon.dialog.TextInputDialogBuilder;
40+
import io.github.muntashirakon.util.UiUtils;
41+
42+
public abstract class AppsBaseProfileActivity extends BaseActivity implements NavigationBarView.OnItemSelectedListener {
43+
protected static final String EXTRA_NEW_PROFILE_NAME = "new_prof";
44+
protected static final String EXTRA_PROFILE_ID = "prof";
45+
protected static final String EXTRA_STATE = "state";
46+
47+
private ViewPager2 mViewPager;
48+
private NavigationBarView mBottomNavigationView;
49+
private MenuItem mPrevMenuItem;
50+
private final Fragment[] mFragments = new Fragment[3];
51+
private final ViewPager2.OnPageChangeCallback mPageChangeCallback = new ViewPager2.OnPageChangeCallback() {
52+
@Override
53+
public void onPageSelected(int position) {
54+
if (mPrevMenuItem != null) {
55+
mPrevMenuItem.setChecked(false);
56+
} else {
57+
mBottomNavigationView.getMenu().getItem(0).setChecked(false);
58+
}
59+
mBottomNavigationView.getMenu().getItem(position).setChecked(true);
60+
mPrevMenuItem = mBottomNavigationView.getMenu().getItem(position);
61+
}
62+
};
63+
private final OnBackPressedCallback mOnBackPressedCallback = new OnBackPressedCallback(false) {
64+
@Override
65+
public void handleOnBackPressed() {
66+
if (model != null && model.isModified()) {
67+
new MaterialAlertDialogBuilder(AppsBaseProfileActivity.this)
68+
.setTitle(R.string.exit_confirmation)
69+
.setMessage(R.string.profile_modified_are_you_sure)
70+
.setPositiveButton(R.string.no, null)
71+
.setNegativeButton(R.string.yes, (dialog, which) -> {
72+
setEnabled(false);
73+
getOnBackPressedDispatcher().onBackPressed();
74+
})
75+
.setNeutralButton(R.string.save_and_exit, (dialog, which) -> {
76+
model.save(true);
77+
setEnabled(false);
78+
})
79+
.show();
80+
return;
81+
}
82+
setEnabled(false);
83+
getOnBackPressedDispatcher().onBackPressed();
84+
}
85+
};
86+
@Nullable
87+
protected String profileId;
88+
AppsProfileViewModel model;
89+
FloatingActionButton fab;
90+
LinearProgressIndicator progressIndicator;
91+
92+
public abstract Fragment getAppsBaseFragment();
93+
94+
public abstract void loadNewProfile(@NonNull String newProfileName, @NonNull Intent intent);
95+
96+
@CallSuper
97+
@Override
98+
protected void onAuthenticated(@Nullable Bundle savedInstanceState) {
99+
model = new ViewModelProvider(this).get(AppsProfileViewModel.class);
100+
setContentView(R.layout.activity_apps_profile);
101+
setSupportActionBar(findViewById(R.id.toolbar));
102+
getOnBackPressedDispatcher().addCallback(this, mOnBackPressedCallback);
103+
progressIndicator = findViewById(R.id.progress_linear);
104+
progressIndicator.setVisibilityAfterHide(View.GONE);
105+
fab = findViewById(R.id.floatingActionButton);
106+
UiUtils.applyWindowInsetsAsMargin(fab);
107+
if (getIntent() == null) {
108+
finish();
109+
return;
110+
}
111+
@Nullable String newProfileName = getIntent().getStringExtra(EXTRA_NEW_PROFILE_NAME);
112+
profileId = getIntent().getStringExtra(EXTRA_PROFILE_ID);
113+
if (profileId == null && newProfileName == null) {
114+
// Neither profile name/id is set
115+
finish();
116+
return;
117+
}
118+
// Load/clone profile
119+
if (newProfileName != null) {
120+
// New profile requested
121+
if (profileId != null) {
122+
// Clone profile
123+
model.loadAndCloneProfile(profileId, newProfileName);
124+
} else {
125+
// New profile
126+
loadNewProfile(newProfileName, getIntent());
127+
}
128+
} else {
129+
model.loadProfile(profileId);
130+
}
131+
mViewPager = findViewById(R.id.pager);
132+
mViewPager.setOffscreenPageLimit(2);
133+
mViewPager.registerOnPageChangeCallback(mPageChangeCallback);
134+
mViewPager.setAdapter(new ProfileFragmentPagerAdapter(this));
135+
mBottomNavigationView = findViewById(R.id.bottom_navigation);
136+
mBottomNavigationView.setOnItemSelectedListener(this);
137+
// Observers
138+
model.getProfileModifiedLiveData().observe(this, modified -> {
139+
mOnBackPressedCallback.setEnabled(modified);
140+
if (getSupportActionBar() != null) {
141+
String name = (modified ? "* " : "") + model.getProfileName();
142+
getSupportActionBar().setTitle(name);
143+
}
144+
});
145+
model.observeToast().observe(this, stringResAndIsFinish -> {
146+
UIUtils.displayShortToast(stringResAndIsFinish.first);
147+
if (stringResAndIsFinish.second) {
148+
getOnBackPressedDispatcher().onBackPressed();
149+
}
150+
});
151+
model.observeProfileLoaded().observe(this, profileName -> {
152+
setTitle(profileName);
153+
progressIndicator.hide();
154+
});
155+
}
156+
157+
@Override
158+
public boolean onCreateOptionsMenu(Menu menu) {
159+
getMenuInflater().inflate(R.menu.fragment_profile_apps_actions, menu);
160+
return super.onCreateOptionsMenu(menu);
161+
}
162+
163+
@Override
164+
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
165+
int id = item.getItemId();
166+
if (id == android.R.id.home) {
167+
getOnBackPressedDispatcher().onBackPressed();
168+
} else if (id == R.id.action_apply) {
169+
Intent intent = ProfileApplierActivity.getApplierIntent(this, model.getProfileName());
170+
startActivity(intent);
171+
} else if (id == R.id.action_save) {
172+
model.save(false);
173+
} else if (id == R.id.action_discard) {
174+
model.discard();
175+
} else if (id == R.id.action_delete) {
176+
new MaterialAlertDialogBuilder(this)
177+
.setTitle(getString(R.string.delete_filename, model.getProfileName()))
178+
.setMessage(R.string.are_you_sure)
179+
.setPositiveButton(R.string.cancel, null)
180+
.setNegativeButton(R.string.ok, (dialog, which) -> model.delete())
181+
.show();
182+
} else if (id == R.id.action_duplicate) {
183+
new TextInputDialogBuilder(this, R.string.input_profile_name)
184+
.setTitle(R.string.new_profile)
185+
.setHelperText(R.string.input_profile_name_description)
186+
.setNegativeButton(R.string.cancel, null)
187+
.setPositiveButton(R.string.go, (dialog, which, profName, isChecked) -> {
188+
if (TextUtils.isEmpty(profName)) {
189+
UIUtils.displayShortToast(R.string.failed_to_duplicate_profile);
190+
return;
191+
}
192+
progressIndicator.show();
193+
model.cloneProfile(profName.toString());
194+
})
195+
.show();
196+
} else if (id == R.id.action_shortcut) {
197+
final String[] shortcutTypesL = new String[]{
198+
getString(R.string.simple),
199+
getString(R.string.advanced)
200+
};
201+
final String[] shortcutTypes = new String[]{ST_SIMPLE, ST_ADVANCED};
202+
new SearchableSingleChoiceDialogBuilder<>(this, shortcutTypes, shortcutTypesL)
203+
.setTitle(R.string.create_shortcut)
204+
.setOnSingleChoiceClickListener((dialog, which, item1, isChecked) -> {
205+
if (!isChecked) {
206+
return;
207+
}
208+
Drawable icon = Objects.requireNonNull(ContextCompat.getDrawable(this, R.drawable.ic_launcher_foreground));
209+
ProfileShortcutInfo shortcutInfo = new ProfileShortcutInfo(model.getProfileId(),
210+
model.getProfileName(), shortcutTypes[which], shortcutTypesL[which]);
211+
shortcutInfo.setIcon(UIUtils.getBitmapFromDrawable(icon));
212+
CreateShortcutDialogFragment dialog1 = CreateShortcutDialogFragment.getInstance(shortcutInfo);
213+
dialog1.show(getSupportFragmentManager(), CreateShortcutDialogFragment.TAG);
214+
dialog.dismiss();
215+
})
216+
.show();
217+
} else return super.onOptionsItemSelected(item);
218+
return true;
219+
}
220+
221+
@Override
222+
protected void onDestroy() {
223+
if (mViewPager != null) {
224+
mViewPager.unregisterOnPageChangeCallback(mPageChangeCallback);
225+
}
226+
super.onDestroy();
227+
}
228+
229+
@Override
230+
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
231+
int itemId = item.getItemId();
232+
if (itemId == R.id.action_apps) {
233+
mViewPager.setCurrentItem(0, true);
234+
} else if (itemId == R.id.action_conf) {
235+
mViewPager.setCurrentItem(1, true);
236+
} else if (itemId == R.id.action_logs) {
237+
mViewPager.setCurrentItem(2, true);
238+
} else return false;
239+
return true;
240+
}
241+
242+
// For tab layout
243+
private class ProfileFragmentPagerAdapter extends FragmentStateAdapter {
244+
ProfileFragmentPagerAdapter(@NonNull FragmentActivity fragmentActivity) {
245+
super(fragmentActivity);
246+
}
247+
248+
@NonNull
249+
@Override
250+
public Fragment createFragment(int position) {
251+
Fragment fragment = mFragments[position];
252+
if (fragment == null) {
253+
switch (position) {
254+
case 0:
255+
return mFragments[position] = getAppsBaseFragment();
256+
case 1:
257+
return mFragments[position] = new ConfFragment();
258+
case 2:
259+
return mFragments[position] = new LogViewerFragment();
260+
}
261+
}
262+
return Objects.requireNonNull(fragment);
263+
}
264+
265+
@Override
266+
public int getItemCount() {
267+
return mFragments.length;
268+
}
269+
}
270+
}

0 commit comments

Comments
 (0)