|
| 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