mirror of
https://github.com/KokaKiwi/BarInsta
synced 2024-11-22 14:47: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:
parent
056604d9bb
commit
05c0937c6f
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -73,7 +73,14 @@ public final class DirectMessageInboxItemViewHolder extends RecyclerView.ViewHol
|
||||
}
|
||||
}
|
||||
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();
|
||||
// binding.notTextType.setVisibility(itemType != DirectItemType.TEXT ? View.VISIBLE : View.GONE);
|
||||
final Context context = itemView.getContext();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -157,7 +157,7 @@ public class FavoritesFragment extends Fragment {
|
||||
result.getSdProfilePic(),
|
||||
model.getDateAdded()
|
||||
);
|
||||
Utils.dataBox.addFavorite(updated);
|
||||
Utils.dataBox.addOrUpdateFavorite(updated);
|
||||
updatedList.add(i, updated);
|
||||
} finally {
|
||||
try {
|
||||
@ -186,7 +186,7 @@ public class FavoritesFragment extends Fragment {
|
||||
result.getSdProfilePic(),
|
||||
model.getDateAdded()
|
||||
);
|
||||
Utils.dataBox.addFavorite(updated);
|
||||
Utils.dataBox.addOrUpdateFavorite(updated);
|
||||
updatedList.add(i, updated);
|
||||
} finally {
|
||||
try {
|
||||
|
@ -369,7 +369,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
|
||||
binding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24);
|
||||
message = getString(R.string.removed_from_favs);
|
||||
} else {
|
||||
Utils.dataBox.addFavorite(new DataBox.FavoriteModel(
|
||||
Utils.dataBox.addOrUpdateFavorite(new DataBox.FavoriteModel(
|
||||
-1,
|
||||
hashtag.substring(1),
|
||||
FavoriteType.HASHTAG,
|
||||
|
@ -364,7 +364,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
|
||||
binding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24);
|
||||
message = getString(R.string.removed_from_favs);
|
||||
} else {
|
||||
Utils.dataBox.addFavorite(new DataBox.FavoriteModel(
|
||||
Utils.dataBox.addOrUpdateFavorite(new DataBox.FavoriteModel(
|
||||
-1,
|
||||
locationId,
|
||||
FavoriteType.LOCATION,
|
||||
|
@ -741,7 +741,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
|
||||
profileModel.getSdProfilePic(),
|
||||
new Date()
|
||||
);
|
||||
Utils.dataBox.addFavorite(model);
|
||||
Utils.dataBox.addOrUpdateFavorite(model);
|
||||
binding.favCb.setButtonDrawable(R.drawable.ic_star_check_24);
|
||||
message = getString(R.string.added_to_favs);
|
||||
} else {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -21,7 +21,7 @@ public abstract class BasePreferencesFragment extends PreferenceFragmentCompat i
|
||||
@Override
|
||||
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
|
||||
final PreferenceManager preferenceManager = getPreferenceManager();
|
||||
preferenceManager.setSharedPreferencesName("settings");
|
||||
preferenceManager.setSharedPreferencesName(Constants.SHARED_PREFERENCES_NAME);
|
||||
preferenceManager.getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
|
||||
final Context context = getContext();
|
||||
if (context == null) return;
|
||||
|
@ -20,7 +20,7 @@ import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import awais.instagrabber.BuildConfig;
|
||||
import awais.instagrabber.R;
|
||||
@ -55,7 +55,7 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
|
||||
accountCategory.setTitle(R.string.account);
|
||||
accountCategory.setIconSpaceReserved(false);
|
||||
screen.addPreference(accountCategory);
|
||||
final ArrayList<DataBox.CookieModel> allCookies = Utils.dataBox.getAllCookies();
|
||||
final List<DataBox.CookieModel> allCookies = Utils.dataBox.getAllCookies();
|
||||
if (isLoggedIn) {
|
||||
accountCategory.setSummary(R.string.account_hint);
|
||||
accountCategory.addPreference(getAccountSwitcherPreference(cookie));
|
||||
@ -120,6 +120,11 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
|
||||
NavHostFragment.findNavController(this).navigate(navDirections);
|
||||
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 -> {
|
||||
final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToAboutFragment();
|
||||
NavHostFragment.findNavController(this).navigate(navDirections);
|
||||
|
@ -180,9 +180,9 @@ public class SettingsPreferencesFragment extends BasePreferencesFragment {
|
||||
if (context == null) return null;
|
||||
return new SaveToCustomFolderPreference(context, (resultCallback) -> new DirectoryChooser()
|
||||
.setInitialDirectory(settingsHelper.getString(FOLDER_PATH))
|
||||
.setInteractionListener(path -> {
|
||||
settingsHelper.putString(FOLDER_PATH, path);
|
||||
resultCallback.onResult(path);
|
||||
.setInteractionListener(file -> {
|
||||
settingsHelper.putString(FOLDER_PATH, file.getAbsolutePath());
|
||||
resultCallback.onResult(file.getAbsolutePath());
|
||||
})
|
||||
.show(getParentFragmentManager(), null));
|
||||
}
|
||||
|
@ -82,4 +82,5 @@ public final class Constants {
|
||||
public static final String PREF_DARK_THEME = "dark_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 SHARED_PREFERENCES_NAME = "settings";
|
||||
}
|
@ -6,6 +6,7 @@ import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@ -95,7 +96,7 @@ public final class DataBox extends SQLiteOpenHelper {
|
||||
+ FAV_COL_DATE_ADDED + " INTEGER)");
|
||||
// add the old favorites back
|
||||
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));
|
||||
@ -117,18 +118,10 @@ public final class DataBox extends SQLiteOpenHelper {
|
||||
do {
|
||||
try {
|
||||
final String queryText = cursor.getString(cursor.getColumnIndex("query_text"));
|
||||
FavoriteType type = null;
|
||||
String query = null;
|
||||
if (queryText.startsWith("@")) {
|
||||
type = FavoriteType.USER;
|
||||
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);
|
||||
}
|
||||
final Pair<FavoriteType, String> favoriteTypeQueryPair = Utils.migrateOldFavQuery(queryText);
|
||||
if (favoriteTypeQueryPair == null) continue;
|
||||
final FavoriteType type = favoriteTypeQueryPair.first;
|
||||
final String query = favoriteTypeQueryPair.second;
|
||||
oldModels.add(new FavoriteModel(
|
||||
-1,
|
||||
query,
|
||||
@ -170,20 +163,20 @@ public final class DataBox extends SQLiteOpenHelper {
|
||||
return exists;
|
||||
}
|
||||
|
||||
public final void addFavorite(@NonNull final FavoriteModel model) {
|
||||
public final void addOrUpdateFavorite(@NonNull final FavoriteModel model) {
|
||||
final String query = model.getQuery();
|
||||
if (!TextUtils.isEmpty(query)) {
|
||||
try (final SQLiteDatabase db = getWritableDatabase()) {
|
||||
db.beginTransaction();
|
||||
try {
|
||||
addFavorite(db, model);
|
||||
addOrUpdateFavorite(db, model);
|
||||
db.setTransactionSuccessful();
|
||||
} catch (final Exception e) {
|
||||
if (logCollector != null) {
|
||||
logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "addFavorite");
|
||||
logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "addOrUpdateFavorite");
|
||||
}
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.e(TAG, "", e);
|
||||
Log.e(TAG, "Error adding/updating favorite", e);
|
||||
}
|
||||
} finally {
|
||||
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();
|
||||
values.put(FAV_COL_QUERY, model.getQuery());
|
||||
values.put(FAV_COL_TYPE, model.getType().toString());
|
||||
values.put(FAV_COL_DISPLAY_NAME, model.getDisplayName());
|
||||
values.put(FAV_COL_PIC_URL, model.getPicUrl());
|
||||
values.put(FAV_COL_DATE_ADDED, model.getDateAdded().getTime());
|
||||
int rows = 0;
|
||||
int rows;
|
||||
if (model.getId() >= 1) {
|
||||
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) {
|
||||
db.insertOrThrow(TABLE_FAVORITES, null, values);
|
||||
@ -304,6 +303,16 @@ public final class DataBox extends SQLiteOpenHelper {
|
||||
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,
|
||||
final String username,
|
||||
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
|
||||
public final CookieModel getCookie(final String uid) {
|
||||
CookieModel cookie = null;
|
||||
@ -404,10 +404,9 @@ public final class DataBox extends SQLiteOpenHelper {
|
||||
return cookie;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public final ArrayList<CookieModel> getAllCookies() {
|
||||
ArrayList<CookieModel> cookies = null;
|
||||
|
||||
@NonNull
|
||||
public final List<CookieModel> getAllCookies() {
|
||||
final List<CookieModel> cookies = new ArrayList<>();
|
||||
try (final SQLiteDatabase db = getReadableDatabase();
|
||||
final Cursor cursor = db.rawQuery(
|
||||
"SELECT "
|
||||
@ -419,7 +418,6 @@ public final class DataBox extends SQLiteOpenHelper {
|
||||
+ " FROM " + TABLE_COOKIES, null)
|
||||
) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
cookies = new ArrayList<>();
|
||||
do {
|
||||
cookies.add(new CookieModel(
|
||||
cursor.getString(cursor.getColumnIndex(KEY_UID)),
|
||||
@ -431,7 +429,6 @@ public final class DataBox extends SQLiteOpenHelper {
|
||||
} while (cursor.moveToNext());
|
||||
}
|
||||
}
|
||||
|
||||
return cookies;
|
||||
}
|
||||
|
||||
@ -483,6 +480,12 @@ public final class DataBox extends SQLiteOpenHelper {
|
||||
this.selected = selected;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return !TextUtils.isEmpty(uid)
|
||||
&& !TextUtils.isEmpty(username)
|
||||
&& !TextUtils.isEmpty(cookie);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) return true;
|
||||
@ -501,7 +504,14 @@ public final class DataBox extends SQLiteOpenHelper {
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return username;
|
||||
return "CookieModel{" +
|
||||
"uid='" + uid + '\'' +
|
||||
", username='" + username + '\'' +
|
||||
", cookie='" + cookie + '\'' +
|
||||
", fullName='" + fullName + '\'' +
|
||||
", profilePic='" + profilePic + '\'' +
|
||||
", selected=" + selected +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,20 +3,26 @@ package awais.instagrabber.utils;
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.FileObserver;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
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.util.ArrayList;
|
||||
@ -24,22 +30,27 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
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 {
|
||||
private static final String TAG = "DirectoryChooser";
|
||||
|
||||
public static final String KEY_CURRENT_DIRECTORY = "CURRENT_DIRECTORY";
|
||||
private static final File sdcardPathFile = Environment.getExternalStorageDirectory();
|
||||
private static final String sdcardPath = sdcardPathFile.getPath();
|
||||
private final List<String> fileNames = new ArrayList<>();
|
||||
|
||||
private Context context;
|
||||
private View btnConfirm, btnNavUp, btnCancel;
|
||||
private LayoutDirectoryChooserBinding binding;
|
||||
private FileObserver fileObserver;
|
||||
private File selectedDir;
|
||||
private String initialDirectory;
|
||||
private TextView tvSelectedFolder;
|
||||
private FileObserver fileObserver;
|
||||
private SimpleAdapter<String> listDirectoriesAdapter;
|
||||
private OnFragmentInteractionListener interactionListener;
|
||||
private boolean showZaAiConfigFiles = false;
|
||||
private boolean showBackupFiles = false;
|
||||
private View.OnClickListener navigationOnClickListener;
|
||||
private FileListViewModel fileListViewModel;
|
||||
private OnCancelListener onCancelListener;
|
||||
|
||||
public DirectoryChooser() {
|
||||
super();
|
||||
@ -51,8 +62,8 @@ public final class DirectoryChooser extends DialogFragment {
|
||||
return this;
|
||||
}
|
||||
|
||||
public DirectoryChooser setShowZaAiConfigFiles(final boolean showZaAiConfigFiles) {
|
||||
this.showZaAiConfigFiles = showZaAiConfigFiles;
|
||||
public DirectoryChooser setShowBackupFiles(final boolean showBackupFiles) {
|
||||
this.showBackupFiles = showBackupFiles;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -74,60 +85,71 @@ public final class DirectoryChooser extends DialogFragment {
|
||||
@NonNull
|
||||
@Override
|
||||
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;
|
||||
if (context == null) context = getContext();
|
||||
if (context == null) context = getActivity();
|
||||
if (context == null) context = inflater.getContext();
|
||||
|
||||
final View view = inflater.inflate(R.layout.layout_directory_chooser, container, false);
|
||||
|
||||
btnNavUp = view.findViewById(R.id.btnNavUp);
|
||||
btnCancel = view.findViewById(R.id.btnCancel);
|
||||
btnConfirm = view.findViewById(R.id.btnConfirm);
|
||||
tvSelectedFolder = view.findViewById(R.id.txtvSelectedFolder);
|
||||
|
||||
if (context == null) return;
|
||||
if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) != PackageManager.PERMISSION_GRANTED) {
|
||||
final String text = "Storage permissions denied!";
|
||||
if (container == null) {
|
||||
Toast.makeText(context, text, Toast.LENGTH_LONG).show();
|
||||
} else {
|
||||
Snackbar.make(container, text, BaseTransientBottomBar.LENGTH_LONG).show();
|
||||
}
|
||||
dismiss();
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
} else if (v == btnNavUp) {
|
||||
final File parent;
|
||||
if (selectedDir != null && (parent = selectedDir.getParentFile()) != null)
|
||||
changeDirectory(parent);
|
||||
|
||||
} else if (v == btnConfirm) {
|
||||
if (v == binding.btnConfirm) {
|
||||
if (interactionListener != null && isValidFile(selectedDir))
|
||||
interactionListener.onSelectDirectory(selectedDir.getAbsolutePath());
|
||||
interactionListener.onSelectDirectory(selectedDir);
|
||||
dismiss();
|
||||
} else if (v == btnCancel) {
|
||||
} else if (v == binding.btnCancel) {
|
||||
if (onCancelListener != null) {
|
||||
onCancelListener.onCancel();
|
||||
}
|
||||
dismiss();
|
||||
}
|
||||
};
|
||||
|
||||
btnNavUp.setOnClickListener(clickListener);
|
||||
btnCancel.setOnClickListener(clickListener);
|
||||
btnConfirm.setOnClickListener(clickListener);
|
||||
|
||||
listDirectoriesAdapter = new SimpleAdapter<>(context, fileNames, clickListener);
|
||||
|
||||
final RecyclerView directoriesList = view.findViewById(R.id.directoryList);
|
||||
directoriesList.setLayoutManager(new LinearLayoutManager(context));
|
||||
directoriesList.setAdapter(listDirectoriesAdapter);
|
||||
|
||||
navigationOnClickListener = v -> {
|
||||
final File parent;
|
||||
if (selectedDir != null && (parent = selectedDir.getParentFile()) != null) {
|
||||
changeDirectory(parent);
|
||||
}
|
||||
};
|
||||
binding.toolbar.setNavigationOnClickListener(navigationOnClickListener);
|
||||
binding.toolbar.setSubtitle(showBackupFiles ? R.string.select_backup_file : R.string.select_folder);
|
||||
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 initialDir = !TextUtils.isEmpty(initialDirectory) && isValidFile(initDir) ? initDir : Environment.getExternalStorageDirectory();
|
||||
|
||||
changeDirectory(initialDir);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -153,10 +175,14 @@ public final class DirectoryChooser extends DialogFragment {
|
||||
public void onBackPressed() {
|
||||
if (selectedDir != null) {
|
||||
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();
|
||||
else
|
||||
} else {
|
||||
changeDirectory(selectedDir.getParentFile());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -189,21 +215,28 @@ public final class DirectoryChooser extends DialogFragment {
|
||||
private void changeDirectory(final File dir) {
|
||||
if (dir != null && dir.isDirectory()) {
|
||||
final String path = dir.getAbsolutePath();
|
||||
|
||||
binding.toolbar.setTitle(path);
|
||||
final File[] contents = dir.listFiles();
|
||||
if (contents != null) {
|
||||
fileNames.clear();
|
||||
|
||||
final List<File> fileNames = new ArrayList<>();
|
||||
for (final File f : contents) {
|
||||
final String name = f.getName();
|
||||
if (f.isDirectory() || showZaAiConfigFiles && f.isFile() && name.toLowerCase().endsWith(".zaai"))
|
||||
fileNames.add(name);
|
||||
final String nameLowerCase = name.toLowerCase();
|
||||
final boolean isBackupFile = nameLowerCase.endsWith(".zaai") || nameLowerCase.endsWith(".backup");
|
||||
if (f.isDirectory() || (showBackupFiles && f.isFile() && isBackupFile))
|
||||
fileNames.add(f);
|
||||
}
|
||||
|
||||
Collections.sort(fileNames);
|
||||
Collections.sort(fileNames, (o1, o2) -> {
|
||||
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;
|
||||
tvSelectedFolder.setText(path);
|
||||
listDirectoriesAdapter.notifyDataSetChanged();
|
||||
fileObserver = new FileObserver(path, FileObserver.CREATE | FileObserver.DELETE | FileObserver.MOVED_FROM | FileObserver.MOVED_TO) {
|
||||
private final Runnable currentDirRefresher = () -> changeDirectory(selectedDir);
|
||||
|
||||
@ -222,15 +255,15 @@ public final class DirectoryChooser extends DialogFragment {
|
||||
if (selectedDir != null) {
|
||||
final String path = selectedDir.getAbsolutePath();
|
||||
toggleUpButton(!path.equals(sdcardPathFile.getAbsolutePath()) && selectedDir != sdcardPathFile);
|
||||
btnConfirm.setEnabled(isValidFile(selectedDir));
|
||||
binding.btnConfirm.setEnabled(isValidFile(selectedDir));
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleUpButton(final boolean enable) {
|
||||
if (btnNavUp != null) {
|
||||
btnNavUp.setEnabled(enable);
|
||||
btnNavUp.setAlpha(enable ? 1f : 0.617f);
|
||||
}
|
||||
binding.toolbar.setNavigationOnClickListener(enable ? navigationOnClickListener : null);
|
||||
final Drawable navigationIcon = binding.toolbar.getNavigationIcon();
|
||||
if (navigationIcon == null) return;
|
||||
navigationIcon.setAlpha(enable ? 255 : (int) (255 * 0.617));
|
||||
}
|
||||
|
||||
private boolean isValidFile(final File file) {
|
||||
@ -242,7 +275,17 @@ public final class DirectoryChooser extends DialogFragment {
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setOnCancelListener(final OnCancelListener onCancelListener) {
|
||||
if (onCancelListener != null) {
|
||||
this.onCancelListener = onCancelListener;
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnCancelListener {
|
||||
void onCancel();
|
||||
}
|
||||
|
||||
public interface OnFragmentInteractionListener {
|
||||
void onSelectDirectory(final String path);
|
||||
void onSelectDirectory(final File file);
|
||||
}
|
||||
}
|
@ -37,7 +37,9 @@ import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
|
||||
public final class DownloadUtils {
|
||||
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) {
|
||||
if (Utils.settingsHelper == null) Utils.settingsHelper = new SettingsHelper(context);
|
||||
|
||||
|
@ -1,35 +1,32 @@
|
||||
package awais.instagrabber.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.InputFilter;
|
||||
import android.text.InputType;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.AppCompatEditText;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.util.Map;
|
||||
|
||||
import awais.instagrabber.BuildConfig;
|
||||
import awais.instagrabber.R;
|
||||
import awais.instagrabber.interfaces.FetchListener;
|
||||
import awais.instagrabber.models.enums.FavoriteType;
|
||||
import awais.instagrabber.utils.PasswordUtils.IncorrectPasswordException;
|
||||
import awaisomereport.LogCollector.LogFile;
|
||||
|
||||
import static awais.instagrabber.utils.Utils.logCollector;
|
||||
@ -45,90 +42,86 @@ public final class ExportImportUtils {
|
||||
@IntDef(value = {FLAG_COOKIES, FLAG_FAVORITES, FLAG_SETTINGS}, flag = true)
|
||||
@interface ExportImportFlags {}
|
||||
|
||||
public static void Export(@Nullable final String password, @ExportImportFlags final int flags, @NonNull final File filePath,
|
||||
final FetchListener<Boolean> fetchListener) {
|
||||
final String exportString = ExportImportUtils.getExportString(flags);
|
||||
if (!TextUtils.isEmpty(exportString)) {
|
||||
final boolean isPass = !TextUtils.isEmpty(password);
|
||||
byte[] exportBytes = null;
|
||||
|
||||
if (isPass) {
|
||||
final byte[] passwordBytes = password.getBytes();
|
||||
final byte[] bytes = new byte[32];
|
||||
System.arraycopy(passwordBytes, 0, bytes, 0, Math.min(passwordBytes.length, 32));
|
||||
|
||||
try {
|
||||
exportBytes = PasswordUtils.enc(exportString, bytes);
|
||||
} catch (final Exception e) {
|
||||
if (fetchListener != null) fetchListener.onResult(false);
|
||||
if (logCollector != null)
|
||||
logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::isPass");
|
||||
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
|
||||
}
|
||||
} else {
|
||||
exportBytes = Base64.encode(exportString.getBytes(), Base64.DEFAULT | Base64.NO_WRAP | Base64.NO_PADDING);
|
||||
public static void exportData(@Nullable final String password,
|
||||
@ExportImportFlags final int flags,
|
||||
@NonNull final File filePath,
|
||||
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);
|
||||
byte[] exportBytes = null;
|
||||
if (isPass) {
|
||||
final byte[] passwordBytes = password.getBytes();
|
||||
final byte[] bytes = new byte[32];
|
||||
System.arraycopy(passwordBytes, 0, bytes, 0, Math.min(passwordBytes.length, 32));
|
||||
try {
|
||||
exportBytes = PasswordUtils.enc(exportString, bytes);
|
||||
} catch (final Exception e) {
|
||||
if (fetchListener != null) fetchListener.onResult(false);
|
||||
if (logCollector != null)
|
||||
logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::isPass");
|
||||
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
|
||||
}
|
||||
|
||||
if (exportBytes != null && exportBytes.length > 1) {
|
||||
try (final FileOutputStream fos = new FileOutputStream(filePath)) {
|
||||
fos.write(isPass ? 'A' : 'Z');
|
||||
fos.write(exportBytes);
|
||||
if (fetchListener != null) fetchListener.onResult(true);
|
||||
} catch (final Exception e) {
|
||||
if (fetchListener != null) fetchListener.onResult(false);
|
||||
if (logCollector != null)
|
||||
logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::notPass");
|
||||
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
|
||||
}
|
||||
} else if (fetchListener != null) fetchListener.onResult(false);
|
||||
} else {
|
||||
exportBytes = Base64.encode(exportString.getBytes(), Base64.DEFAULT | Base64.NO_WRAP | Base64.NO_PADDING);
|
||||
}
|
||||
if (exportBytes != null && exportBytes.length > 1) {
|
||||
try (final FileOutputStream fos = new FileOutputStream(filePath)) {
|
||||
fos.write(isPass ? 'A' : 'Z');
|
||||
fos.write(exportBytes);
|
||||
if (fetchListener != null) fetchListener.onResult(true);
|
||||
} catch (final Exception e) {
|
||||
if (fetchListener != null) fetchListener.onResult(false);
|
||||
if (logCollector != null)
|
||||
logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::notPass");
|
||||
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
|
||||
}
|
||||
} else if (fetchListener != null) fetchListener.onResult(false);
|
||||
}
|
||||
|
||||
public static void Import(@NonNull final Context context, @ExportImportFlags final int flags, @NonNull final File filePath,
|
||||
final FetchListener<Boolean> fetchListener) {
|
||||
try (final FileInputStream fis = new FileInputStream(filePath)) {
|
||||
public static void importData(@NonNull final Context context,
|
||||
@ExportImportFlags final int flags,
|
||||
@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 StringBuilder builder = new StringBuilder();
|
||||
int c;
|
||||
while ((c = fis.read()) != -1) {
|
||||
builder.append((char) c);
|
||||
}
|
||||
|
||||
if (configType == 'A') {
|
||||
// password
|
||||
final AppCompatEditText editText = new AppCompatEditText(context);
|
||||
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 {
|
||||
final byte[] passwordBytes = text.toString().getBytes();
|
||||
final byte[] bytes = new byte[32];
|
||||
System.arraycopy(passwordBytes, 0, bytes, 0, Math.min(passwordBytes.length, 32));
|
||||
saveToSettings(new String(PasswordUtils.dec(builder.toString(), bytes)), flags,
|
||||
fetchListener);
|
||||
} catch (final Exception e) {
|
||||
if (fetchListener != null) fetchListener.onResult(false);
|
||||
if (logCollector != null)
|
||||
logCollector.appendException(e, LogFile.UTILS_IMPORT, "Import::pass");
|
||||
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
|
||||
}
|
||||
|
||||
} else
|
||||
Toast.makeText(context, R.string.dialog_export_err_password_empty, Toast.LENGTH_SHORT).show();
|
||||
}).show();
|
||||
|
||||
if (TextUtils.isEmpty(password)) return;
|
||||
try {
|
||||
final byte[] passwordBytes = password.getBytes();
|
||||
final byte[] bytes = new byte[32];
|
||||
System.arraycopy(passwordBytes, 0, bytes, 0, Math.min(passwordBytes.length, 32));
|
||||
importJson(new String(PasswordUtils.dec(builder.toString(), bytes)),
|
||||
flags,
|
||||
fetchListener);
|
||||
} catch (final IncorrectPasswordException e) {
|
||||
throw e;
|
||||
} catch (final Exception e) {
|
||||
if (fetchListener != null) fetchListener.onResult(false);
|
||||
if (logCollector != null)
|
||||
logCollector.appendException(e, LogFile.UTILS_IMPORT, "Import::pass");
|
||||
if (BuildConfig.DEBUG) Log.e(TAG, "Error importing backup", e);
|
||||
}
|
||||
} else if (configType == 'Z') {
|
||||
saveToSettings(new String(Base64.decode(builder.toString(), Base64.DEFAULT | Base64.NO_PADDING | Base64.NO_WRAP)),
|
||||
flags, fetchListener);
|
||||
importJson(new String(Base64.decode(builder.toString(), Base64.DEFAULT | Base64.NO_PADDING | Base64.NO_WRAP)),
|
||||
flags,
|
||||
fetchListener);
|
||||
|
||||
} 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);
|
||||
}
|
||||
} catch (IncorrectPasswordException e) {
|
||||
// separately handle incorrect password
|
||||
throw e;
|
||||
} catch (final Exception e) {
|
||||
if (fetchListener != null) fetchListener.onResult(false);
|
||||
if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_IMPORT, "Import");
|
||||
@ -136,80 +129,125 @@ 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 {
|
||||
final JSONObject jsonObject = new JSONObject(json);
|
||||
|
||||
if ((flags & FLAG_SETTINGS) == FLAG_SETTINGS && jsonObject.has("settings")) {
|
||||
final JSONObject objSettings = jsonObject.getJSONObject("settings");
|
||||
final Iterator<String> keys = objSettings.keys();
|
||||
while (keys.hasNext()) {
|
||||
final String key = keys.next();
|
||||
final Object val = objSettings.opt(key);
|
||||
if (val instanceof String) {
|
||||
settingsHelper.putString(key, (String) val);
|
||||
} else if (val instanceof Integer) {
|
||||
settingsHelper.putInteger(key, (int) val);
|
||||
} else if (val instanceof Boolean) {
|
||||
settingsHelper.putBoolean(key, (boolean) val);
|
||||
}
|
||||
}
|
||||
importSettings(jsonObject);
|
||||
}
|
||||
|
||||
if ((flags & FLAG_COOKIES) == FLAG_COOKIES && jsonObject.has("cookies")) {
|
||||
final JSONArray cookies = jsonObject.getJSONArray("cookies");
|
||||
final int cookiesLen = cookies.length();
|
||||
for (int i = 0; i < cookiesLen; ++i) {
|
||||
final JSONObject cookieObject = cookies.getJSONObject(i);
|
||||
// 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());
|
||||
}
|
||||
importAccounts(jsonObject);
|
||||
}
|
||||
|
||||
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")));
|
||||
}
|
||||
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, "saveToSettings");
|
||||
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 Iterator<String> keys = objSettings.keys();
|
||||
while (keys.hasNext()) {
|
||||
final String key = keys.next();
|
||||
final Object val = objSettings.opt(key);
|
||||
// Log.d(TAG, "importJson: key: " + key + ", val: " + val);
|
||||
if (val instanceof String) {
|
||||
settingsHelper.putString(key, (String) val);
|
||||
} else if (val instanceof Integer) {
|
||||
settingsHelper.putInteger(key, (int) val);
|
||||
} else if (val instanceof Boolean) {
|
||||
settingsHelper.putBoolean(key, (boolean) val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isEncrypted(final File file) {
|
||||
try (final FileInputStream fis = new FileInputStream(file)) {
|
||||
final int configType = fis.read();
|
||||
if (configType == 'A') {
|
||||
return true;
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
Log.e(TAG, "isEncrypted", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getExportString(@ExportImportFlags final int flags) {
|
||||
private static String getExportString(@ExportImportFlags final int flags,
|
||||
@NonNull final Context context) {
|
||||
String result = null;
|
||||
try {
|
||||
final JSONObject jsonObject = new JSONObject();
|
||||
|
||||
String str;
|
||||
if ((flags & FLAG_SETTINGS) == FLAG_SETTINGS) {
|
||||
str = getSettings();
|
||||
if (str != null) jsonObject.put("settings", new JSONObject(str));
|
||||
jsonObject.put("settings", getSettings(context));
|
||||
}
|
||||
|
||||
if ((flags & FLAG_COOKIES) == FLAG_COOKIES) {
|
||||
str = getCookies();
|
||||
if (str != null) jsonObject.put("cookies", new JSONArray(str));
|
||||
jsonObject.put("cookies", getCookies());
|
||||
}
|
||||
|
||||
if ((flags & FLAG_FAVORITES) == FLAG_FAVORITES) {
|
||||
str = getFavorites();
|
||||
if (str != null) jsonObject.put("favs", new JSONArray(str));
|
||||
jsonObject.put("favs", getFavorites());
|
||||
}
|
||||
|
||||
result = jsonObject.toString();
|
||||
@ -220,117 +258,73 @@ public final class ExportImportUtils {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getSettings() {
|
||||
String result = null;
|
||||
|
||||
if (settingsHelper != null) {
|
||||
try {
|
||||
final JSONObject json = new JSONObject();
|
||||
json.put(Constants.APP_THEME, settingsHelper.getString(Constants.APP_THEME));
|
||||
json.put(Constants.APP_LANGUAGE, settingsHelper.getString(Constants.APP_LANGUAGE));
|
||||
|
||||
String str = settingsHelper.getString(Constants.FOLDER_PATH);
|
||||
if (!TextUtils.isEmpty(str)) json.put(Constants.FOLDER_PATH, str);
|
||||
|
||||
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);
|
||||
}
|
||||
@NonNull
|
||||
private static JSONObject getSettings(@NonNull final Context context) {
|
||||
final SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
|
||||
final Map<String, ?> allPrefs = sharedPreferences.getAll();
|
||||
if (allPrefs == null) {
|
||||
return new JSONObject();
|
||||
}
|
||||
|
||||
return result;
|
||||
try {
|
||||
final JSONObject jsonObject = new JSONObject(allPrefs);
|
||||
jsonObject.remove(Constants.COOKIE);
|
||||
jsonObject.remove(Constants.DEVICE_UUID);
|
||||
jsonObject.remove(Constants.PREV_INSTALL_VERSION);
|
||||
return jsonObject;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error exporting settings", e);
|
||||
}
|
||||
return new JSONObject();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getFavorites() {
|
||||
String result = null;
|
||||
if (Utils.dataBox != null) {
|
||||
try {
|
||||
final List<DataBox.FavoriteModel> allFavorites = Utils.dataBox.getAllFavorites();
|
||||
final int allFavoritesSize;
|
||||
if ((allFavoritesSize = allFavorites.size()) > 0) {
|
||||
final JSONArray jsonArray = new JSONArray();
|
||||
for (int i = 0; i < allFavoritesSize; i++) {
|
||||
final DataBox.FavoriteModel favorite = allFavorites.get(i);
|
||||
final JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("q", favorite.getQuery());
|
||||
jsonObject.put("d", favorite.getDateAdded().getTime());
|
||||
jsonObject.put("s", favorite.getDisplayName());
|
||||
jsonArray.put(jsonObject);
|
||||
}
|
||||
result = jsonArray.toString();
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
result = null;
|
||||
if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_EXPORT, "getFavorites");
|
||||
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
|
||||
@NonNull
|
||||
private static JSONArray getFavorites() {
|
||||
if (Utils.dataBox == null) return new JSONArray();
|
||||
try {
|
||||
final List<DataBox.FavoriteModel> allFavorites = Utils.dataBox.getAllFavorites();
|
||||
final JSONArray jsonArray = new JSONArray();
|
||||
for (final DataBox.FavoriteModel favorite : allFavorites) {
|
||||
final JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("q", favorite.getQuery());
|
||||
jsonObject.put("type", favorite.getType().toString());
|
||||
jsonObject.put("s", favorite.getDisplayName());
|
||||
jsonObject.put("pic_url", favorite.getPicUrl());
|
||||
jsonObject.put("d", favorite.getDateAdded().getTime());
|
||||
jsonArray.put(jsonObject);
|
||||
}
|
||||
return jsonArray;
|
||||
} catch (final Exception e) {
|
||||
if (logCollector != null) {
|
||||
logCollector.appendException(e, LogFile.UTILS_EXPORT, "getFavorites");
|
||||
}
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.e(TAG, "Error exporting favorites", e);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return new JSONArray();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String getCookies() {
|
||||
String result = null;
|
||||
if (Utils.dataBox != null) {
|
||||
try {
|
||||
final ArrayList<DataBox.CookieModel> allCookies = Utils.dataBox.getAllCookies();
|
||||
final int allCookiesSize;
|
||||
if (allCookies != null && (allCookiesSize = allCookies.size()) > 0) {
|
||||
final JSONArray jsonArray = new JSONArray();
|
||||
for (int i = 0; i < allCookiesSize; i++) {
|
||||
final DataBox.CookieModel cookieModel = allCookies.get(i);
|
||||
final JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("i", cookieModel.getUid());
|
||||
jsonObject.put("u", cookieModel.getUsername());
|
||||
jsonObject.put("c", cookieModel.getCookie());
|
||||
jsonArray.put(jsonObject);
|
||||
}
|
||||
result = jsonArray.toString();
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
result = null;
|
||||
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
|
||||
@NonNull
|
||||
private static JSONArray getCookies() {
|
||||
if (Utils.dataBox == null) return new JSONArray();
|
||||
try {
|
||||
final List<DataBox.CookieModel> allCookies = Utils.dataBox.getAllCookies();
|
||||
final JSONArray jsonArray = new JSONArray();
|
||||
for (final DataBox.CookieModel cookie : allCookies) {
|
||||
final JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("i", cookie.getUid());
|
||||
jsonObject.put("u", cookie.getUsername());
|
||||
jsonObject.put("c", cookie.getCookie());
|
||||
jsonObject.put("full_name", cookie.getFullName());
|
||||
jsonObject.put("profile_pic", cookie.getProfilePic());
|
||||
jsonArray.put(jsonObject);
|
||||
}
|
||||
return jsonArray;
|
||||
} catch (final Exception e) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.e(TAG, "Error exporting accounts", e);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
return new JSONArray();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -39,7 +39,7 @@ public final class SettingsHelper {
|
||||
private final SharedPreferences sharedPreferences;
|
||||
|
||||
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
|
||||
|
@ -1,26 +1,20 @@
|
||||
package awais.instagrabber.utils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.text.Editable;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.util.Pair;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.exoplayer2.database.ExoDatabaseProvider;
|
||||
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor;
|
||||
@ -37,11 +31,9 @@ import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import awais.instagrabber.R;
|
||||
import awais.instagrabber.databinding.DialogImportExportBinding;
|
||||
import awais.instagrabber.models.enums.FavoriteType;
|
||||
import awaisomereport.LogCollector;
|
||||
|
||||
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
|
||||
|
||||
public final class Utils {
|
||||
private static final String TAG = "Utils";
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
if (clipboardManager == null)
|
||||
clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
@ -87,100 +66,6 @@ public final class Utils {
|
||||
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) {
|
||||
final String signed = sign(new JSONObject(form).toString());
|
||||
if (signed == null) {
|
||||
@ -249,4 +134,16 @@ public final class Utils {
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
10
app/src/main/res/drawable/ic_file_24.xml
Normal file
10
app/src/main/res/drawable/ic_file_24.xml
Normal 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>
|
10
app/src/main/res/drawable/ic_folder_24.xml
Normal file
10
app/src/main/res/drawable/ic_folder_24.xml
Normal 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>
|
10
app/src/main/res/drawable/ic_settings_backup_restore_24.xml
Normal file
10
app/src/main/res/drawable/ic_settings_backup_restore_24.xml
Normal 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>
|
73
app/src/main/res/layout/dialog_create_backup.xml
Normal file
73
app/src/main/res/layout/dialog_create_backup.xml
Normal 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>
|
@ -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>
|
143
app/src/main/res/layout/dialog_restore_backup.xml
Normal file
143
app/src/main/res/layout/dialog_restore_backup.xml
Normal 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>
|
@ -1,15 +1,31 @@
|
||||
<?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"
|
||||
android:id="@android:id/text1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="?android:attr/listPreferredItemHeightSmall"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:paddingRight="?android:attr/listPreferredItemPaddingRight"
|
||||
android:textAppearance="?android:attr/textAppearanceListItemSmall"
|
||||
tools:viewBindingIgnore="true" />
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:minHeight="56dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
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>
|
@ -1,116 +1,65 @@
|
||||
<?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:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/footer"
|
||||
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"
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appBarLayout"
|
||||
android:layout_width="match_parent"
|
||||
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
|
||||
android:id="@+id/btnNavUp"
|
||||
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"
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/txtvSelectedFolderLabel"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
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>
|
||||
app:navigationIcon="@drawable/ic_arrow_upward_24"
|
||||
tools:title="/this/that/thy" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/directoryList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_above="@id/footer"
|
||||
android:layout_below="@id/directoryInfo" />
|
||||
</RelativeLayout>
|
||||
app:layout_constraintBottom_toTopOf="@id/bottom_horizontal_divider"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
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>
|
@ -72,7 +72,8 @@
|
||||
android:layout_marginLeft="15dip"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone">
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/btnSaveTo"
|
||||
|
@ -68,6 +68,9 @@
|
||||
<action
|
||||
android:id="@+id/action_morePreferencesFragment_to_favoritesFragment"
|
||||
app:destination="@id/favoritesFragment" />
|
||||
<action
|
||||
android:id="@+id/action_morePreferencesFragment_to_backupPreferencesFragment"
|
||||
app:destination="@id/backupPreferencesFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/settingsPreferencesFragment"
|
||||
@ -94,4 +97,8 @@
|
||||
android:id="@+id/favoritesFragment"
|
||||
android:name="awais.instagrabber.fragments.FavoritesFragment"
|
||||
android:label="@string/title_favorites" />
|
||||
<fragment
|
||||
android:id="@+id/backupPreferencesFragment"
|
||||
android:name="awais.instagrabber.fragments.settings.BackupPreferencesFragment"
|
||||
android:label="@string/backup_and_restore" />
|
||||
</navigation>
|
@ -18,6 +18,8 @@
|
||||
<string name="clipboard_copied">Copied to clipboard!</string>
|
||||
<string name="report">Report</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="yes">Yes</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
@ -25,7 +27,7 @@
|
||||
<string name="confirm">Confirm</string>
|
||||
<string name="nav_up">Up</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_discover">Discover</string>
|
||||
<string name="title_comments">Comments</string>
|
||||
@ -126,16 +128,18 @@
|
||||
<string name="dialog_export_btn_export">Export</string>
|
||||
<string name="dialog_export_btn_import">Import</string>
|
||||
<string name="dialog_export_logins">Export Logins</string>
|
||||
<string name="dialog_export_settings">Export Settings</string>
|
||||
<string name="dialog_export_favorites">Export Favorites</string>
|
||||
<string name="dialog_import_settings">Import Settings</string>
|
||||
<string name="dialog_export_accounts">Accounts</string>
|
||||
<string name="dialog_export_settings">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_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_failed">Failed to import!</string>
|
||||
<string name="dialog_export_success">Successfully exported!</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="get_cookies">Get cookies</string>
|
||||
<string name="desktop_2fa">Desktop Mode</string>
|
||||
@ -292,4 +296,10 @@
|
||||
<string name="locations">Locations</string>
|
||||
<string name="unknown">Unknown</string>
|
||||
<string name="removed_from_favs">Removed from Favourites</string>
|
||||
<string name="backup_and_restore">Backup & 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>
|
@ -57,7 +57,11 @@
|
||||
</style>
|
||||
|
||||
<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 name="Widget.MaterialComponents.Button.Dark.Black" parent="Widget.MaterialComponents.Button">
|
||||
@ -110,12 +114,6 @@
|
||||
</style>
|
||||
|
||||
<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="buttonBarNeutralButtonStyle">@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="">
|
||||
<item name="colorPrimary">?attr/colorPrimaryDark</item>
|
||||
</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>
|
||||
|
@ -38,6 +38,7 @@
|
||||
<item name="android:windowBackground">@color/white</item>
|
||||
<item name="bottomNavigationStyle">@style/Widget.BottomNavigationView.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 name="AppTheme.Light.Barinsta" parent="AppTheme.Light">
|
||||
|
Loading…
Reference in New Issue
Block a user