1
0
mirror of https://github.com/KokaKiwi/BarInsta synced 2024-11-22 22:57:29 +00:00

Add Backup and restore. Update DirectoryChooser UI.

The updated backup and restore is backward compatible with old backup files. Just have updated the default file name and extension for newer backups.
This commit is contained in:
Ammar Githam 2020-09-21 03:52:34 +09:00
parent 056604d9bb
commit 05c0937c6f
35 changed files with 1387 additions and 898 deletions

View File

@ -0,0 +1,75 @@
package awais.instagrabber.adapters;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
import java.io.File;
import awais.instagrabber.R;
import awais.instagrabber.databinding.ItemDirListBinding;
public final class DirectoryFilesAdapter extends ListAdapter<File, DirectoryFilesAdapter.ViewHolder> {
private final OnFileClickListener onFileClickListener;
private static final DiffUtil.ItemCallback<File> DIFF_CALLBACK = new DiffUtil.ItemCallback<File>() {
@Override
public boolean areItemsTheSame(@NonNull final File oldItem, @NonNull final File newItem) {
return oldItem.getAbsolutePath().equals(newItem.getAbsolutePath());
}
@Override
public boolean areContentsTheSame(@NonNull final File oldItem, @NonNull final File newItem) {
return oldItem.getAbsolutePath().equals(newItem.getAbsolutePath());
}
};
public DirectoryFilesAdapter(final OnFileClickListener onFileClickListener) {
super(DIFF_CALLBACK);
this.onFileClickListener = onFileClickListener;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
final ItemDirListBinding binding = ItemDirListBinding.inflate(inflater, parent, false);
return new ViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull final ViewHolder holder, final int position) {
final File file = getItem(position);
holder.bind(file, onFileClickListener);
}
public interface OnFileClickListener {
void onFileClick(File file);
}
static final class ViewHolder extends RecyclerView.ViewHolder {
private final ItemDirListBinding binding;
private ViewHolder(final ItemDirListBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public void bind(final File file, final OnFileClickListener onFileClickListener) {
if (file == null) return;
if (onFileClickListener != null) {
itemView.setOnClickListener(v -> onFileClickListener.onFileClick(file));
}
binding.text.setText(file.getName());
if (file.isDirectory()) {
binding.icon.setImageResource(R.drawable.ic_folder_24);
return;
}
binding.icon.setImageResource(R.drawable.ic_file_24);
}
}
}

View File

@ -1,75 +0,0 @@
package awais.instagrabber.adapters;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.utils.DataBox;
public final class SimpleAdapter<T> extends RecyclerView.Adapter<SimpleAdapter.SimpleViewHolder> {
private List<T> items;
private final LayoutInflater layoutInflater;
private final View.OnClickListener onClickListener;
private final View.OnLongClickListener longClickListener;
public SimpleAdapter(final Context context, final List<T> items, final View.OnClickListener onClickListener) {
this(context, items, onClickListener, null);
}
public SimpleAdapter(final Context context, final List<T> items, final View.OnClickListener onClickListener,
final View.OnLongClickListener longClickListener) {
this.layoutInflater = LayoutInflater.from(context);
this.items = items;
this.onClickListener = onClickListener;
this.longClickListener = longClickListener;
}
public void setItems(final List<T> items) {
this.items = items;
notifyDataSetChanged();
}
@NonNull
@Override
public SimpleViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
return new SimpleViewHolder(layoutInflater.
inflate(R.layout.item_dir_list, parent, false), onClickListener, longClickListener);
}
@Override
public void onBindViewHolder(@NonNull final SimpleViewHolder holder, final int position) {
final T item = items.get(position);
holder.itemView.setTag(item);
holder.text.setText(item.toString());
if (item instanceof DataBox.CookieModel && ((DataBox.CookieModel) item).isSelected() ||
item instanceof String && ((String) item).toLowerCase().endsWith(".zaai"))
holder.itemView.setBackgroundColor(0xF0_125687);
else
holder.itemView.setBackground(null);
}
@Override
public int getItemCount() {
return items != null ? items.size() : 0;
}
static final class SimpleViewHolder extends RecyclerView.ViewHolder {
private final TextView text;
private SimpleViewHolder(@NonNull final View itemView, final View.OnClickListener onClickListener,
final View.OnLongClickListener longClickListener) {
super(itemView);
text = itemView.findViewById(android.R.id.text1);
itemView.setOnClickListener(onClickListener);
if (longClickListener != null) itemView.setOnLongClickListener(longClickListener);
}
}
}

View File

@ -73,7 +73,14 @@ public final class DirectMessageInboxItemViewHolder extends RecyclerView.ViewHol
} }
} }
binding.tvUsername.setText(model.getThreadTitle()); binding.tvUsername.setText(model.getThreadTitle());
final DirectItemModel lastItemModel = itemModels[itemModels.length - 1]; final int length = itemModels.length;
DirectItemModel lastItemModel = null;
if (length != 0) {
lastItemModel = itemModels[length - 1];
}
if (lastItemModel == null) {
return;
}
final DirectItemType itemType = lastItemModel.getItemType(); final DirectItemType itemType = lastItemModel.getItemType();
// binding.notTextType.setVisibility(itemType != DirectItemType.TEXT ? View.VISIBLE : View.GONE); // binding.notTextType.setVisibility(itemType != DirectItemType.TEXT ? View.VISIBLE : View.GONE);
final Context context = itemView.getContext(); final Context context = itemView.getContext();

View File

@ -0,0 +1,169 @@
package awais.instagrabber.dialogs;
import android.app.Dialog;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.inputmethod.InputMethodManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentTransaction;
import java.io.File;
import java.util.Locale;
import awais.instagrabber.databinding.DialogCreateBackupBinding;
import awais.instagrabber.utils.DirectoryChooser;
import awais.instagrabber.utils.ExportImportUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.DownloadUtils.PERMS;
public class CreateBackupDialogFragment extends DialogFragment {
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
private final OnResultListener onResultListener;
private DialogCreateBackupBinding binding;
public CreateBackupDialogFragment(final OnResultListener onResultListener) {
this.onResultListener = onResultListener;
}
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
final ViewGroup container,
final Bundle savedInstanceState) {
binding = DialogCreateBackupBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
return dialog;
}
@Override
public void onStart() {
super.onStart();
final Dialog dialog = getDialog();
if (dialog == null) return;
final Window window = dialog.getWindow();
if (window == null) return;
final int height = ViewGroup.LayoutParams.WRAP_CONTENT;
final int width = (int) (Utils.displayMetrics.widthPixels * 0.8);
window.setLayout(width, height);
}
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
init();
}
private void init() {
binding.etPassword.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {}
@Override
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
binding.btnSaveTo.setEnabled(!TextUtils.isEmpty(s));
}
@Override
public void afterTextChanged(final Editable s) {}
});
final Context context = getContext();
if (context == null) {
return;
}
binding.cbPassword.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (isChecked) {
if (TextUtils.isEmpty(binding.etPassword.getText())) {
binding.btnSaveTo.setEnabled(false);
}
binding.passwordField.setVisibility(View.VISIBLE);
binding.etPassword.requestFocus();
final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm == null) return;
imm.showSoftInput(binding.etPassword, InputMethodManager.SHOW_IMPLICIT);
return;
}
binding.btnSaveTo.setEnabled(true);
binding.passwordField.setVisibility(View.GONE);
final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm == null) return;
imm.hideSoftInputFromWindow(binding.etPassword.getWindowToken(), InputMethodManager.RESULT_UNCHANGED_SHOWN);
});
binding.btnSaveTo.setOnClickListener(v -> {
if (ContextCompat.checkSelfPermission(context, PERMS[0]) == PackageManager.PERMISSION_GRANTED) {
showChooser(context);
} else {
requestPermissions(PERMS, STORAGE_PERM_REQUEST_CODE);
}
});
}
@Override
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == STORAGE_PERM_REQUEST_CODE && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
final Context context = getContext();
if (context == null) return;
showChooser(context);
}
}
private void showChooser(@NonNull final Context context) {
final String folderPath = Utils.settingsHelper.getString(FOLDER_PATH);
final Editable passwordText = binding.etPassword.getText();
final String password = binding.cbPassword.isChecked()
&& passwordText != null
&& !TextUtils.isEmpty(passwordText.toString())
? passwordText.toString().trim()
: null;
final DirectoryChooser directoryChooser = new DirectoryChooser()
.setInitialDirectory(folderPath)
.setInteractionListener(path -> {
final File file = new File(path, String.format(Locale.ENGLISH, "barinsta_%d.backup", System.currentTimeMillis()));
int flags = 0;
if (binding.cbExportFavorites.isChecked()) {
flags |= ExportImportUtils.FLAG_FAVORITES;
}
if (binding.cbExportSettings.isChecked()) {
flags |= ExportImportUtils.FLAG_SETTINGS;
}
if (binding.cbExportLogins.isChecked()) {
flags |= ExportImportUtils.FLAG_COOKIES;
}
ExportImportUtils.exportData(password, flags, file, result -> {
if (onResultListener != null) {
onResultListener.onResult(result);
}
dismiss();
}, context);
});
directoryChooser.setEnterTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
directoryChooser.setExitTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
directoryChooser.show(getChildFragmentManager(), "directory_chooser");
}
public interface OnResultListener {
void onResult(boolean result);
}
}

View File

@ -0,0 +1,180 @@
package awais.instagrabber.dialogs;
import android.app.Dialog;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.inputmethod.InputMethodManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentTransaction;
import java.io.File;
import awais.instagrabber.databinding.DialogRestoreBackupBinding;
import awais.instagrabber.utils.DirectoryChooser;
import awais.instagrabber.utils.ExportImportUtils;
import awais.instagrabber.utils.PasswordUtils.IncorrectPasswordException;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.DownloadUtils.PERMS;
public class RestoreBackupDialogFragment extends DialogFragment {
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
private final OnResultListener onResultListener;
private DialogRestoreBackupBinding binding;
private File file;
private boolean isEncrypted;
public RestoreBackupDialogFragment(final OnResultListener onResultListener) {
this.onResultListener = onResultListener;
}
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
final ViewGroup container,
final Bundle savedInstanceState) {
binding = DialogRestoreBackupBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
return dialog;
}
@Override
public void onStart() {
super.onStart();
final Dialog dialog = getDialog();
if (dialog == null) return;
final Window window = dialog.getWindow();
if (window == null) return;
final int height = ViewGroup.LayoutParams.WRAP_CONTENT;
final int width = (int) (Utils.displayMetrics.widthPixels * 0.8);
window.setLayout(width, height);
}
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
init();
}
@Override
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == STORAGE_PERM_REQUEST_CODE && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
showChooser();
}
}
private void init() {
final Context context = getContext();
if (context == null) {
return;
}
binding.btnRestore.setEnabled(false);
binding.btnRestore.setOnClickListener(v -> {
int flags = 0;
if (binding.cbFavorites.isChecked()) {
flags |= ExportImportUtils.FLAG_FAVORITES;
}
if (binding.cbSettings.isChecked()) {
flags |= ExportImportUtils.FLAG_SETTINGS;
}
if (binding.cbAccounts.isChecked()) {
flags |= ExportImportUtils.FLAG_COOKIES;
}
final Editable text = binding.etPassword.getText();
if (isEncrypted && text == null) return;
try {
ExportImportUtils.importData(
context,
flags,
file,
!isEncrypted ? null : text.toString(),
result -> {
if (onResultListener != null) {
onResultListener.onResult(result);
}
dismiss();
}
);
} catch (IncorrectPasswordException e) {
binding.passwordField.setError("Incorrect password");
}
});
binding.etPassword.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {}
@Override
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
binding.btnRestore.setEnabled(!TextUtils.isEmpty(s));
binding.passwordField.setError(null);
}
@Override
public void afterTextChanged(final Editable s) {}
});
if (ContextCompat.checkSelfPermission(context, PERMS[0]) == PackageManager.PERMISSION_GRANTED) {
showChooser();
return;
}
requestPermissions(PERMS, STORAGE_PERM_REQUEST_CODE);
}
private void showChooser() {
final String folderPath = Utils.settingsHelper.getString(FOLDER_PATH);
final Context context = getContext();
if (context == null) return;
final DirectoryChooser directoryChooser = new DirectoryChooser()
.setInitialDirectory(folderPath)
.setShowBackupFiles(true)
.setInteractionListener(file -> {
isEncrypted = ExportImportUtils.isEncrypted(file);
if (isEncrypted) {
binding.passwordGroup.setVisibility(View.VISIBLE);
binding.passwordGroup.post(() -> {
binding.etPassword.requestFocus();
binding.etPassword.post(() -> {
final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm == null) return;
imm.showSoftInput(binding.etPassword, InputMethodManager.SHOW_IMPLICIT);
});
binding.btnRestore.setEnabled(!TextUtils.isEmpty(binding.etPassword.getText()));
});
} else {
binding.passwordGroup.setVisibility(View.GONE);
binding.btnRestore.setEnabled(true);
}
this.file = file;
binding.filePath.setText(file.getAbsolutePath());
});
directoryChooser.setEnterTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
directoryChooser.setExitTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
directoryChooser.setOnCancelListener(this::dismiss);
directoryChooser.show(getChildFragmentManager(), "directory_chooser");
}
public interface OnResultListener {
void onResult(boolean result);
}
}

View File

@ -157,7 +157,7 @@ public class FavoritesFragment extends Fragment {
result.getSdProfilePic(), result.getSdProfilePic(),
model.getDateAdded() model.getDateAdded()
); );
Utils.dataBox.addFavorite(updated); Utils.dataBox.addOrUpdateFavorite(updated);
updatedList.add(i, updated); updatedList.add(i, updated);
} finally { } finally {
try { try {
@ -186,7 +186,7 @@ public class FavoritesFragment extends Fragment {
result.getSdProfilePic(), result.getSdProfilePic(),
model.getDateAdded() model.getDateAdded()
); );
Utils.dataBox.addFavorite(updated); Utils.dataBox.addOrUpdateFavorite(updated);
updatedList.add(i, updated); updatedList.add(i, updated);
} finally { } finally {
try { try {

View File

@ -369,7 +369,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
binding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24); binding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24);
message = getString(R.string.removed_from_favs); message = getString(R.string.removed_from_favs);
} else { } else {
Utils.dataBox.addFavorite(new DataBox.FavoriteModel( Utils.dataBox.addOrUpdateFavorite(new DataBox.FavoriteModel(
-1, -1,
hashtag.substring(1), hashtag.substring(1),
FavoriteType.HASHTAG, FavoriteType.HASHTAG,

View File

@ -364,7 +364,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
binding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24); binding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24);
message = getString(R.string.removed_from_favs); message = getString(R.string.removed_from_favs);
} else { } else {
Utils.dataBox.addFavorite(new DataBox.FavoriteModel( Utils.dataBox.addOrUpdateFavorite(new DataBox.FavoriteModel(
-1, -1,
locationId, locationId,
FavoriteType.LOCATION, FavoriteType.LOCATION,

View File

@ -741,7 +741,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
profileModel.getSdProfilePic(), profileModel.getSdProfilePic(),
new Date() new Date()
); );
Utils.dataBox.addFavorite(model); Utils.dataBox.addOrUpdateFavorite(model);
binding.favCb.setButtonDrawable(R.drawable.ic_star_check_24); binding.favCb.setButtonDrawable(R.drawable.ic_star_check_24);
message = getString(R.string.added_to_favs); message = getString(R.string.added_to_favs);
} else { } else {

View File

@ -0,0 +1,107 @@
package awais.instagrabber.fragments.settings;
import android.content.Context;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.google.android.material.snackbar.BaseTransientBottomBar;
import com.google.android.material.snackbar.Snackbar;
import awais.instagrabber.R;
import awais.instagrabber.dialogs.CreateBackupDialogFragment;
import awais.instagrabber.dialogs.RestoreBackupDialogFragment;
public class BackupPreferencesFragment extends BasePreferencesFragment {
@Override
void setupPreferenceScreen(final PreferenceScreen screen) {
final Context context = getContext();
if (context == null) {
return;
}
screen.addPreference(getCreatePreference(context));
screen.addPreference(getRestorePreference(context));
}
private Preference getCreatePreference(@NonNull final Context context) {
final Preference preference = new Preference(context);
preference.setTitle(R.string.create_backup);
preference.setIconSpaceReserved(false);
preference.setOnPreferenceClickListener(preference1 -> {
final FragmentManager fragmentManager = getParentFragmentManager();
final CreateBackupDialogFragment fragment = new CreateBackupDialogFragment(result -> {
final View view = getView();
if (view != null) {
Snackbar.make(view,
result ? R.string.dialog_export_success
: R.string.dialog_export_failed,
BaseTransientBottomBar.LENGTH_LONG)
.setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE)
.setAction(R.string.ok, v -> {})
.show();
return;
}
Toast.makeText(context,
result ? R.string.dialog_export_success
: R.string.dialog_export_failed,
Toast.LENGTH_LONG)
.show();
});
final FragmentTransaction ft = fragmentManager.beginTransaction();
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.add(fragment, "createBackup")
.commit();
return true;
});
return preference;
}
private Preference getRestorePreference(@NonNull final Context context) {
final Preference preference = new Preference(context);
preference.setTitle(R.string.restore_backup);
preference.setIconSpaceReserved(false);
preference.setOnPreferenceClickListener(preference1 -> {
final FragmentManager fragmentManager = getParentFragmentManager();
final RestoreBackupDialogFragment fragment = new RestoreBackupDialogFragment(result -> {
final View view = getView();
if (view != null) {
Snackbar.make(view,
result ? R.string.dialog_import_success
: R.string.dialog_import_failed,
BaseTransientBottomBar.LENGTH_LONG)
.setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE)
.setAction(R.string.ok, v -> {})
.addCallback(new BaseTransientBottomBar.BaseCallback<Snackbar>() {
@Override
public void onDismissed(final Snackbar transientBottomBar, final int event) {
recreateActivity(result);
}
})
.show();
return;
}
recreateActivity(result);
});
final FragmentTransaction ft = fragmentManager.beginTransaction();
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.add(fragment, "restoreBackup")
.commit();
return true;
});
return preference;
}
private void recreateActivity(final boolean result) {
if (!result) return;
final FragmentActivity activity = getActivity();
if (activity == null) return;
activity.recreate();
}
}

View File

@ -21,7 +21,7 @@ public abstract class BasePreferencesFragment extends PreferenceFragmentCompat i
@Override @Override
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) { public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
final PreferenceManager preferenceManager = getPreferenceManager(); final PreferenceManager preferenceManager = getPreferenceManager();
preferenceManager.setSharedPreferencesName("settings"); preferenceManager.setSharedPreferencesName(Constants.SHARED_PREFERENCES_NAME);
preferenceManager.getSharedPreferences().registerOnSharedPreferenceChangeListener(this); preferenceManager.getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
final Context context = getContext(); final Context context = getContext();
if (context == null) return; if (context == null) return;

View File

@ -20,7 +20,7 @@ import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceScreen;
import androidx.preference.PreferenceViewHolder; import androidx.preference.PreferenceViewHolder;
import java.util.ArrayList; import java.util.List;
import awais.instagrabber.BuildConfig; import awais.instagrabber.BuildConfig;
import awais.instagrabber.R; import awais.instagrabber.R;
@ -55,7 +55,7 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
accountCategory.setTitle(R.string.account); accountCategory.setTitle(R.string.account);
accountCategory.setIconSpaceReserved(false); accountCategory.setIconSpaceReserved(false);
screen.addPreference(accountCategory); screen.addPreference(accountCategory);
final ArrayList<DataBox.CookieModel> allCookies = Utils.dataBox.getAllCookies(); final List<DataBox.CookieModel> allCookies = Utils.dataBox.getAllCookies();
if (isLoggedIn) { if (isLoggedIn) {
accountCategory.setSummary(R.string.account_hint); accountCategory.setSummary(R.string.account_hint);
accountCategory.addPreference(getAccountSwitcherPreference(cookie)); accountCategory.addPreference(getAccountSwitcherPreference(cookie));
@ -120,6 +120,11 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
NavHostFragment.findNavController(this).navigate(navDirections); NavHostFragment.findNavController(this).navigate(navDirections);
return true; return true;
})); }));
screen.addPreference(getPreference(R.string.backup_and_restore, R.drawable.ic_settings_backup_restore_24, preference -> {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToBackupPreferencesFragment();
NavHostFragment.findNavController(this).navigate(navDirections);
return true;
}));
screen.addPreference(getPreference(R.string.action_about, R.drawable.ic_outline_info_24, preference1 -> { screen.addPreference(getPreference(R.string.action_about, R.drawable.ic_outline_info_24, preference1 -> {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToAboutFragment(); final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToAboutFragment();
NavHostFragment.findNavController(this).navigate(navDirections); NavHostFragment.findNavController(this).navigate(navDirections);

View File

@ -180,9 +180,9 @@ public class SettingsPreferencesFragment extends BasePreferencesFragment {
if (context == null) return null; if (context == null) return null;
return new SaveToCustomFolderPreference(context, (resultCallback) -> new DirectoryChooser() return new SaveToCustomFolderPreference(context, (resultCallback) -> new DirectoryChooser()
.setInitialDirectory(settingsHelper.getString(FOLDER_PATH)) .setInitialDirectory(settingsHelper.getString(FOLDER_PATH))
.setInteractionListener(path -> { .setInteractionListener(file -> {
settingsHelper.putString(FOLDER_PATH, path); settingsHelper.putString(FOLDER_PATH, file.getAbsolutePath());
resultCallback.onResult(path); resultCallback.onResult(file.getAbsolutePath());
}) })
.show(getParentFragmentManager(), null)); .show(getParentFragmentManager(), null));
} }

View File

@ -82,4 +82,5 @@ public final class Constants {
public static final String PREF_DARK_THEME = "dark_theme"; public static final String PREF_DARK_THEME = "dark_theme";
public static final String PREF_LIGHT_THEME = "light_theme"; public static final String PREF_LIGHT_THEME = "light_theme";
public static final String DEFAULT_HASH_TAG_PIC = "https://www.instagram.com/static/images/hashtag/search-hashtag-default-avatar.png/1d8417c9a4f5.png"; public static final String DEFAULT_HASH_TAG_PIC = "https://www.instagram.com/static/images/hashtag/search-hashtag-default-avatar.png/1d8417c9a4f5.png";
public static final String SHARED_PREFERENCES_NAME = "settings";
} }

View File

@ -6,6 +6,7 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log; import android.util.Log;
import android.util.Pair;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -95,7 +96,7 @@ public final class DataBox extends SQLiteOpenHelper {
+ FAV_COL_DATE_ADDED + " INTEGER)"); + FAV_COL_DATE_ADDED + " INTEGER)");
// add the old favorites back // add the old favorites back
for (final FavoriteModel oldFavorite : oldFavorites) { for (final FavoriteModel oldFavorite : oldFavorites) {
addFavorite(db, oldFavorite); addOrUpdateFavorite(db, oldFavorite);
} }
} }
Log.i(TAG, String.format("DB update from v%d to v%d completed!", oldVersion, newVersion)); Log.i(TAG, String.format("DB update from v%d to v%d completed!", oldVersion, newVersion));
@ -117,18 +118,10 @@ public final class DataBox extends SQLiteOpenHelper {
do { do {
try { try {
final String queryText = cursor.getString(cursor.getColumnIndex("query_text")); final String queryText = cursor.getString(cursor.getColumnIndex("query_text"));
FavoriteType type = null; final Pair<FavoriteType, String> favoriteTypeQueryPair = Utils.migrateOldFavQuery(queryText);
String query = null; if (favoriteTypeQueryPair == null) continue;
if (queryText.startsWith("@")) { final FavoriteType type = favoriteTypeQueryPair.first;
type = FavoriteType.USER; final String query = favoriteTypeQueryPair.second;
query = queryText.substring(1);
} else if (queryText.contains("/")) {
type = FavoriteType.LOCATION;
query = queryText.substring(0, queryText.indexOf("/"));
} else if (queryText.startsWith("#")) {
type = FavoriteType.HASHTAG;
query = queryText.substring(1);
}
oldModels.add(new FavoriteModel( oldModels.add(new FavoriteModel(
-1, -1,
query, query,
@ -170,20 +163,20 @@ public final class DataBox extends SQLiteOpenHelper {
return exists; return exists;
} }
public final void addFavorite(@NonNull final FavoriteModel model) { public final void addOrUpdateFavorite(@NonNull final FavoriteModel model) {
final String query = model.getQuery(); final String query = model.getQuery();
if (!TextUtils.isEmpty(query)) { if (!TextUtils.isEmpty(query)) {
try (final SQLiteDatabase db = getWritableDatabase()) { try (final SQLiteDatabase db = getWritableDatabase()) {
db.beginTransaction(); db.beginTransaction();
try { try {
addFavorite(db, model); addOrUpdateFavorite(db, model);
db.setTransactionSuccessful(); db.setTransactionSuccessful();
} catch (final Exception e) { } catch (final Exception e) {
if (logCollector != null) { if (logCollector != null) {
logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "addFavorite"); logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "addOrUpdateFavorite");
} }
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Log.e(TAG, "", e); Log.e(TAG, "Error adding/updating favorite", e);
} }
} finally { } finally {
db.endTransaction(); db.endTransaction();
@ -192,16 +185,22 @@ public final class DataBox extends SQLiteOpenHelper {
} }
} }
private void addFavorite(@NonNull final SQLiteDatabase db, @NonNull final FavoriteModel model) { private void addOrUpdateFavorite(@NonNull final SQLiteDatabase db, @NonNull final FavoriteModel model) {
final ContentValues values = new ContentValues(); final ContentValues values = new ContentValues();
values.put(FAV_COL_QUERY, model.getQuery()); values.put(FAV_COL_QUERY, model.getQuery());
values.put(FAV_COL_TYPE, model.getType().toString()); values.put(FAV_COL_TYPE, model.getType().toString());
values.put(FAV_COL_DISPLAY_NAME, model.getDisplayName()); values.put(FAV_COL_DISPLAY_NAME, model.getDisplayName());
values.put(FAV_COL_PIC_URL, model.getPicUrl()); values.put(FAV_COL_PIC_URL, model.getPicUrl());
values.put(FAV_COL_DATE_ADDED, model.getDateAdded().getTime()); values.put(FAV_COL_DATE_ADDED, model.getDateAdded().getTime());
int rows = 0; int rows;
if (model.getId() >= 1) { if (model.getId() >= 1) {
rows = db.update(TABLE_FAVORITES, values, FAV_COL_ID + "=?", new String[]{String.valueOf(model.getId())}); rows = db.update(TABLE_FAVORITES, values, FAV_COL_ID + "=?", new String[]{String.valueOf(model.getId())});
} else {
rows = db.update(TABLE_FAVORITES,
values,
FAV_COL_QUERY + "=?" +
" AND " + FAV_COL_TYPE + "=?",
new String[]{model.getQuery(), model.getType().toString()});
} }
if (rows != 1) { if (rows != 1) {
db.insertOrThrow(TABLE_FAVORITES, null, values); db.insertOrThrow(TABLE_FAVORITES, null, values);
@ -304,6 +303,16 @@ public final class DataBox extends SQLiteOpenHelper {
return null; return null;
} }
public final void addOrUpdateUser(@NonNull final DataBox.CookieModel cookieModel) {
addOrUpdateUser(
cookieModel.getUid(),
cookieModel.getUsername(),
cookieModel.getCookie(),
cookieModel.getFullName(),
cookieModel.getProfilePic()
);
}
public final void addOrUpdateUser(final String uid, public final void addOrUpdateUser(final String uid,
final String username, final String username,
final String cookie, final String cookie,
@ -368,15 +377,6 @@ public final class DataBox extends SQLiteOpenHelper {
} }
} }
public final int getCookieCount() {
int cookieCount = 0;
try (final SQLiteDatabase db = getReadableDatabase();
final Cursor cursor = db.rawQuery("SELECT * FROM cookies", null)) {
if (cursor != null) cookieCount = cursor.getCount();
}
return cookieCount;
}
@Nullable @Nullable
public final CookieModel getCookie(final String uid) { public final CookieModel getCookie(final String uid) {
CookieModel cookie = null; CookieModel cookie = null;
@ -404,10 +404,9 @@ public final class DataBox extends SQLiteOpenHelper {
return cookie; return cookie;
} }
@Nullable @NonNull
public final ArrayList<CookieModel> getAllCookies() { public final List<CookieModel> getAllCookies() {
ArrayList<CookieModel> cookies = null; final List<CookieModel> cookies = new ArrayList<>();
try (final SQLiteDatabase db = getReadableDatabase(); try (final SQLiteDatabase db = getReadableDatabase();
final Cursor cursor = db.rawQuery( final Cursor cursor = db.rawQuery(
"SELECT " "SELECT "
@ -419,7 +418,6 @@ public final class DataBox extends SQLiteOpenHelper {
+ " FROM " + TABLE_COOKIES, null) + " FROM " + TABLE_COOKIES, null)
) { ) {
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
cookies = new ArrayList<>();
do { do {
cookies.add(new CookieModel( cookies.add(new CookieModel(
cursor.getString(cursor.getColumnIndex(KEY_UID)), cursor.getString(cursor.getColumnIndex(KEY_UID)),
@ -431,7 +429,6 @@ public final class DataBox extends SQLiteOpenHelper {
} while (cursor.moveToNext()); } while (cursor.moveToNext());
} }
} }
return cookies; return cookies;
} }
@ -483,6 +480,12 @@ public final class DataBox extends SQLiteOpenHelper {
this.selected = selected; this.selected = selected;
} }
public boolean isValid() {
return !TextUtils.isEmpty(uid)
&& !TextUtils.isEmpty(username)
&& !TextUtils.isEmpty(cookie);
}
@Override @Override
public boolean equals(final Object o) { public boolean equals(final Object o) {
if (this == o) return true; if (this == o) return true;
@ -501,7 +504,14 @@ public final class DataBox extends SQLiteOpenHelper {
@NonNull @NonNull
@Override @Override
public String toString() { public String toString() {
return username; return "CookieModel{" +
"uid='" + uid + '\'' +
", username='" + username + '\'' +
", cookie='" + cookie + '\'' +
", fullName='" + fullName + '\'' +
", profilePic='" + profilePic + '\'' +
", selected=" + selected +
'}';
} }
} }

View File

@ -3,20 +3,26 @@ package awais.instagrabber.utils;
import android.app.Activity; import android.app.Activity;
import android.app.Dialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment; import android.os.Environment;
import android.os.FileObserver; import android.os.FileObserver;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment; import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.snackbar.BaseTransientBottomBar;
import com.google.android.material.snackbar.Snackbar;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
@ -24,22 +30,27 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.adapters.SimpleAdapter; import awais.instagrabber.adapters.DirectoryFilesAdapter;
import awais.instagrabber.databinding.LayoutDirectoryChooserBinding;
import awais.instagrabber.viewmodels.FileListViewModel;
public final class DirectoryChooser extends DialogFragment { public final class DirectoryChooser extends DialogFragment {
private static final String TAG = "DirectoryChooser";
public static final String KEY_CURRENT_DIRECTORY = "CURRENT_DIRECTORY"; public static final String KEY_CURRENT_DIRECTORY = "CURRENT_DIRECTORY";
private static final File sdcardPathFile = Environment.getExternalStorageDirectory(); private static final File sdcardPathFile = Environment.getExternalStorageDirectory();
private static final String sdcardPath = sdcardPathFile.getPath(); private static final String sdcardPath = sdcardPathFile.getPath();
private final List<String> fileNames = new ArrayList<>();
private Context context; private Context context;
private View btnConfirm, btnNavUp, btnCancel; private LayoutDirectoryChooserBinding binding;
private FileObserver fileObserver;
private File selectedDir; private File selectedDir;
private String initialDirectory; private String initialDirectory;
private TextView tvSelectedFolder;
private FileObserver fileObserver;
private SimpleAdapter<String> listDirectoriesAdapter;
private OnFragmentInteractionListener interactionListener; private OnFragmentInteractionListener interactionListener;
private boolean showZaAiConfigFiles = false; private boolean showBackupFiles = false;
private View.OnClickListener navigationOnClickListener;
private FileListViewModel fileListViewModel;
private OnCancelListener onCancelListener;
public DirectoryChooser() { public DirectoryChooser() {
super(); super();
@ -51,8 +62,8 @@ public final class DirectoryChooser extends DialogFragment {
return this; return this;
} }
public DirectoryChooser setShowZaAiConfigFiles(final boolean showZaAiConfigFiles) { public DirectoryChooser setShowBackupFiles(final boolean showBackupFiles) {
this.showZaAiConfigFiles = showZaAiConfigFiles; this.showBackupFiles = showBackupFiles;
return this; return this;
} }
@ -74,60 +85,71 @@ public final class DirectoryChooser extends DialogFragment {
@NonNull @NonNull
@Override @Override
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) {
binding = LayoutDirectoryChooserBinding.inflate(inflater, container, false);
init(container);
return binding.getRoot();
}
private void init(final ViewGroup container) {
Context context = this.context; Context context = this.context;
if (context == null) context = getContext(); if (context == null) context = getContext();
if (context == null) context = getActivity(); if (context == null) context = getActivity();
if (context == null) context = inflater.getContext(); if (context == null) return;
if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) != PackageManager.PERMISSION_GRANTED) {
final View view = inflater.inflate(R.layout.layout_directory_chooser, container, false); final String text = "Storage permissions denied!";
if (container == null) {
btnNavUp = view.findViewById(R.id.btnNavUp); Toast.makeText(context, text, Toast.LENGTH_LONG).show();
btnCancel = view.findViewById(R.id.btnCancel); } else {
btnConfirm = view.findViewById(R.id.btnConfirm); Snackbar.make(container, text, BaseTransientBottomBar.LENGTH_LONG).show();
tvSelectedFolder = view.findViewById(R.id.txtvSelectedFolder); }
final View.OnClickListener clickListener = v -> {
final Object tag;
if (v instanceof TextView && (tag = v.getTag()) instanceof CharSequence) {
final File file = new File(selectedDir, tag.toString());
if (file.isDirectory())
changeDirectory(file);
else if (showZaAiConfigFiles && file.isFile()) {
if (interactionListener != null && file.canRead())
interactionListener.onSelectDirectory(file.getAbsolutePath());
dismiss(); dismiss();
} }
final View.OnClickListener clickListener = v -> {
} else if (v == btnNavUp) { if (v == binding.btnConfirm) {
final File parent;
if (selectedDir != null && (parent = selectedDir.getParentFile()) != null)
changeDirectory(parent);
} else if (v == btnConfirm) {
if (interactionListener != null && isValidFile(selectedDir)) if (interactionListener != null && isValidFile(selectedDir))
interactionListener.onSelectDirectory(selectedDir.getAbsolutePath()); interactionListener.onSelectDirectory(selectedDir);
dismiss(); dismiss();
} else if (v == btnCancel) { } else if (v == binding.btnCancel) {
if (onCancelListener != null) {
onCancelListener.onCancel();
}
dismiss(); dismiss();
} }
}; };
btnNavUp.setOnClickListener(clickListener); navigationOnClickListener = v -> {
btnCancel.setOnClickListener(clickListener); final File parent;
btnConfirm.setOnClickListener(clickListener); if (selectedDir != null && (parent = selectedDir.getParentFile()) != null) {
changeDirectory(parent);
listDirectoriesAdapter = new SimpleAdapter<>(context, fileNames, clickListener); }
};
final RecyclerView directoriesList = view.findViewById(R.id.directoryList); binding.toolbar.setNavigationOnClickListener(navigationOnClickListener);
directoriesList.setLayoutManager(new LinearLayoutManager(context)); binding.toolbar.setSubtitle(showBackupFiles ? R.string.select_backup_file : R.string.select_folder);
directoriesList.setAdapter(listDirectoriesAdapter); binding.btnCancel.setOnClickListener(clickListener);
// no need to show confirm for file picker
binding.btnConfirm.setVisibility(showBackupFiles ? View.GONE : View.VISIBLE);
if (!showBackupFiles) {
binding.btnConfirm.setOnClickListener(clickListener);
}
fileListViewModel = new ViewModelProvider(this).get(FileListViewModel.class);
final DirectoryFilesAdapter listDirectoriesAdapter = new DirectoryFilesAdapter(file -> {
if (file.isDirectory()) {
changeDirectory(file);
return;
}
if (showBackupFiles && file.isFile()) {
if (interactionListener != null && file.canRead()) {
interactionListener.onSelectDirectory(file);
}
dismiss();
}
});
fileListViewModel.getList().observe(this, listDirectoriesAdapter::submitList);
binding.directoryList.setLayoutManager(new LinearLayoutManager(context));
binding.directoryList.setAdapter(listDirectoriesAdapter);
final File initDir = new File(initialDirectory); final File initDir = new File(initialDirectory);
final File initialDir = !TextUtils.isEmpty(initialDirectory) && isValidFile(initDir) ? initDir : Environment.getExternalStorageDirectory(); final File initialDir = !TextUtils.isEmpty(initialDirectory) && isValidFile(initDir) ? initDir : Environment.getExternalStorageDirectory();
changeDirectory(initialDir); changeDirectory(initialDir);
return view;
} }
@Override @Override
@ -153,12 +175,16 @@ public final class DirectoryChooser extends DialogFragment {
public void onBackPressed() { public void onBackPressed() {
if (selectedDir != null) { if (selectedDir != null) {
final String absolutePath = selectedDir.getAbsolutePath(); final String absolutePath = selectedDir.getAbsolutePath();
if (absolutePath.equals(sdcardPath) || absolutePath.equals(sdcardPathFile.getAbsolutePath())) if (absolutePath.equals(sdcardPath) || absolutePath.equals(sdcardPathFile.getAbsolutePath())) {
if (onCancelListener != null) {
onCancelListener.onCancel();
}
dismiss(); dismiss();
else } else {
changeDirectory(selectedDir.getParentFile()); changeDirectory(selectedDir.getParentFile());
} }
} }
}
}; };
} }
@ -189,21 +215,28 @@ public final class DirectoryChooser extends DialogFragment {
private void changeDirectory(final File dir) { private void changeDirectory(final File dir) {
if (dir != null && dir.isDirectory()) { if (dir != null && dir.isDirectory()) {
final String path = dir.getAbsolutePath(); final String path = dir.getAbsolutePath();
binding.toolbar.setTitle(path);
final File[] contents = dir.listFiles(); final File[] contents = dir.listFiles();
if (contents != null) { if (contents != null) {
fileNames.clear(); final List<File> fileNames = new ArrayList<>();
for (final File f : contents) { for (final File f : contents) {
final String name = f.getName(); final String name = f.getName();
if (f.isDirectory() || showZaAiConfigFiles && f.isFile() && name.toLowerCase().endsWith(".zaai")) final String nameLowerCase = name.toLowerCase();
fileNames.add(name); final boolean isBackupFile = nameLowerCase.endsWith(".zaai") || nameLowerCase.endsWith(".backup");
if (f.isDirectory() || (showBackupFiles && f.isFile() && isBackupFile))
fileNames.add(f);
} }
Collections.sort(fileNames, (o1, o2) -> {
Collections.sort(fileNames); if ((o1.isDirectory() && o2.isDirectory())
|| (o1.isFile() && o2.isFile())) {
return o1.getName().compareToIgnoreCase(o2.getName());
}
if (o1.isDirectory()) return -1;
if (o2.isDirectory()) return 1;
return 0;
});
fileListViewModel.getList().postValue(fileNames);
selectedDir = dir; selectedDir = dir;
tvSelectedFolder.setText(path);
listDirectoriesAdapter.notifyDataSetChanged();
fileObserver = new FileObserver(path, FileObserver.CREATE | FileObserver.DELETE | FileObserver.MOVED_FROM | FileObserver.MOVED_TO) { fileObserver = new FileObserver(path, FileObserver.CREATE | FileObserver.DELETE | FileObserver.MOVED_FROM | FileObserver.MOVED_TO) {
private final Runnable currentDirRefresher = () -> changeDirectory(selectedDir); private final Runnable currentDirRefresher = () -> changeDirectory(selectedDir);
@ -222,15 +255,15 @@ public final class DirectoryChooser extends DialogFragment {
if (selectedDir != null) { if (selectedDir != null) {
final String path = selectedDir.getAbsolutePath(); final String path = selectedDir.getAbsolutePath();
toggleUpButton(!path.equals(sdcardPathFile.getAbsolutePath()) && selectedDir != sdcardPathFile); toggleUpButton(!path.equals(sdcardPathFile.getAbsolutePath()) && selectedDir != sdcardPathFile);
btnConfirm.setEnabled(isValidFile(selectedDir)); binding.btnConfirm.setEnabled(isValidFile(selectedDir));
} }
} }
private void toggleUpButton(final boolean enable) { private void toggleUpButton(final boolean enable) {
if (btnNavUp != null) { binding.toolbar.setNavigationOnClickListener(enable ? navigationOnClickListener : null);
btnNavUp.setEnabled(enable); final Drawable navigationIcon = binding.toolbar.getNavigationIcon();
btnNavUp.setAlpha(enable ? 1f : 0.617f); if (navigationIcon == null) return;
} navigationIcon.setAlpha(enable ? 255 : (int) (255 * 0.617));
} }
private boolean isValidFile(final File file) { private boolean isValidFile(final File file) {
@ -242,7 +275,17 @@ public final class DirectoryChooser extends DialogFragment {
return this; return this;
} }
public void setOnCancelListener(final OnCancelListener onCancelListener) {
if (onCancelListener != null) {
this.onCancelListener = onCancelListener;
}
}
public interface OnCancelListener {
void onCancel();
}
public interface OnFragmentInteractionListener { public interface OnFragmentInteractionListener {
void onSelectDirectory(final String path); void onSelectDirectory(final File file);
} }
} }

View File

@ -37,7 +37,9 @@ import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
public final class DownloadUtils { public final class DownloadUtils {
public static final String[] PERMS = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; public static final String[] PERMS = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
public static void batchDownload(@NonNull final Context context, @Nullable String username, final DownloadMethod method, public static void batchDownload(@NonNull final Context context,
@Nullable String username,
final DownloadMethod method,
final List<? extends BasePostModel> itemsToDownload) { final List<? extends BasePostModel> itemsToDownload) {
if (Utils.settingsHelper == null) Utils.settingsHelper = new SettingsHelper(context); if (Utils.settingsHelper == null) Utils.settingsHelper = new SettingsHelper(context);

View File

@ -1,35 +1,32 @@
package awais.instagrabber.utils; package awais.instagrabber.utils;
import android.content.Context; import android.content.Context;
import android.text.InputFilter; import android.content.SharedPreferences;
import android.text.InputType;
import android.util.Base64; import android.util.Base64;
import android.util.Log; import android.util.Log;
import android.util.Pair;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.AppCompatEditText;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.util.ArrayList; import java.util.Date;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import awais.instagrabber.BuildConfig; import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.utils.PasswordUtils.IncorrectPasswordException;
import awaisomereport.LogCollector.LogFile; import awaisomereport.LogCollector.LogFile;
import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.logCollector;
@ -45,18 +42,19 @@ public final class ExportImportUtils {
@IntDef(value = {FLAG_COOKIES, FLAG_FAVORITES, FLAG_SETTINGS}, flag = true) @IntDef(value = {FLAG_COOKIES, FLAG_FAVORITES, FLAG_SETTINGS}, flag = true)
@interface ExportImportFlags {} @interface ExportImportFlags {}
public static void Export(@Nullable final String password, @ExportImportFlags final int flags, @NonNull final File filePath, public static void exportData(@Nullable final String password,
final FetchListener<Boolean> fetchListener) { @ExportImportFlags final int flags,
final String exportString = ExportImportUtils.getExportString(flags); @NonNull final File filePath,
if (!TextUtils.isEmpty(exportString)) { final FetchListener<Boolean> fetchListener,
@NonNull final Context context) {
final String exportString = getExportString(flags, context);
if (TextUtils.isEmpty(exportString)) return;
final boolean isPass = !TextUtils.isEmpty(password); final boolean isPass = !TextUtils.isEmpty(password);
byte[] exportBytes = null; byte[] exportBytes = null;
if (isPass) { if (isPass) {
final byte[] passwordBytes = password.getBytes(); final byte[] passwordBytes = password.getBytes();
final byte[] bytes = new byte[32]; final byte[] bytes = new byte[32];
System.arraycopy(passwordBytes, 0, bytes, 0, Math.min(passwordBytes.length, 32)); System.arraycopy(passwordBytes, 0, bytes, 0, Math.min(passwordBytes.length, 32));
try { try {
exportBytes = PasswordUtils.enc(exportString, bytes); exportBytes = PasswordUtils.enc(exportString, bytes);
} catch (final Exception e) { } catch (final Exception e) {
@ -68,7 +66,6 @@ public final class ExportImportUtils {
} else { } else {
exportBytes = Base64.encode(exportString.getBytes(), Base64.DEFAULT | Base64.NO_WRAP | Base64.NO_PADDING); exportBytes = Base64.encode(exportString.getBytes(), Base64.DEFAULT | Base64.NO_WRAP | Base64.NO_PADDING);
} }
if (exportBytes != null && exportBytes.length > 1) { if (exportBytes != null && exportBytes.length > 1) {
try (final FileOutputStream fos = new FileOutputStream(filePath)) { try (final FileOutputStream fos = new FileOutputStream(filePath)) {
fos.write(isPass ? 'A' : 'Z'); fos.write(isPass ? 'A' : 'Z');
@ -82,53 +79,49 @@ public final class ExportImportUtils {
} }
} else if (fetchListener != null) fetchListener.onResult(false); } else if (fetchListener != null) fetchListener.onResult(false);
} }
}
public static void Import(@NonNull final Context context, @ExportImportFlags final int flags, @NonNull final File filePath, public static void importData(@NonNull final Context context,
final FetchListener<Boolean> fetchListener) { @ExportImportFlags final int flags,
try (final FileInputStream fis = new FileInputStream(filePath)) { @NonNull final File file,
final String password,
final FetchListener<Boolean> fetchListener) throws IncorrectPasswordException {
try (final FileInputStream fis = new FileInputStream(file)) {
final int configType = fis.read(); final int configType = fis.read();
final StringBuilder builder = new StringBuilder(); final StringBuilder builder = new StringBuilder();
int c; int c;
while ((c = fis.read()) != -1) { while ((c = fis.read()) != -1) {
builder.append((char) c); builder.append((char) c);
} }
if (configType == 'A') { if (configType == 'A') {
// password // password
final AppCompatEditText editText = new AppCompatEditText(context); if (TextUtils.isEmpty(password)) return;
editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(32)});
editText.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD);
new AlertDialog.Builder(context).setView(editText).setTitle(R.string.password)
.setPositiveButton(R.string.confirm, (dialog, which) -> {
final CharSequence text = editText.getText();
if (!TextUtils.isEmpty(text)) {
try { try {
final byte[] passwordBytes = text.toString().getBytes(); final byte[] passwordBytes = password.getBytes();
final byte[] bytes = new byte[32]; final byte[] bytes = new byte[32];
System.arraycopy(passwordBytes, 0, bytes, 0, Math.min(passwordBytes.length, 32)); System.arraycopy(passwordBytes, 0, bytes, 0, Math.min(passwordBytes.length, 32));
saveToSettings(new String(PasswordUtils.dec(builder.toString(), bytes)), flags, importJson(new String(PasswordUtils.dec(builder.toString(), bytes)),
flags,
fetchListener); fetchListener);
} catch (final IncorrectPasswordException e) {
throw e;
} catch (final Exception e) { } catch (final Exception e) {
if (fetchListener != null) fetchListener.onResult(false); if (fetchListener != null) fetchListener.onResult(false);
if (logCollector != null) if (logCollector != null)
logCollector.appendException(e, LogFile.UTILS_IMPORT, "Import::pass"); logCollector.appendException(e, LogFile.UTILS_IMPORT, "Import::pass");
if (BuildConfig.DEBUG) Log.e(TAG, "", e); if (BuildConfig.DEBUG) Log.e(TAG, "Error importing backup", e);
} }
} else
Toast.makeText(context, R.string.dialog_export_err_password_empty, Toast.LENGTH_SHORT).show();
}).show();
} else if (configType == 'Z') { } else if (configType == 'Z') {
saveToSettings(new String(Base64.decode(builder.toString(), Base64.DEFAULT | Base64.NO_PADDING | Base64.NO_WRAP)), importJson(new String(Base64.decode(builder.toString(), Base64.DEFAULT | Base64.NO_PADDING | Base64.NO_WRAP)),
flags, fetchListener); flags,
fetchListener);
} else { } else {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); Toast.makeText(context, "File is corrupted!", Toast.LENGTH_LONG).show();
if (fetchListener != null) fetchListener.onResult(false); if (fetchListener != null) fetchListener.onResult(false);
} }
} catch (IncorrectPasswordException e) {
// separately handle incorrect password
throw e;
} catch (final Exception e) { } catch (final Exception e) {
if (fetchListener != null) fetchListener.onResult(false); if (fetchListener != null) fetchListener.onResult(false);
if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_IMPORT, "Import"); if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_IMPORT, "Import");
@ -136,16 +129,89 @@ public final class ExportImportUtils {
} }
} }
private static void saveToSettings(final String json, @ExportImportFlags final int flags, final FetchListener<Boolean> fetchListener) { private static void importJson(@NonNull final String json,
@ExportImportFlags final int flags,
final FetchListener<Boolean> fetchListener) {
try { try {
final JSONObject jsonObject = new JSONObject(json); final JSONObject jsonObject = new JSONObject(json);
if ((flags & FLAG_SETTINGS) == FLAG_SETTINGS && jsonObject.has("settings")) { if ((flags & FLAG_SETTINGS) == FLAG_SETTINGS && jsonObject.has("settings")) {
importSettings(jsonObject);
}
if ((flags & FLAG_COOKIES) == FLAG_COOKIES && jsonObject.has("cookies")) {
importAccounts(jsonObject);
}
if ((flags & FLAG_FAVORITES) == FLAG_FAVORITES && jsonObject.has("favs")) {
importFavorites(jsonObject);
}
if (fetchListener != null) fetchListener.onResult(true);
} catch (final Exception e) {
if (fetchListener != null) fetchListener.onResult(false);
if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_IMPORT, "importJson");
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
}
private static void importFavorites(final JSONObject jsonObject) throws JSONException {
final JSONArray favs = jsonObject.getJSONArray("favs");
for (int i = 0; i < favs.length(); i++) {
final JSONObject favsObject = favs.getJSONObject(i);
final String queryText = favsObject.optString("q");
if (TextUtils.isEmpty(queryText)) continue;
final Pair<FavoriteType, String> favoriteTypeQueryPair;
String query = null;
FavoriteType favoriteType = null;
if (queryText.contains("@")
|| queryText.contains("#")
|| queryText.contains("/")) {
favoriteTypeQueryPair = Utils.migrateOldFavQuery(queryText);
if (favoriteTypeQueryPair != null) {
query = favoriteTypeQueryPair.second;
favoriteType = favoriteTypeQueryPair.first;
}
} else {
query = queryText;
favoriteType = FavoriteType.valueOf(favsObject.optString("type"));
}
if (query == null || favoriteType == null) {
continue;
}
final DataBox.FavoriteModel favoriteModel = new DataBox.FavoriteModel(
-1,
query,
favoriteType,
favsObject.optString("s"),
favoriteType == FavoriteType.HASHTAG ? null
: favsObject.optString("pic_url"),
new Date(favsObject.getLong("d")));
// Log.d(TAG, "importJson: favoriteModel: " + favoriteModel);
Utils.dataBox.addOrUpdateFavorite(favoriteModel);
}
}
private static void importAccounts(final JSONObject jsonObject) throws JSONException {
final JSONArray cookies = jsonObject.getJSONArray("cookies");
for (int i = 0; i < cookies.length(); i++) {
final JSONObject cookieObject = cookies.getJSONObject(i);
final DataBox.CookieModel cookieModel = new DataBox.CookieModel(
cookieObject.optString("i"),
cookieObject.optString("u"),
cookieObject.optString("c"),
cookieObject.optString("full_name"),
cookieObject.optString("profile_pic")
);
if (!cookieModel.isValid()) continue;
// Log.d(TAG, "importJson: cookieModel: " + cookieModel);
Utils.dataBox.addOrUpdateUser(cookieModel);
}
}
private static void importSettings(final JSONObject jsonObject) throws JSONException {
final JSONObject objSettings = jsonObject.getJSONObject("settings"); final JSONObject objSettings = jsonObject.getJSONObject("settings");
final Iterator<String> keys = objSettings.keys(); final Iterator<String> keys = objSettings.keys();
while (keys.hasNext()) { while (keys.hasNext()) {
final String key = keys.next(); final String key = keys.next();
final Object val = objSettings.opt(key); final Object val = objSettings.opt(key);
// Log.d(TAG, "importJson: key: " + key + ", val: " + val);
if (val instanceof String) { if (val instanceof String) {
settingsHelper.putString(key, (String) val); settingsHelper.putString(key, (String) val);
} else if (val instanceof Integer) { } else if (val instanceof Integer) {
@ -156,60 +222,32 @@ public final class ExportImportUtils {
} }
} }
if ((flags & FLAG_COOKIES) == FLAG_COOKIES && jsonObject.has("cookies")) { public static boolean isEncrypted(final File file) {
final JSONArray cookies = jsonObject.getJSONArray("cookies"); try (final FileInputStream fis = new FileInputStream(file)) {
final int cookiesLen = cookies.length(); final int configType = fis.read();
for (int i = 0; i < cookiesLen; ++i) { if (configType == 'A') {
final JSONObject cookieObject = cookies.getJSONObject(i); return true;
// final DataBox.CookieModel cookieModel = new DataBox.CookieModel(cookieObject.getString("i"),
// cookieObject.getString("u"),
// cookieObject.getString("c"),
// fullName,
// profilePic);
// Utils.dataBox.addOrUpdateUser(cookieModel.getUid(), cookieModel.getUserInfo(), cookieModel.getCookie());
} }
}
if ((flags & FLAG_FAVORITES) == FLAG_FAVORITES && jsonObject.has("favs")) {
final JSONArray favs = jsonObject.getJSONArray("favs");
final int favsLen = favs.length();
for (int i = 0; i < favsLen; ++i) {
final JSONObject favsObject = favs.getJSONObject(i);
// Utils.dataBox.addFavorite(new DataBox.FavoriteModel(favsObject.getString("q"),
// favsObject.getLong("d"),
// favsObject.has("s") ? favsObject.getString("s") : favsObject.getString("q")));
}
}
if (fetchListener != null) fetchListener.onResult(true);
} catch (final Exception e) { } catch (final Exception e) {
if (fetchListener != null) fetchListener.onResult(false); Log.e(TAG, "isEncrypted", e);
if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_IMPORT, "saveToSettings");
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
} }
return false;
} }
@Nullable @Nullable
private static String getExportString(@ExportImportFlags final int flags) { private static String getExportString(@ExportImportFlags final int flags,
@NonNull final Context context) {
String result = null; String result = null;
try { try {
final JSONObject jsonObject = new JSONObject(); final JSONObject jsonObject = new JSONObject();
String str;
if ((flags & FLAG_SETTINGS) == FLAG_SETTINGS) { if ((flags & FLAG_SETTINGS) == FLAG_SETTINGS) {
str = getSettings(); jsonObject.put("settings", getSettings(context));
if (str != null) jsonObject.put("settings", new JSONObject(str));
} }
if ((flags & FLAG_COOKIES) == FLAG_COOKIES) { if ((flags & FLAG_COOKIES) == FLAG_COOKIES) {
str = getCookies(); jsonObject.put("cookies", getCookies());
if (str != null) jsonObject.put("cookies", new JSONArray(str));
} }
if ((flags & FLAG_FAVORITES) == FLAG_FAVORITES) { if ((flags & FLAG_FAVORITES) == FLAG_FAVORITES) {
str = getFavorites(); jsonObject.put("favs", getFavorites());
if (str != null) jsonObject.put("favs", new JSONArray(str));
} }
result = jsonObject.toString(); result = jsonObject.toString();
@ -220,117 +258,73 @@ public final class ExportImportUtils {
return result; return result;
} }
@Nullable @NonNull
private static String getSettings() { private static JSONObject getSettings(@NonNull final Context context) {
String result = null; final SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
final Map<String, ?> allPrefs = sharedPreferences.getAll();
if (settingsHelper != null) { if (allPrefs == null) {
return new JSONObject();
}
try { try {
final JSONObject json = new JSONObject(); final JSONObject jsonObject = new JSONObject(allPrefs);
json.put(Constants.APP_THEME, settingsHelper.getString(Constants.APP_THEME)); jsonObject.remove(Constants.COOKIE);
json.put(Constants.APP_LANGUAGE, settingsHelper.getString(Constants.APP_LANGUAGE)); jsonObject.remove(Constants.DEVICE_UUID);
jsonObject.remove(Constants.PREV_INSTALL_VERSION);
String str = settingsHelper.getString(Constants.FOLDER_PATH); return jsonObject;
if (!TextUtils.isEmpty(str)) json.put(Constants.FOLDER_PATH, str); } catch (Exception e) {
Log.e(TAG, "Error exporting settings", e);
str = settingsHelper.getString(Constants.DATE_TIME_FORMAT);
if (!TextUtils.isEmpty(str)) json.put(Constants.DATE_TIME_FORMAT, str);
str = settingsHelper.getString(Constants.DATE_TIME_SELECTION);
if (!TextUtils.isEmpty(str)) json.put(Constants.DATE_TIME_SELECTION, str);
str = settingsHelper.getString(Constants.CUSTOM_DATE_TIME_FORMAT);
if (!TextUtils.isEmpty(str)) json.put(Constants.CUSTOM_DATE_TIME_FORMAT, str);
json.put(Constants.DOWNLOAD_USER_FOLDER, settingsHelper.getBoolean(Constants.DOWNLOAD_USER_FOLDER));
json.put(Constants.MUTED_VIDEOS, settingsHelper.getBoolean(Constants.MUTED_VIDEOS));
json.put(Constants.AUTOPLAY_VIDEOS, settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS));
json.put(Constants.AUTOLOAD_POSTS, settingsHelper.getBoolean(Constants.AUTOLOAD_POSTS));
json.put(Constants.FOLDER_SAVE_TO, settingsHelper.getBoolean(Constants.FOLDER_SAVE_TO));
result = json.toString();
} catch (final Exception e) {
result = null;
if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_EXPORT, "getSettings");
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
} }
return new JSONObject();
} }
return result; @NonNull
} private static JSONArray getFavorites() {
if (Utils.dataBox == null) return new JSONArray();
@Nullable
private static String getFavorites() {
String result = null;
if (Utils.dataBox != null) {
try { try {
final List<DataBox.FavoriteModel> allFavorites = Utils.dataBox.getAllFavorites(); final List<DataBox.FavoriteModel> allFavorites = Utils.dataBox.getAllFavorites();
final int allFavoritesSize;
if ((allFavoritesSize = allFavorites.size()) > 0) {
final JSONArray jsonArray = new JSONArray(); final JSONArray jsonArray = new JSONArray();
for (int i = 0; i < allFavoritesSize; i++) { for (final DataBox.FavoriteModel favorite : allFavorites) {
final DataBox.FavoriteModel favorite = allFavorites.get(i);
final JSONObject jsonObject = new JSONObject(); final JSONObject jsonObject = new JSONObject();
jsonObject.put("q", favorite.getQuery()); jsonObject.put("q", favorite.getQuery());
jsonObject.put("d", favorite.getDateAdded().getTime()); jsonObject.put("type", favorite.getType().toString());
jsonObject.put("s", favorite.getDisplayName()); jsonObject.put("s", favorite.getDisplayName());
jsonObject.put("pic_url", favorite.getPicUrl());
jsonObject.put("d", favorite.getDateAdded().getTime());
jsonArray.put(jsonObject); jsonArray.put(jsonObject);
} }
result = jsonArray.toString(); return jsonArray;
}
} catch (final Exception e) { } catch (final Exception e) {
result = null; if (logCollector != null) {
if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_EXPORT, "getFavorites"); logCollector.appendException(e, LogFile.UTILS_EXPORT, "getFavorites");
if (BuildConfig.DEBUG) Log.e(TAG, "", e); }
if (BuildConfig.DEBUG) {
Log.e(TAG, "Error exporting favorites", e);
} }
} }
return result; return new JSONArray();
} }
@Nullable @NonNull
private static String getCookies() { private static JSONArray getCookies() {
String result = null; if (Utils.dataBox == null) return new JSONArray();
if (Utils.dataBox != null) {
try { try {
final ArrayList<DataBox.CookieModel> allCookies = Utils.dataBox.getAllCookies(); final List<DataBox.CookieModel> allCookies = Utils.dataBox.getAllCookies();
final int allCookiesSize;
if (allCookies != null && (allCookiesSize = allCookies.size()) > 0) {
final JSONArray jsonArray = new JSONArray(); final JSONArray jsonArray = new JSONArray();
for (int i = 0; i < allCookiesSize; i++) { for (final DataBox.CookieModel cookie : allCookies) {
final DataBox.CookieModel cookieModel = allCookies.get(i);
final JSONObject jsonObject = new JSONObject(); final JSONObject jsonObject = new JSONObject();
jsonObject.put("i", cookieModel.getUid()); jsonObject.put("i", cookie.getUid());
jsonObject.put("u", cookieModel.getUsername()); jsonObject.put("u", cookie.getUsername());
jsonObject.put("c", cookieModel.getCookie()); jsonObject.put("c", cookie.getCookie());
jsonObject.put("full_name", cookie.getFullName());
jsonObject.put("profile_pic", cookie.getProfilePic());
jsonArray.put(jsonObject); jsonArray.put(jsonObject);
} }
result = jsonArray.toString(); return jsonArray;
}
} catch (final Exception e) { } catch (final Exception e) {
result = null; if (BuildConfig.DEBUG) {
if (BuildConfig.DEBUG) Log.e(TAG, "", e); Log.e(TAG, "Error exporting accounts", e);
} }
} }
return result; return new JSONArray();
}
private final static class PasswordUtils {
private static final String cipherAlgo = "AES";
private static final String cipherTran = "AES/CBC/PKCS5Padding";
private static byte[] dec(final String encrypted, final byte[] keyValue) throws Exception {
final Cipher cipher = Cipher.getInstance(cipherTran);
final SecretKeySpec secretKey = new SecretKeySpec(keyValue, cipherAlgo);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(new byte[16]));
return cipher.doFinal(Base64.decode(encrypted, Base64.DEFAULT | Base64.NO_PADDING | Base64.NO_WRAP));
}
private static byte[] enc(@NonNull final String str, final byte[] keyValue) throws Exception {
final Cipher cipher = Cipher.getInstance(cipherTran);
final SecretKeySpec secretKey = new SecretKeySpec(keyValue, cipherAlgo);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(new byte[16]));
final byte[] bytes = cipher.doFinal(str.getBytes());
return Base64.encode(bytes, Base64.DEFAULT | Base64.NO_PADDING | Base64.NO_WRAP);
}
} }
} }

View File

@ -0,0 +1,47 @@
package awais.instagrabber.utils;
import android.util.Base64;
import androidx.annotation.NonNull;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public final class PasswordUtils {
private static final String cipherAlgo = "AES";
private static final String cipherTran = "AES/CBC/PKCS5Padding";
public static byte[] dec(final String encrypted, final byte[] keyValue) throws Exception {
try {
final Cipher cipher = Cipher.getInstance(cipherTran);
final SecretKeySpec secretKey = new SecretKeySpec(keyValue, cipherAlgo);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(new byte[16]));
return cipher.doFinal(Base64.decode(encrypted, Base64.DEFAULT | Base64.NO_PADDING | Base64.NO_WRAP));
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException | InvalidKeyException | BadPaddingException | IllegalBlockSizeException e) {
throw new IncorrectPasswordException(e);
}
}
public static byte[] enc(@NonNull final String str, final byte[] keyValue) throws Exception {
final Cipher cipher = Cipher.getInstance(cipherTran);
final SecretKeySpec secretKey = new SecretKeySpec(keyValue, cipherAlgo);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(new byte[16]));
final byte[] bytes = cipher.doFinal(str.getBytes());
return Base64.encode(bytes, Base64.DEFAULT | Base64.NO_PADDING | Base64.NO_WRAP);
}
public static class IncorrectPasswordException extends Exception {
public IncorrectPasswordException(final GeneralSecurityException e) {
super(e);
}
}
}

View File

@ -39,7 +39,7 @@ public final class SettingsHelper {
private final SharedPreferences sharedPreferences; private final SharedPreferences sharedPreferences;
public SettingsHelper(@NonNull final Context context) { public SettingsHelper(@NonNull final Context context) {
this.sharedPreferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE); this.sharedPreferences = context.getSharedPreferences(Constants.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
} }
@NonNull @NonNull

View File

@ -1,26 +1,20 @@
package awais.instagrabber.utils; package awais.instagrabber.utils;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog;
import android.content.ClipData; import android.content.ClipData;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.text.Editable;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.util.Pair;
import android.view.View;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.StringRes; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.FragmentManager;
import com.google.android.exoplayer2.database.ExoDatabaseProvider; import com.google.android.exoplayer2.database.ExoDatabaseProvider;
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor; import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor;
@ -37,11 +31,9 @@ import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.databinding.DialogImportExportBinding; import awais.instagrabber.models.enums.FavoriteType;
import awaisomereport.LogCollector; import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
public final class Utils { public final class Utils {
private static final String TAG = "Utils"; private static final String TAG = "Utils";
private static final int VIDEO_CACHE_MAX_BYTES = 10 * 1024 * 1024; private static final int VIDEO_CACHE_MAX_BYTES = 10 * 1024 * 1024;
@ -62,19 +54,6 @@ public final class Utils {
return Math.round((dp * displayMetrics.densityDpi) / 160.0f); return Math.round((dp * displayMetrics.densityDpi) / 160.0f);
} }
public static void setTooltipText(final View view, @StringRes final int tooltipTextRes) {
if (view != null && tooltipTextRes != 0 && tooltipTextRes != -1) {
final Context context = view.getContext();
final String tooltipText = context.getResources().getString(tooltipTextRes);
if (Build.VERSION.SDK_INT >= 26) view.setTooltipText(tooltipText);
else view.setOnLongClickListener(v -> {
Toast.makeText(context, tooltipText, Toast.LENGTH_SHORT).show();
return true;
});
}
}
public static void copyText(@NonNull final Context context, final CharSequence string) { public static void copyText(@NonNull final Context context, final CharSequence string) {
if (clipboardManager == null) if (clipboardManager == null)
clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
@ -87,100 +66,6 @@ public final class Utils {
Toast.makeText(context, toastMessage, Toast.LENGTH_SHORT).show(); Toast.makeText(context, toastMessage, Toast.LENGTH_SHORT).show();
} }
public static void showImportExportDialog(final Context context) {
final DialogImportExportBinding importExportBinding = DialogImportExportBinding.inflate(LayoutInflater.from(context));
final View passwordParent = (View) importExportBinding.cbPassword.getParent();
final View exportLoginsParent = (View) importExportBinding.cbExportLogins.getParent();
final View exportFavoritesParent = (View) importExportBinding.cbExportFavorites.getParent();
final View exportSettingsParent = (View) importExportBinding.cbExportSettings.getParent();
final View importLoginsParent = (View) importExportBinding.cbImportLogins.getParent();
final View importFavoritesParent = (View) importExportBinding.cbImportFavorites.getParent();
final View importSettingsParent = (View) importExportBinding.cbImportSettings.getParent();
importExportBinding.cbPassword.setOnCheckedChangeListener((buttonView, isChecked) ->
importExportBinding.etPassword.etPassword.setEnabled(isChecked));
final AlertDialog[] dialog = new AlertDialog[1];
final View.OnClickListener onClickListener = v -> {
if (v == passwordParent) importExportBinding.cbPassword.performClick();
else if (v == exportLoginsParent) importExportBinding.cbExportLogins.performClick();
else if (v == exportFavoritesParent)
importExportBinding.cbExportFavorites.performClick();
else if (v == importLoginsParent) importExportBinding.cbImportLogins.performClick();
else if (v == importFavoritesParent)
importExportBinding.cbImportFavorites.performClick();
else if (v == exportSettingsParent) importExportBinding.cbExportSettings.performClick();
else if (v == importSettingsParent) importExportBinding.cbImportSettings.performClick();
else if (context instanceof AppCompatActivity) {
final FragmentManager fragmentManager = ((AppCompatActivity) context).getSupportFragmentManager();
final String folderPath = settingsHelper.getString(FOLDER_PATH);
if (v == importExportBinding.btnSaveTo) {
final Editable text = importExportBinding.etPassword.etPassword.getText();
final boolean passwordChecked = importExportBinding.cbPassword.isChecked();
if (passwordChecked && TextUtils.isEmpty(text))
Toast.makeText(context, R.string.dialog_export_err_password_empty, Toast.LENGTH_SHORT).show();
else {
new DirectoryChooser().setInitialDirectory(folderPath).setInteractionListener(path -> {
final File file = new File(path, "InstaGrabber_Settings_" + System.currentTimeMillis() + ".zaai");
final String password = passwordChecked ? text.toString() : null;
int flags = 0;
if (importExportBinding.cbExportFavorites.isChecked())
flags |= ExportImportUtils.FLAG_FAVORITES;
if (importExportBinding.cbExportSettings.isChecked())
flags |= ExportImportUtils.FLAG_SETTINGS;
if (importExportBinding.cbExportLogins.isChecked())
flags |= ExportImportUtils.FLAG_COOKIES;
ExportImportUtils.Export(password, flags, file, result -> {
Toast.makeText(context, result ? R.string.dialog_export_success : R.string.dialog_export_failed, Toast.LENGTH_SHORT)
.show();
if (dialog[0] != null && dialog[0].isShowing()) dialog[0].dismiss();
});
}).show(fragmentManager, null);
}
} else if (v == importExportBinding.btnImport) {
new DirectoryChooser().setInitialDirectory(folderPath).setShowZaAiConfigFiles(true).setInteractionListener(path -> {
int flags = 0;
if (importExportBinding.cbImportFavorites.isChecked())
flags |= ExportImportUtils.FLAG_FAVORITES;
if (importExportBinding.cbImportSettings.isChecked())
flags |= ExportImportUtils.FLAG_SETTINGS;
if (importExportBinding.cbImportLogins.isChecked())
flags |= ExportImportUtils.FLAG_COOKIES;
ExportImportUtils.Import(context, flags, new File(path), result -> {
((AppCompatActivity) context).recreate();
Toast.makeText(context, result ? R.string.dialog_import_success : R.string.dialog_import_failed, Toast.LENGTH_SHORT)
.show();
if (dialog[0] != null && dialog[0].isShowing()) dialog[0].dismiss();
});
}).show(fragmentManager, null);
}
}
};
passwordParent.setOnClickListener(onClickListener);
exportLoginsParent.setOnClickListener(onClickListener);
exportSettingsParent.setOnClickListener(onClickListener);
exportFavoritesParent.setOnClickListener(onClickListener);
importLoginsParent.setOnClickListener(onClickListener);
importSettingsParent.setOnClickListener(onClickListener);
importFavoritesParent.setOnClickListener(onClickListener);
importExportBinding.btnSaveTo.setOnClickListener(onClickListener);
importExportBinding.btnImport.setOnClickListener(onClickListener);
dialog[0] = new AlertDialog.Builder(context).setView(importExportBinding.getRoot()).show();
}
public static Map<String, String> sign(final Map<String, Object> form) { public static Map<String, String> sign(final Map<String, Object> form) {
final String signed = sign(new JSONObject(form).toString()); final String signed = sign(new JSONObject(form).toString());
if (signed == null) { if (signed == null) {
@ -249,4 +134,16 @@ public final class Utils {
} }
return simpleCache; return simpleCache;
} }
@Nullable
public static Pair<FavoriteType, String> migrateOldFavQuery(final String queryText) {
if (queryText.startsWith("@")) {
return new Pair<>(FavoriteType.USER, queryText.substring(1));
} else if (queryText.contains("/")) {
return new Pair<>(FavoriteType.LOCATION, queryText.substring(0, queryText.indexOf("/")));
} else if (queryText.startsWith("#")) {
return new Pair<>(FavoriteType.HASHTAG, queryText.substring(1));
}
return null;
}
} }

View File

@ -0,0 +1,18 @@
package awais.instagrabber.viewmodels;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import java.io.File;
import java.util.List;
public class FileListViewModel extends ViewModel {
private MutableLiveData<List<File>> list;
public MutableLiveData<List<File>> getList() {
if (list == null) {
list = new MutableLiveData<>();
}
return list;
}
}

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6L6,2zM13,9L13,3.5L18.5,9L13,9z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M10,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2h-8l-2,-2z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M14,12c0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2 0.9,2 2,2 2,-0.9 2,-2zM12,3c-4.97,0 -9,4.03 -9,9L0,12l4,4 4,-4L5,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.51,0 -2.91,-0.49 -4.06,-1.3l-1.42,1.44C8.04,20.3 9.94,21 12,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9z"/>
</vector>

View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingLeft="16dp"
android:paddingTop="16dp"
android:paddingRight="16dp"
android:paddingBottom="0dp">
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbExportSettings"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/dialog_export_settings" />
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbExportLogins"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dialog_export_accounts" />
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbExportFavorites"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dialog_export_favorites" />
<include layout="@layout/item_pref_divider" />
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbPassword"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/set_password" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/passwordField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password_no_max"
android:visibility="gone"
app:counterEnabled="true"
app:counterMaxLength="32"
app:endIconMode="password_toggle"
tools:visibility="visible">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints="no"
android:inputType="textPassword"
android:maxLength="2200"
android:scrollHorizontally="false"
tools:text="test" />
</com.google.android.material.textfield.TextInputLayout>
<include layout="@layout/item_pref_divider" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnSaveTo"
style="@style/Widget.MaterialComponents.Button.TextButton.Dialog.Flush"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="16dp"
android:text="@string/create_backup" />
</LinearLayout>

View File

@ -1,230 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
style="@style/TextAppearance.AppCompat.Headline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:paddingStart="?attr/dialogPreferredPadding"
android:paddingLeft="?attr/dialogPreferredPadding"
android:paddingTop="10dp"
android:paddingEnd="?attr/dialogPreferredPadding"
android:paddingRight="?attr/dialogPreferredPadding"
android:paddingBottom="6dp"
android:singleLine="true"
android:text="@string/import_export" />
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:background="?android:selectableItemBackground"
android:orientation="horizontal"
android:padding="5dp">
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbExportSettings"
android:layout_width="30dp"
android:layout_height="30dp"
android:checked="true" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:padding="5dp"
android:text="@string/dialog_export_settings"
android:textColor="?android:textColorPrimary"
android:textSize="16sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:background="?android:selectableItemBackground"
android:orientation="horizontal"
android:padding="5dp">
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbExportLogins"
android:layout_width="30dp"
android:layout_height="30dp" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:padding="5dp"
android:text="@string/dialog_export_logins"
android:textColor="?android:textColorPrimary"
android:textSize="16sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:background="?android:selectableItemBackground"
android:orientation="horizontal"
android:padding="5dp">
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbExportFavorites"
android:layout_width="30dp"
android:layout_height="30dp" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:padding="5dp"
android:text="@string/dialog_export_favorites"
android:textColor="?android:textColorPrimary"
android:textSize="16sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:background="?android:selectableItemBackground"
android:orientation="horizontal"
android:padding="5dp">
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbPassword"
android:layout_width="30dp"
android:layout_height="30dp" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:padding="5dp"
android:text="@string/password"
android:textColor="?android:textColorPrimary"
android:textSize="16sp" />
<include
android:id="@+id/etPassword"
layout="@layout/layout_password" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnSaveTo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:paddingStart="24dp"
android:paddingLeft="24dp"
android:paddingEnd="24dp"
android:paddingRight="24dp"
android:text="@string/dialog_export_btn_export" />
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_marginTop="8dp"
android:background="?android:attr/dividerVertical" />
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:background="?android:selectableItemBackground"
android:orientation="horizontal"
android:padding="5dp">
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbImportSettings"
android:layout_width="30dp"
android:layout_height="30dp"
android:checked="true" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:padding="5dp"
android:text="@string/dialog_import_settings"
android:textColor="?android:textColorPrimary"
android:textSize="16sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:background="?android:selectableItemBackground"
android:orientation="horizontal"
android:padding="5dp">
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbImportLogins"
android:layout_width="30dp"
android:layout_height="30dp"
android:checked="true" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:padding="5dp"
android:text="@string/dialog_import_logins"
android:textColor="?android:textColorPrimary"
android:textSize="16sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:background="?android:selectableItemBackground"
android:orientation="horizontal"
android:padding="5dp">
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbImportFavorites"
android:layout_width="30dp"
android:layout_height="30dp"
android:checked="true" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:padding="5dp"
android:text="@string/dialog_import_favorites"
android:textColor="?android:textColorPrimary"
android:textSize="16sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnImport"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_margin="8dp"
android:paddingStart="24dp"
android:paddingLeft="24dp"
android:paddingEnd="24dp"
android:paddingRight="24dp"
android:text="@string/dialog_export_btn_import" />
</LinearLayout>

View File

@ -0,0 +1,143 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingLeft="16dp"
android:paddingTop="16dp"
android:paddingRight="16dp"
android:paddingBottom="0dp">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/file_chosen_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/file_chosen_label"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
app:layout_constraintBottom_toTopOf="@id/file_path"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/file_path"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/file_chosen_label"
tools:text="file path file path file path file path file path file path file path file path file path file path file path " />
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbSettings"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/dialog_export_settings"
app:layout_constraintBottom_toTopOf="@id/cbAccounts"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/file_path" />
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbAccounts"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/dialog_export_accounts"
app:layout_constraintBottom_toTopOf="@id/cbFavorites"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cbSettings" />
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbFavorites"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/dialog_export_favorites"
app:layout_constraintBottom_toTopOf="@id/top_password_divider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cbAccounts" />
<include
android:id="@+id/top_password_divider"
layout="@layout/item_pref_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
app:layout_constraintBottom_toTopOf="@id/enter_password_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cbFavorites" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/enter_password_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="4dp"
android:text="@string/enter_password"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
app:layout_constraintBottom_toTopOf="@id/passwordField"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/top_password_divider" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/passwordField"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:hint="@string/password_no_max"
app:counterEnabled="true"
app:counterMaxLength="32"
app:endIconMode="password_toggle"
app:layout_constraintBottom_toTopOf="@id/bottom_password_divider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/enter_password_label">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints="no"
android:inputType="textPassword"
android:maxLength="2200"
android:scrollHorizontally="false"
tools:text="test" />
</com.google.android.material.textfield.TextInputLayout>
<include
android:id="@+id/bottom_password_divider"
layout="@layout/item_pref_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
app:layout_constraintBottom_toTopOf="@id/btn_restore"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/passwordField" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_restore"
style="@style/Widget.MaterialComponents.Button.TextButton.Dialog.Flush"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="16dp"
android:text="@string/restore_backup"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/bottom_password_divider" />
<androidx.constraintlayout.widget.Group
android:id="@+id/password_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="top_password_divider,bottom_password_divider,enter_password_label,passwordField"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,15 +1,31 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@android:id/text1"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?android:selectableItemBackground" android:background="?attr/selectableItemBackground"
android:gravity="center_vertical" android:minHeight="56dp">
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:paddingStart="?android:attr/listPreferredItemPaddingStart" <ImageView
android:paddingLeft="?android:attr/listPreferredItemPaddingLeft" android:id="@+id/icon"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" android:layout_width="wrap_content"
android:paddingRight="?android:attr/listPreferredItemPaddingRight" android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItemSmall" android:layout_gravity="center"
tools:viewBindingIgnore="true" /> android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:maxLines="1"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:textAppearance="?attr/textAppearanceSubtitle1"
tools:text="Line line" />
</LinearLayout>

View File

@ -1,116 +1,65 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<RelativeLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/footer" android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_alignParentBottom="true">
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_alignParentTop="true"
android:background="@android:color/darker_gray" />
<View
android:id="@+id/horizontalDivider"
android:layout_width="1dip"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:background="@android:color/darker_gray" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnCancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_alignParentBottom="true"
android:layout_toStartOf="@id/horizontalDivider"
android:layout_toLeftOf="@id/horizontalDivider"
android:background="?android:selectableItemBackground"
android:text="@string/cancel" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnConfirm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_toEndOf="@id/horizontalDivider"
android:layout_toRightOf="@id/horizontalDivider"
android:background="?android:selectableItemBackground"
android:text="@string/confirm" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/directoryInfo"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true"> app:layout_constraintBottom_toTopOf="@id/directoryList"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageButton <com.google.android.material.appbar.MaterialToolbar
android:id="@+id/btnNavUp" android:id="@+id/toolbar"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:background="?android:selectableItemBackground"
android:contentDescription="@string/nav_up"
android:padding="8dp"
android:scaleType="fitCenter"
app:srcCompat="@drawable/ic_arrow_upward_24" />
<TextView
android:id="@+id/txtvSelectedFolderLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginRight="8dp"
android:layout_toEndOf="@id/btnNavUp"
android:layout_toRightOf="@id/btnNavUp"
android:text="@string/selected_folder_label"
android:textStyle="bold" />
<TextView
android:id="@+id/txtvSelectedFolder"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/txtvSelectedFolderLabel" app:navigationIcon="@drawable/ic_arrow_upward_24"
android:layout_marginStart="8dp" tools:title="/this/that/thy" />
android:layout_marginLeft="8dp" </com.google.android.material.appbar.AppBarLayout>
android:layout_marginTop="4dp"
android:layout_marginBottom="8dp"
android:layout_toEndOf="@id/btnNavUp"
android:layout_toRightOf="@id/btnNavUp"
android:ellipsize="start"
android:scrollHorizontally="true"
android:singleLine="true" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_below="@id/btnNavUp"
android:background="@android:color/darker_gray" />
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/directoryList" android:id="@+id/directoryList"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_above="@id/footer" app:layout_constraintBottom_toTopOf="@id/bottom_horizontal_divider"
android:layout_below="@id/directoryInfo" /> app:layout_constraintEnd_toEndOf="parent"
</RelativeLayout> app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appBarLayout" />
<include
android:id="@+id/bottom_horizontal_divider"
layout="@layout/item_pref_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
app:layout_constraintBottom_toTopOf="@id/btnCancel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/directoryList" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnCancel"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cancel"
app:icon="@drawable/ic_close_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/directoryList" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnConfirm"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/confirm"
app:icon="@drawable/ic_check_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/directoryList" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -72,7 +72,8 @@
android:layout_marginLeft="15dip" android:layout_marginLeft="15dip"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal" android:orientation="horizontal"
android:visibility="gone"> android:visibility="gone"
tools:visibility="visible">
<androidx.appcompat.widget.AppCompatButton <androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnSaveTo" android:id="@+id/btnSaveTo"

View File

@ -68,6 +68,9 @@
<action <action
android:id="@+id/action_morePreferencesFragment_to_favoritesFragment" android:id="@+id/action_morePreferencesFragment_to_favoritesFragment"
app:destination="@id/favoritesFragment" /> app:destination="@id/favoritesFragment" />
<action
android:id="@+id/action_morePreferencesFragment_to_backupPreferencesFragment"
app:destination="@id/backupPreferencesFragment" />
</fragment> </fragment>
<fragment <fragment
android:id="@+id/settingsPreferencesFragment" android:id="@+id/settingsPreferencesFragment"
@ -94,4 +97,8 @@
android:id="@+id/favoritesFragment" android:id="@+id/favoritesFragment"
android:name="awais.instagrabber.fragments.FavoritesFragment" android:name="awais.instagrabber.fragments.FavoritesFragment"
android:label="@string/title_favorites" /> android:label="@string/title_favorites" />
<fragment
android:id="@+id/backupPreferencesFragment"
android:name="awais.instagrabber.fragments.settings.BackupPreferencesFragment"
android:label="@string/backup_and_restore" />
</navigation> </navigation>

View File

@ -18,6 +18,8 @@
<string name="clipboard_copied">Copied to clipboard!</string> <string name="clipboard_copied">Copied to clipboard!</string>
<string name="report">Report</string> <string name="report">Report</string>
<string name="password">Password (Max 32 chars)</string> <string name="password">Password (Max 32 chars)</string>
<string name="set_password">Set a password (max 32 chars)</string>
<string name="password_no_max">Password</string>
<string name="ok">OK</string> <string name="ok">OK</string>
<string name="yes">Yes</string> <string name="yes">Yes</string>
<string name="cancel">Cancel</string> <string name="cancel">Cancel</string>
@ -25,7 +27,7 @@
<string name="confirm">Confirm</string> <string name="confirm">Confirm</string>
<string name="nav_up">Up</string> <string name="nav_up">Up</string>
<string name="dont_show_again">Don\'t Show Again</string> <string name="dont_show_again">Don\'t Show Again</string>
<string name="selected_folder_label">Selected folder:</string> <string name="selected_folder_label">Current directory</string>
<string name="title_favorites">Favorites</string> <string name="title_favorites">Favorites</string>
<string name="title_discover">Discover</string> <string name="title_discover">Discover</string>
<string name="title_comments">Comments</string> <string name="title_comments">Comments</string>
@ -126,16 +128,18 @@
<string name="dialog_export_btn_export">Export</string> <string name="dialog_export_btn_export">Export</string>
<string name="dialog_export_btn_import">Import</string> <string name="dialog_export_btn_import">Import</string>
<string name="dialog_export_logins">Export Logins</string> <string name="dialog_export_logins">Export Logins</string>
<string name="dialog_export_settings">Export Settings</string> <string name="dialog_export_accounts">Accounts</string>
<string name="dialog_export_favorites">Export Favorites</string> <string name="dialog_export_settings">Settings</string>
<string name="dialog_import_settings">Import Settings</string> <string name="dialog_export_favorites">Favorites</string>
<string name="dialog_import_settings">Import settings</string>
<string name="dialog_import_logins">Import Logins</string> <string name="dialog_import_logins">Import Logins</string>
<string name="dialog_import_favorites">Import Favorites</string> <string name="dialog_import_accounts">Import accounts</string>
<string name="dialog_import_favorites">Import favorites</string>
<string name="dialog_import_success">Successfully imported!</string> <string name="dialog_import_success">Successfully imported!</string>
<string name="dialog_import_failed">Failed to import!</string> <string name="dialog_import_failed">Failed to import!</string>
<string name="dialog_export_success">Successfully exported!</string> <string name="dialog_export_success">Successfully exported!</string>
<string name="dialog_export_failed">Failed to export!</string> <string name="dialog_export_failed">Failed to export!</string>
<string name="dialog_export_err_password_empty">Password is empty! Password cannot be empt, dumbass!</string> <string name="dialog_export_err_password_empty">Password is empty!</string>
<string name="refresh">Refresh</string> <string name="refresh">Refresh</string>
<string name="get_cookies">Get cookies</string> <string name="get_cookies">Get cookies</string>
<string name="desktop_2fa">Desktop Mode</string> <string name="desktop_2fa">Desktop Mode</string>
@ -292,4 +296,10 @@
<string name="locations">Locations</string> <string name="locations">Locations</string>
<string name="unknown">Unknown</string> <string name="unknown">Unknown</string>
<string name="removed_from_favs">Removed from Favourites</string> <string name="removed_from_favs">Removed from Favourites</string>
<string name="backup_and_restore">Backup &amp; Restore</string>
<string name="create_backup">Create</string>
<string name="restore_backup">Restore</string>
<string name="file_chosen_label">File:</string>
<string name="enter_password">Enter password</string>
<string name="select_backup_file">Select a backup file (.zaai/.backup)</string>
</resources> </resources>

View File

@ -57,7 +57,11 @@
</style> </style>
<style name="Widget.MaterialComponents.Button.Light.White" parent="Widget.MaterialComponents.Button"> <style name="Widget.MaterialComponents.Button.Light.White" parent="Widget.MaterialComponents.Button">
<item name="materialThemeOverlay">@style/ThemeOverlay.Button.Dark.Black</item> <item name="materialThemeOverlay">@style/ThemeOverlay.Button.Light.White</item>
</style>
<style name="ThemeOverlay.Button.Light.White" parent="">
<item name="colorPrimary">@color/black</item>
</style> </style>
<style name="Widget.MaterialComponents.Button.Dark.Black" parent="Widget.MaterialComponents.Button"> <style name="Widget.MaterialComponents.Button.Dark.Black" parent="Widget.MaterialComponents.Button">
@ -110,12 +114,6 @@
</style> </style>
<style name="ThemeOverlay.MaterialComponents.MaterialAlertDialog.Light" parent="ThemeOverlay.MaterialComponents.MaterialAlertDialog"> <style name="ThemeOverlay.MaterialComponents.MaterialAlertDialog.Light" parent="ThemeOverlay.MaterialComponents.MaterialAlertDialog">
<!--<item name="colorPrimary">?attr/colorPrimaryDark</item>-->
<!--<item name="colorSecondary">?attr/colorSecondaryVariant</item>-->
<!--<item name="colorSurface">@color/shrine_pink_light</item>-->
<!--<item name="colorOnSurface">@color/shrine_pink_900</item>-->
<!--<item name="materialAlertDialogTitleTextStyle">@style/MaterialAlertDialog.App.Title.Text</item>-->
<!--<item name="colorPrimary">?attr/colorPrimaryDark</item>-->
<item name="buttonBarPositiveButtonStyle">@style/Widget.MaterialComponents.Button.TextButton.Dialog.Light</item> <item name="buttonBarPositiveButtonStyle">@style/Widget.MaterialComponents.Button.TextButton.Dialog.Light</item>
<item name="buttonBarNeutralButtonStyle">@style/Widget.MaterialComponents.Button.TextButton.Dialog.Light</item> <item name="buttonBarNeutralButtonStyle">@style/Widget.MaterialComponents.Button.TextButton.Dialog.Light</item>
<item name="buttonBarNegativeButtonStyle">@style/Widget.MaterialComponents.Button.TextButton.Dialog.Light</item> <item name="buttonBarNegativeButtonStyle">@style/Widget.MaterialComponents.Button.TextButton.Dialog.Light</item>
@ -128,4 +126,15 @@
<style name="ThemeOverlay.MaterialComponents.Button.TextButton.Light" parent=""> <style name="ThemeOverlay.MaterialComponents.Button.TextButton.Light" parent="">
<item name="colorPrimary">?attr/colorPrimaryDark</item> <item name="colorPrimary">?attr/colorPrimaryDark</item>
</style> </style>
<style name="Widget.MaterialComponents.TextInputLayout.OutlinedBox.Light.White" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<item name="materialThemeOverlay">@style/ThemeOverlay.MaterialComponents.TextInputEditText.OutlinedBox.Light.White</item>
<item name="colorPrimary">@color/black</item>
<item name="colorOnSurface">@color/black</item>
</style>
<style name="ThemeOverlay.MaterialComponents.TextInputEditText.OutlinedBox.Light.White" parent="ThemeOverlay.MaterialComponents.TextInputEditText.OutlinedBox">
<item name="colorPrimary">@color/black</item>
<item name="colorOnSurface">@color/black</item>
</style>
</resources> </resources>

View File

@ -38,6 +38,7 @@
<item name="android:windowBackground">@color/white</item> <item name="android:windowBackground">@color/white</item>
<item name="bottomNavigationStyle">@style/Widget.BottomNavigationView.Light.White</item> <item name="bottomNavigationStyle">@style/Widget.BottomNavigationView.Light.White</item>
<item name="materialButtonStyle">@style/Widget.MaterialComponents.Button.Light.White</item> <item name="materialButtonStyle">@style/Widget.MaterialComponents.Button.Light.White</item>
<item name="textInputStyle">@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Light.White</item>
</style> </style>
<style name="AppTheme.Light.Barinsta" parent="AppTheme.Light"> <style name="AppTheme.Light.Barinsta" parent="AppTheme.Light">