Skip to content

Commit

Permalink
[FM] Implement settings for files
Browse files Browse the repository at this point in the history
1. Option to display “Files” in the app drawer
2. Option to remember last opened path and its position
3. Set home directory (set to /sdcard by default)
4. New icon for Files which will also be displayed in app drawer (if enabled)
5. New window duplicates the last window instead of opening the home directory
6. Added a “Settings” menu item in the Files activities.

Signed-off-by: Muntashir Al-Islam <muntashirakon@riseup.net>
  • Loading branch information
MuntashirAkon committed Jul 13, 2023
1 parent cfd6fb0 commit 4f9f9da
Show file tree
Hide file tree
Showing 27 changed files with 373 additions and 7 deletions.
18 changes: 18 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -950,6 +950,8 @@
android:name=".fm.FmActivity"
android:exported="true"
android:label="@string/files"
android:icon="@mipmap/ic_launcher_fm"
android:roundIcon="@mipmap/ic_launcher_fm_round"
android:taskAffinity=".fm.FmActivity">
<intent-filter android:label="@string/browse_files">
<action android:name="android.intent.action.VIEW" />
Expand All @@ -973,6 +975,22 @@
</intent-filter>
</activity>

<activity-alias
android:name=".fm.FilesActivity"
android:targetActivity=".fm.FmActivity"
android:label="@string/files"
android:icon="@mipmap/ic_launcher_fm"
android:roundIcon="@mipmap/ic_launcher_fm_round"
android:enabled="false"
android:taskAffinity=".fm.FmActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>

<activity
android:name=".fm.OpenWithActivity"
android:label="@string/file_open_with"
Expand Down
Binary file added app/src/main/ic_launcher_fm-playstore.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Parcel;
import android.os.Parcelable;
import android.provider.DocumentsContract;
Expand All @@ -17,12 +16,14 @@
import androidx.annotation.Nullable;
import androidx.core.os.BundleCompat;
import androidx.core.os.ParcelCompat;
import androidx.core.util.Pair;
import androidx.fragment.app.Fragment;

import java.util.Objects;

import io.github.muntashirakon.AppManager.BaseActivity;
import io.github.muntashirakon.AppManager.R;
import io.github.muntashirakon.AppManager.settings.Prefs;

public class FmActivity extends BaseActivity {
public static class Options implements Parcelable {
Expand Down Expand Up @@ -72,6 +73,8 @@ public void writeToParcel(@NonNull Parcel dest, int flags) {
}
}

public static final String LAUNCHER_ALIAS = "io.github.muntashirakon.AppManager.fm.FilesActivity";

public static final String EXTRA_OPTIONS = "opt";

@Override
Expand All @@ -80,7 +83,7 @@ protected void onAuthenticated(@Nullable Bundle savedInstanceState) {
setSupportActionBar(findViewById(R.id.toolbar));
findViewById(R.id.progress_linear).setVisibility(View.GONE);
Uri uri = getIntent().getData();
if (uri.getScheme() == null) {
if (uri != null && uri.getScheme() == null) {
// file:// URI can have no schema. So, fix it by adding file://
if (uri.getPath() != null && uri.getAuthority() == null) {
uri = uri.buildUpon().scheme(ContentResolver.SCHEME_FILE).build();
Expand All @@ -91,11 +94,26 @@ protected void onAuthenticated(@Nullable Bundle savedInstanceState) {
}
if (savedInstanceState == null) {
Options options = getIntent().getExtras() != null ? BundleCompat.getParcelable(getIntent().getExtras(), EXTRA_OPTIONS, Options.class) : null;
Integer position = null;
if (options == null) {
options = new Options(uri != null ? uri : Uri.fromFile(Environment.getExternalStorageDirectory()),
false, false, false);
if (uri != null) {
options = new Options(uri, false, false, false);
} else if (Prefs.FileManager.isRememberLastOpenedPath()) {
Pair<FmActivity.Options, Pair<Uri, Integer>> optionsUriPostionPair = Prefs.FileManager.getLastOpenedPath();
if (optionsUriPostionPair != null) {
options = optionsUriPostionPair.first;
if (options.isVfs) {
uri = optionsUriPostionPair.second.first;
}
position = optionsUriPostionPair.second.second;
}
}
if (options == null) {
// Use home
options = new Options(Prefs.FileManager.getHome(), false, false, false);
}
}
Fragment fragment = FmFragment.getNewInstance(options, options.isVfs ? uri : null);
Fragment fragment = FmFragment.getNewInstance(options, options.isVfs ? uri : null, position);
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.main_layout, fragment, FmFragment.TAG)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.SystemClock;
import android.provider.DocumentsContract;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.format.Formatter;
Expand Down Expand Up @@ -57,6 +58,8 @@
import java.util.concurrent.atomic.AtomicReference;

import io.github.muntashirakon.AppManager.R;
import io.github.muntashirakon.AppManager.settings.Prefs;
import io.github.muntashirakon.AppManager.settings.SettingsActivity;
import io.github.muntashirakon.AppManager.shortcut.CreateShortcutDialogFragment;
import io.github.muntashirakon.AppManager.utils.FileUtils;
import io.github.muntashirakon.AppManager.utils.StorageUtils;
Expand All @@ -83,14 +86,18 @@ public class FmFragment extends Fragment implements SearchView.OnQueryTextListen
public static final String ARG_POSITION = "pos";

@NonNull
public static FmFragment getNewInstance(@NonNull FmActivity.Options options, @Nullable Uri initUri) {
public static FmFragment getNewInstance(@NonNull FmActivity.Options options, @Nullable Uri initUri,
@Nullable Integer position) {
if (!options.isVfs && initUri != null) {
throw new IllegalArgumentException("initUri can only be set when the file system is virtual.");
}
FmFragment fragment = new FmFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_OPTIONS, options);
args.putParcelable(ARG_URI, initUri);
if (position != null) {
args.putInt(ARG_POSITION, position);
}
fragment.setArguments(args);
return fragment;
}
Expand Down Expand Up @@ -155,6 +162,9 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat
}
if (options == null) {
options = Objects.requireNonNull(BundleCompat.getParcelable(requireArguments(), ARG_OPTIONS, FmActivity.Options.class));
if (requireArguments().containsKey(ARG_POSITION)) {
scrollPosition.set(requireArguments().getInt(ARG_POSITION, RecyclerView.NO_POSITION));
}
}
mActivity = (FmActivity) requireActivity();
// Set title and subtitle
Expand Down Expand Up @@ -338,6 +348,17 @@ public void onScrolled(@NonNull androidx.recyclerview.widget.RecyclerView recycl
mModel.setOptions(options, uri);
}

@Override
public void onStop() {
super.onStop();
if (mModel != null && mRecyclerView != null) {
View v = mRecyclerView.getChildAt(0);
if (v != null) {
Prefs.FileManager.setLastOpenedPath(mModel.getOptions(), mModel.getCurrentUri(), mRecyclerView.getChildAdapterPosition(v));
}
}
}

@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
if (mModel != null) {
Expand Down Expand Up @@ -432,9 +453,16 @@ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
return true;
} else if (id == R.id.action_new_window) {
Intent intent = new Intent(mActivity, FmActivity.class);
if (!mModel.getOptions().isVfs) {
intent.setDataAndType(mModel.getCurrentUri(), DocumentsContract.Document.MIME_TYPE_DIR);
}
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
startActivity(intent);
return true;
} else if (id == R.id.action_settings) {
Intent intent = SettingsActivity.getIntent(requireContext(), "files_prefs");
startActivity(intent);
return true;
}
return super.onOptionsItemSelected(item);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import io.github.muntashirakon.io.Path;
import io.github.muntashirakon.io.fs.VirtualFileSystem;

final class FmUtils {
public final class FmUtils {
@NonNull
public static String getDisplayablePath(@NonNull Path path) {
return getDisplayablePath(path.getUri());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: GPL-3.0-or-later

package io.github.muntashirakon.AppManager.settings;

import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;

import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.SwitchPreferenceCompat;

import com.google.android.material.transition.MaterialSharedAxis;

import java.io.File;
import java.util.Objects;

import io.github.muntashirakon.AppManager.BuildConfig;
import io.github.muntashirakon.AppManager.R;
import io.github.muntashirakon.AppManager.fm.FmActivity;
import io.github.muntashirakon.AppManager.fm.FmUtils;
import io.github.muntashirakon.dialog.TextInputDialogBuilder;

public class FileManagerPreferences extends PreferenceFragment {

@Override
public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) {
setPreferencesFromResource(R.xml.preferences_file_manager, rootKey);
getPreferenceManager().setPreferenceDataStore(new SettingsDataStore());
// Display in launcher
SwitchPreferenceCompat displayInLauncherPref = Objects.requireNonNull(findPreference("fm_display_in_launcher"));
displayInLauncherPref.setChecked(Prefs.FileManager.displayInLauncher());
displayInLauncherPref.setOnPreferenceChangeListener((preference, newValue) -> {
boolean isChecked = (boolean) newValue;
int newState = isChecked ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
ComponentName componentName = new ComponentName(BuildConfig.APPLICATION_ID, FmActivity.LAUNCHER_ALIAS);
requireContext().getPackageManager().setComponentEnabledSetting(componentName, newState, PackageManager.DONT_KILL_APP);
return true;
});
// Remember last opened path
SwitchPreferenceCompat filesRememberLastPathPref = Objects.requireNonNull(findPreference("fm_remember_last_path"));
filesRememberLastPathPref.setChecked(Prefs.FileManager.isRememberLastOpenedPath());
// Set home
Preference setHomePrefs = Objects.requireNonNull(findPreference("fm_home"));
setHomePrefs.setSummary(FmUtils.getDisplayablePath(Prefs.FileManager.getHome()));
setHomePrefs.setOnPreferenceClickListener(preference -> {
new TextInputDialogBuilder(requireContext(), null)
.setTitle(R.string.pref_set_home)
.setInputText(FmUtils.getDisplayablePath(Prefs.FileManager.getHome()))
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.ok, (dialog, which, inputText, isChecked) -> {
if (TextUtils.isEmpty(inputText)) {
return;
}
String newHome = inputText.toString();
Uri uri;
if (newHome.startsWith(File.separator)) {
uri = new Uri.Builder().scheme(ContentResolver.SCHEME_FILE).path(newHome).build();
} else uri = Uri.parse(newHome);
Prefs.FileManager.setHome(uri);
setHomePrefs.setSummary(FmUtils.getDisplayablePath(uri));
})
.show();
return true;
});
}

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setEnterTransition(new MaterialSharedAxis(MaterialSharedAxis.Z, true));
setReturnTransition(new MaterialSharedAxis(MaterialSharedAxis.Z, false));
}

@Override
public int getTitle() {
return R.string.files;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,21 @@
import static io.github.muntashirakon.AppManager.backup.MetadataManager.TAR_TYPES;

import android.Manifest;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StyleRes;
import androidx.core.util.Pair;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.File;

Expand All @@ -24,6 +30,7 @@
import io.github.muntashirakon.AppManager.backup.CryptoUtils;
import io.github.muntashirakon.AppManager.compat.ManifestCompat;
import io.github.muntashirakon.AppManager.details.AppDetailsFragment;
import io.github.muntashirakon.AppManager.fm.FmActivity;
import io.github.muntashirakon.AppManager.fm.FmListOptions;
import io.github.muntashirakon.AppManager.logcat.helper.LogcatHelper;
import io.github.muntashirakon.AppManager.main.MainListOptions;
Expand All @@ -32,6 +39,7 @@
import io.github.muntashirakon.AppManager.self.SelfPermissions;
import io.github.muntashirakon.AppManager.utils.AppPref;
import io.github.muntashirakon.AppManager.utils.ArrayUtils;
import io.github.muntashirakon.AppManager.utils.ContextUtils;
import io.github.muntashirakon.AppManager.utils.FreezeUtils;
import io.github.muntashirakon.AppManager.utils.TarUtils;
import io.github.muntashirakon.io.Path;
Expand Down Expand Up @@ -265,6 +273,63 @@ public static void setOpenPgpKeyIds(@NonNull String keyIds) {
}

public static final class FileManager {
public static boolean displayInLauncher() {
ComponentName componentName = new ComponentName(BuildConfig.APPLICATION_ID, FmActivity.LAUNCHER_ALIAS);
int state = ContextUtils.getContext().getPackageManager().getComponentEnabledSetting(componentName);
return state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
}

public static Uri getHome() {
return Uri.parse(AppPref.getString(AppPref.PrefKey.PREF_FM_HOME_STR));
}

public static void setHome(@NonNull Uri uri) {
AppPref.set(AppPref.PrefKey.PREF_FM_HOME_STR, uri.toString());
}

public static boolean isRememberLastOpenedPath() {
return AppPref.getBoolean(AppPref.PrefKey.PREF_FM_REMEMBER_LAST_PATH_BOOL);
}

@Nullable
public static Pair<FmActivity.Options, Pair<Uri, Integer>> getLastOpenedPath() {
String jsonString = AppPref.getString(AppPref.PrefKey.PREF_FM_LAST_PATH_STR);
try {
JSONObject object = new JSONObject(jsonString);
if (object.has("path") && object.has("pos")) {
boolean vfs = object.has("vfs") && object.getBoolean("vfs");
FmActivity.Options options = new FmActivity.Options(Uri.parse(object.getString("path")),
vfs, false, false);
Uri initUri;
if (vfs && object.has("init")) {
initUri = Uri.parse(object.getString("init"));
} else initUri = null;
Pair<Uri, Integer> uriPositionPair = new Pair<>(initUri, object.getInt("pos"));
return new Pair<>(options, uriPositionPair);
}
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}

public static void setLastOpenedPath(@NonNull FmActivity.Options options, @NonNull Uri initUri, int position) {
try {
JSONObject object = new JSONObject();
object.put("pos", position);
if (options.isVfs) {
object.put("vfs", true);
object.put("path", options.uri.toString());
object.put("init", initUri.toString());
} else {
object.put("path", initUri.toString());
}
AppPref.set(AppPref.PrefKey.PREF_FM_LAST_PATH_STR, object.toString());
} catch (JSONException e) {
e.printStackTrace();
}
}

@FmListOptions.Options
public static int getOptions() {
return AppPref.getInt(AppPref.PrefKey.PREF_FM_OPTIONS_INT);
Expand Down
Loading

0 comments on commit 4f9f9da

Please sign in to comment.