Browse Source
Merge pull request #170 from ammargitham/task/add-favourites
Merge pull request #170 from ammargitham/task/add-favourites
add favorites and bugfixesrenovate/org.robolectric-robolectric-4.x
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
114 changed files with 3676 additions and 1838 deletions
-
36.all-contributorsrc
-
4.gitignore
-
44README.md
-
9app/build.gradle
-
2app/src/main/AndroidManifest.xml
-
5app/src/main/java/awais/instagrabber/InstaGrabberApplication.java
-
86app/src/main/java/awais/instagrabber/activities/MainActivity.java
-
75app/src/main/java/awais/instagrabber/adapters/DirectoryFilesAdapter.java
-
202app/src/main/java/awais/instagrabber/adapters/FavoritesAdapter.java
-
36app/src/main/java/awais/instagrabber/adapters/NotificationsAdapter.java
-
75app/src/main/java/awais/instagrabber/adapters/SimpleAdapter.java
-
9app/src/main/java/awais/instagrabber/adapters/viewholder/DirectMessageInboxItemViewHolder.java
-
61app/src/main/java/awais/instagrabber/adapters/viewholder/FavoriteViewHolder.java
-
7app/src/main/java/awais/instagrabber/asyncs/FeedFetcher.java
-
35app/src/main/java/awais/instagrabber/asyncs/HashtagFetcher.java
-
8app/src/main/java/awais/instagrabber/asyncs/HighlightsFetcher.java
-
58app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java
-
3app/src/main/java/awais/instagrabber/asyncs/SuggestionsFetcher.java
-
13app/src/main/java/awais/instagrabber/asyncs/direct_messages/CreateThreadAction.java
-
70app/src/main/java/awais/instagrabber/asyncs/i/iLikedFetcher.java
-
21app/src/main/java/awais/instagrabber/asyncs/i/iStoryStatusFetcher.java
-
169app/src/main/java/awais/instagrabber/dialogs/CreateBackupDialogFragment.java
-
181app/src/main/java/awais/instagrabber/dialogs/QuickAccessDialog.java
-
180app/src/main/java/awais/instagrabber/dialogs/RestoreBackupDialogFragment.java
-
215app/src/main/java/awais/instagrabber/fragments/FavoritesFragment.java
-
163app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java
-
80app/src/main/java/awais/instagrabber/fragments/LocationFragment.java
-
21app/src/main/java/awais/instagrabber/fragments/SavedViewerFragment.java
-
5app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java
-
1app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java
-
328app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java
-
19app/src/main/java/awais/instagrabber/fragments/settings/AboutFragment.java
-
107app/src/main/java/awais/instagrabber/fragments/settings/BackupPreferencesFragment.java
-
2app/src/main/java/awais/instagrabber/fragments/settings/BasePreferencesFragment.java
-
52app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java
-
6app/src/main/java/awais/instagrabber/fragments/settings/SettingsPreferencesFragment.java
-
7app/src/main/java/awais/instagrabber/models/enums/FavoriteType.java
-
5app/src/main/java/awais/instagrabber/models/enums/StoryViewerChoice.java
-
19app/src/main/java/awais/instagrabber/repositories/TagsRepository.java
-
17app/src/main/java/awais/instagrabber/services/ActivityCheckerService.java
-
2app/src/main/java/awais/instagrabber/utils/Constants.java
-
388app/src/main/java/awais/instagrabber/utils/DataBox.java
-
181app/src/main/java/awais/instagrabber/utils/DirectoryChooser.java
-
76app/src/main/java/awais/instagrabber/utils/DirectoryUtils.java
-
4app/src/main/java/awais/instagrabber/utils/DownloadUtils.java
-
461app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java
-
7app/src/main/java/awais/instagrabber/utils/NavigationExtensions.java
-
47app/src/main/java/awais/instagrabber/utils/PasswordUtils.java
-
4app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java
-
2app/src/main/java/awais/instagrabber/utils/SettingsHelper.java
-
135app/src/main/java/awais/instagrabber/utils/Utils.java
-
19app/src/main/java/awais/instagrabber/viewmodels/FavoritesViewModel.java
-
18app/src/main/java/awais/instagrabber/viewmodels/FileListViewModel.java
-
24app/src/main/java/awais/instagrabber/webservices/StoriesService.java
-
101app/src/main/java/awais/instagrabber/webservices/TagsService.java
-
BINapp/src/main/res/drawable-night/expired.png
-
BINapp/src/main/res/drawable/expired.png
-
10app/src/main/res/drawable/ic_account_multiple_remove_24.xml
-
10app/src/main/res/drawable/ic_block_24.xml
-
10app/src/main/res/drawable/ic_clock_alert_outline_24.xml
-
10app/src/main/res/drawable/ic_file_24.xml
-
2app/src/main/res/drawable/ic_folder_24.xml
-
10app/src/main/res/drawable/ic_highlight_off_24.xml
-
10app/src/main/res/drawable/ic_logout_24.xml
-
10app/src/main/res/drawable/ic_outline_class_24.xml
-
10app/src/main/res/drawable/ic_outline_map_24.xml
-
10app/src/main/res/drawable/ic_outline_person_add_24.xml
-
10app/src/main/res/drawable/ic_outline_person_add_disabled_24.xml
-
10app/src/main/res/drawable/ic_outline_person_pin_24.xml
-
10app/src/main/res/drawable/ic_outline_star_24.xml
-
10app/src/main/res/drawable/ic_outline_star_plus_24.xml
-
10app/src/main/res/drawable/ic_settings_backup_restore_24.xml
-
8app/src/main/res/drawable/ic_star_check_24.xml
-
2app/src/main/res/drawable/rounder_corner_semi_black_bg.xml
-
6app/src/main/res/drawable/sl_favourite_24.xml
-
1app/src/main/res/layout/activity_main.xml
-
73app/src/main/res/layout/dialog_create_backup.xml
-
230app/src/main/res/layout/dialog_import_export.xml
-
2app/src/main/res/layout/dialog_profilepic.xml
-
143app/src/main/res/layout/dialog_restore_backup.xml
-
7app/src/main/res/layout/fragment_favorites.xml
-
119app/src/main/res/layout/fragment_hashtag.xml
-
153app/src/main/res/layout/fragment_location.xml
-
141app/src/main/res/layout/fragment_profile.xml
-
38app/src/main/res/layout/item_dir_list.xml
-
13app/src/main/res/layout/item_fav_section_header.xml
-
2app/src/main/res/layout/item_feed_slider.xml
-
4app/src/main/res/layout/item_feed_top.xml
-
114app/src/main/res/layout/item_notification.xml
-
17app/src/main/res/layout/item_suggestion.xml
-
153app/src/main/res/layout/layout_directory_chooser.xml
-
2app/src/main/res/layout/layout_dm_raven_media.xml
-
3app/src/main/res/layout/pref_custom_folder.xml
-
23app/src/main/res/menu/profile_menu.xml
-
32app/src/main/res/navigation/more_nav_graph.xml
-
32app/src/main/res/navigation/profile_nav_graph.xml
-
1app/src/main/res/values-de/arrays.xml
-
1app/src/main/res/values-es/arrays.xml
-
1app/src/main/res/values-fa/arrays.xml
-
1app/src/main/res/values-fr/arrays.xml
@ -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); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,202 @@ |
|||
package awais.instagrabber.adapters; |
|||
|
|||
import android.view.LayoutInflater; |
|||
import android.view.ViewGroup; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.core.util.ObjectsCompat; |
|||
import androidx.recyclerview.widget.AdapterListUpdateCallback; |
|||
import androidx.recyclerview.widget.AsyncDifferConfig; |
|||
import androidx.recyclerview.widget.AsyncListDiffer; |
|||
import androidx.recyclerview.widget.DiffUtil; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.adapters.viewholder.FavoriteViewHolder; |
|||
import awais.instagrabber.databinding.ItemFavSectionHeaderBinding; |
|||
import awais.instagrabber.databinding.ItemSuggestionBinding; |
|||
import awais.instagrabber.models.enums.FavoriteType; |
|||
import awais.instagrabber.utils.DataBox; |
|||
|
|||
public class FavoritesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { |
|||
|
|||
private final OnFavoriteClickListener clickListener; |
|||
private final OnFavoriteLongClickListener longClickListener; |
|||
private final AsyncListDiffer<FavoriteModelOrHeader> differ; |
|||
|
|||
private static final DiffUtil.ItemCallback<FavoriteModelOrHeader> diffCallback = new DiffUtil.ItemCallback<FavoriteModelOrHeader>() { |
|||
@Override |
|||
public boolean areItemsTheSame(@NonNull final FavoriteModelOrHeader oldItem, @NonNull final FavoriteModelOrHeader newItem) { |
|||
boolean areSame = oldItem.isHeader() && newItem.isHeader(); |
|||
if (!areSame) { |
|||
return false; |
|||
} |
|||
if (oldItem.isHeader()) { |
|||
return ObjectsCompat.equals(oldItem.header, newItem.header); |
|||
} |
|||
if (oldItem.model != null && newItem.model != null) { |
|||
return oldItem.model.getId() == newItem.model.getId(); |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public boolean areContentsTheSame(@NonNull final FavoriteModelOrHeader oldItem, @NonNull final FavoriteModelOrHeader newItem) { |
|||
boolean areSame = oldItem.isHeader() && newItem.isHeader(); |
|||
if (!areSame) { |
|||
return false; |
|||
} |
|||
if (oldItem.isHeader()) { |
|||
return ObjectsCompat.equals(oldItem.header, newItem.header); |
|||
} |
|||
return ObjectsCompat.equals(oldItem.model, newItem.model); |
|||
} |
|||
}; |
|||
|
|||
public FavoritesAdapter(final OnFavoriteClickListener clickListener, final OnFavoriteLongClickListener longClickListener) { |
|||
this.clickListener = clickListener; |
|||
this.longClickListener = longClickListener; |
|||
differ = new AsyncListDiffer<>(new AdapterListUpdateCallback(this), |
|||
new AsyncDifferConfig.Builder<>(diffCallback).build()); |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { |
|||
final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); |
|||
if (viewType == 0) { |
|||
// header |
|||
return new FavSectionViewHolder(ItemFavSectionHeaderBinding.inflate(inflater, parent, false)); |
|||
} |
|||
final ItemSuggestionBinding binding = ItemSuggestionBinding.inflate(inflater, parent, false); |
|||
return new FavoriteViewHolder(binding); |
|||
} |
|||
|
|||
@Override |
|||
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) { |
|||
if (getItemViewType(position) == 0) { |
|||
final FavoriteModelOrHeader modelOrHeader = getItem(position); |
|||
if (!modelOrHeader.isHeader()) return; |
|||
((FavSectionViewHolder) holder).bind(modelOrHeader.header); |
|||
return; |
|||
} |
|||
((FavoriteViewHolder) holder).bind(getItem(position).model, clickListener, longClickListener); |
|||
} |
|||
|
|||
protected FavoriteModelOrHeader getItem(int position) { |
|||
return differ.getCurrentList().get(position); |
|||
} |
|||
|
|||
@Override |
|||
public int getItemCount() { |
|||
return differ.getCurrentList().size(); |
|||
} |
|||
|
|||
@Override |
|||
public int getItemViewType(final int position) { |
|||
return getItem(position).isHeader() ? 0 : 1; |
|||
} |
|||
|
|||
public void submitList(@Nullable final List<DataBox.FavoriteModel> list) { |
|||
if (list == null) { |
|||
differ.submitList(null); |
|||
return; |
|||
} |
|||
differ.submitList(sectionAndSort(list)); |
|||
} |
|||
|
|||
public void submitList(@Nullable final List<DataBox.FavoriteModel> list, @Nullable final Runnable commitCallback) { |
|||
if (list == null) { |
|||
differ.submitList(null, commitCallback); |
|||
return; |
|||
} |
|||
differ.submitList(sectionAndSort(list), commitCallback); |
|||
} |
|||
|
|||
@NonNull |
|||
private List<FavoriteModelOrHeader> sectionAndSort(@NonNull final List<DataBox.FavoriteModel> list) { |
|||
final List<DataBox.FavoriteModel> listCopy = new ArrayList<>(list); |
|||
Collections.sort(listCopy, (o1, o2) -> { |
|||
if (o1.getType() == o2.getType()) return 0; |
|||
// keep users at top |
|||
if (o1.getType() == FavoriteType.USER) return -1; |
|||
if (o2.getType() == FavoriteType.USER) return 1; |
|||
// keep locations at bottom |
|||
if (o1.getType() == FavoriteType.LOCATION) return 1; |
|||
if (o2.getType() == FavoriteType.LOCATION) return -1; |
|||
return 0; |
|||
}); |
|||
final List<FavoriteModelOrHeader> modelOrHeaders = new ArrayList<>(); |
|||
for (int i = 0; i < listCopy.size(); i++) { |
|||
final DataBox.FavoriteModel model = listCopy.get(i); |
|||
final FavoriteModelOrHeader prev = modelOrHeaders.isEmpty() ? null : modelOrHeaders.get(modelOrHeaders.size() - 1); |
|||
boolean prevWasSameType = prev != null && prev.model.getType() == model.getType(); |
|||
if (prevWasSameType) { |
|||
// just add model |
|||
final FavoriteModelOrHeader modelOrHeader = new FavoriteModelOrHeader(); |
|||
modelOrHeader.model = model; |
|||
modelOrHeaders.add(modelOrHeader); |
|||
continue; |
|||
} |
|||
// add header and model |
|||
FavoriteModelOrHeader modelOrHeader = new FavoriteModelOrHeader(); |
|||
modelOrHeader.header = model.getType(); |
|||
modelOrHeaders.add(modelOrHeader); |
|||
modelOrHeader = new FavoriteModelOrHeader(); |
|||
modelOrHeader.model = model; |
|||
modelOrHeaders.add(modelOrHeader); |
|||
} |
|||
return modelOrHeaders; |
|||
} |
|||
|
|||
private static class FavoriteModelOrHeader { |
|||
FavoriteType header; |
|||
DataBox.FavoriteModel model; |
|||
|
|||
boolean isHeader() { |
|||
return header != null; |
|||
} |
|||
} |
|||
|
|||
public interface OnFavoriteClickListener { |
|||
void onClick(final DataBox.FavoriteModel model); |
|||
} |
|||
|
|||
public interface OnFavoriteLongClickListener { |
|||
boolean onLongClick(final DataBox.FavoriteModel model); |
|||
} |
|||
|
|||
public static class FavSectionViewHolder extends RecyclerView.ViewHolder { |
|||
private final ItemFavSectionHeaderBinding binding; |
|||
|
|||
public FavSectionViewHolder(@NonNull final ItemFavSectionHeaderBinding binding) { |
|||
super(binding.getRoot()); |
|||
this.binding = binding; |
|||
} |
|||
|
|||
public void bind(final FavoriteType header) { |
|||
if (header == null) return; |
|||
final int headerText; |
|||
switch (header) { |
|||
case USER: |
|||
headerText = R.string.accounts; |
|||
break; |
|||
case HASHTAG: |
|||
headerText = R.string.hashtags; |
|||
break; |
|||
case LOCATION: |
|||
headerText = R.string.locations; |
|||
break; |
|||
default: |
|||
headerText = R.string.unknown; |
|||
break; |
|||
} |
|||
binding.getRoot().setText(headerText); |
|||
} |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,61 @@ |
|||
package awais.instagrabber.adapters.viewholder; |
|||
|
|||
import android.view.View; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import awais.instagrabber.adapters.FavoritesAdapter; |
|||
import awais.instagrabber.databinding.ItemSuggestionBinding; |
|||
import awais.instagrabber.models.enums.FavoriteType; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.DataBox; |
|||
|
|||
public class FavoriteViewHolder extends RecyclerView.ViewHolder { |
|||
private static final String TAG = "FavoriteViewHolder"; |
|||
|
|||
private final ItemSuggestionBinding binding; |
|||
|
|||
public FavoriteViewHolder(@NonNull final ItemSuggestionBinding binding) { |
|||
super(binding.getRoot()); |
|||
this.binding = binding; |
|||
binding.isVerified.setVisibility(View.GONE); |
|||
} |
|||
|
|||
public void bind(final DataBox.FavoriteModel model, |
|||
final FavoritesAdapter.OnFavoriteClickListener clickListener, |
|||
final FavoritesAdapter.OnFavoriteLongClickListener longClickListener) { |
|||
// Log.d(TAG, "bind: " + model); |
|||
if (model == null) return; |
|||
itemView.setOnClickListener(v -> { |
|||
if (clickListener == null) return; |
|||
clickListener.onClick(model); |
|||
}); |
|||
itemView.setOnLongClickListener(v -> { |
|||
if (clickListener == null) return false; |
|||
return longClickListener.onLongClick(model); |
|||
}); |
|||
if (model.getType() == FavoriteType.HASHTAG) { |
|||
binding.ivProfilePic.setImageURI(Constants.DEFAULT_HASH_TAG_PIC); |
|||
} else { |
|||
binding.ivProfilePic.setImageURI(model.getPicUrl()); |
|||
} |
|||
binding.tvFullName.setText(model.getDisplayName()); |
|||
binding.tvUsername.setVisibility(View.VISIBLE); |
|||
String query = model.getQuery(); |
|||
switch (model.getType()) { |
|||
case HASHTAG: |
|||
query = "#" + query; |
|||
break; |
|||
case USER: |
|||
query = "@" + query; |
|||
break; |
|||
case LOCATION: |
|||
binding.tvUsername.setVisibility(View.GONE); |
|||
break; |
|||
default: |
|||
// do nothing |
|||
} |
|||
binding.tvUsername.setText(query); |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
} |
@ -1,181 +0,0 @@ |
|||
package awais.instagrabber.dialogs; |
|||
|
|||
import android.app.Activity; |
|||
import android.app.Dialog; |
|||
import android.content.Context; |
|||
import android.content.DialogInterface; |
|||
import android.content.pm.PackageManager; |
|||
import android.os.Bundle; |
|||
import android.view.View; |
|||
import android.widget.Toast; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.appcompat.app.AlertDialog; |
|||
import androidx.core.content.ContextCompat; |
|||
import androidx.recyclerview.widget.DividerItemDecoration; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment; |
|||
|
|||
import java.util.ArrayList; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.adapters.SimpleAdapter; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.CookieUtils; |
|||
import awais.instagrabber.utils.DataBox; |
|||
import awais.instagrabber.utils.DownloadUtils; |
|||
import awais.instagrabber.utils.TextUtils; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
import static awais.instagrabber.utils.Utils.settingsHelper; |
|||
|
|||
public final class QuickAccessDialog extends BottomSheetDialogFragment implements DialogInterface.OnShowListener, |
|||
View.OnClickListener, View.OnLongClickListener { |
|||
private boolean cookieChanged, isQuery; |
|||
private Activity activity; |
|||
private String userQuery, displayName; |
|||
private View btnFavorite, btnImportExport; |
|||
private SimpleAdapter<DataBox.FavoriteModel> favoritesAdapter; |
|||
private RecyclerView rvFavorites, rvQuickAccess; |
|||
|
|||
public QuickAccessDialog setQuery(final String userQuery, final String displayName) { |
|||
this.userQuery = userQuery; |
|||
this.displayName = displayName; |
|||
return this; |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { |
|||
final Dialog dialog = super.onCreateDialog(savedInstanceState); |
|||
|
|||
dialog.setOnShowListener(this); |
|||
|
|||
final Context context = getContext(); |
|||
activity = context instanceof Activity ? (Activity) context : getActivity(); |
|||
|
|||
final View contentView = View.inflate(activity, R.layout.dialog_quick_access, null); |
|||
|
|||
btnFavorite = contentView.findViewById(R.id.btnFavorite); |
|||
btnImportExport = contentView.findViewById(R.id.importExport); |
|||
|
|||
isQuery = !TextUtils.isEmpty(userQuery); |
|||
btnFavorite.setVisibility(isQuery ? View.VISIBLE : View.GONE); |
|||
Utils.setTooltipText(btnImportExport, R.string.import_export); |
|||
|
|||
favoritesAdapter = new SimpleAdapter<>(activity, Utils.dataBox.getAllFavorites(), this, this); |
|||
|
|||
btnFavorite.setOnClickListener(this); |
|||
btnImportExport.setOnClickListener(this); |
|||
|
|||
rvFavorites = contentView.findViewById(R.id.rvFavorites); |
|||
rvQuickAccess = contentView.findViewById(R.id.rvQuickAccess); |
|||
|
|||
final DividerItemDecoration itemDecoration = new DividerItemDecoration(activity, DividerItemDecoration.VERTICAL); |
|||
rvFavorites.addItemDecoration(itemDecoration); |
|||
rvFavorites.setAdapter(favoritesAdapter); |
|||
|
|||
final String cookieStr = settingsHelper.getString(Constants.COOKIE); |
|||
if (!TextUtils.isEmpty(cookieStr) |
|||
|| Utils.dataBox.getCookieCount() > 0 // fallback for export / import |
|||
) { |
|||
rvQuickAccess.addItemDecoration(itemDecoration); |
|||
final ArrayList<DataBox.CookieModel> allCookies = Utils.dataBox.getAllCookies(); |
|||
if (!TextUtils.isEmpty(cookieStr) && allCookies != null) { |
|||
for (final DataBox.CookieModel cookie : allCookies) { |
|||
if (cookieStr.equals(cookie.getCookie())) { |
|||
cookie.setSelected(true); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
rvQuickAccess.setAdapter(new SimpleAdapter<>(activity, allCookies, this, this)); |
|||
} else { |
|||
((View) rvQuickAccess.getParent()).setVisibility(View.GONE); |
|||
} |
|||
|
|||
dialog.setContentView(contentView); |
|||
return dialog; |
|||
} |
|||
|
|||
@Override |
|||
public void onClick(@NonNull final View v) { |
|||
final Object tag = v.getTag(); |
|||
if (v == btnFavorite) { |
|||
if (isQuery) { |
|||
Utils.dataBox.addFavorite(new DataBox.FavoriteModel(userQuery, System.currentTimeMillis(), displayName)); |
|||
favoritesAdapter.setItems(Utils.dataBox.getAllFavorites()); |
|||
} |
|||
} else if (v == btnImportExport) { |
|||
if (ContextCompat.checkSelfPermission(activity, DownloadUtils.PERMS[0]) == PackageManager.PERMISSION_DENIED) |
|||
requestPermissions(DownloadUtils.PERMS, 6007); |
|||
else Utils.showImportExportDialog(v.getContext()); |
|||
|
|||
} else if (tag instanceof DataBox.FavoriteModel) { |
|||
// if (MainActivityBackup.scanHack != null) { |
|||
// MainActivityBackup.scanHack.onResult(((DataBox.FavoriteModel) tag).getQuery()); |
|||
// dismiss(); |
|||
// } |
|||
|
|||
} else if (tag instanceof DataBox.CookieModel) { |
|||
final DataBox.CookieModel cookieModel = (DataBox.CookieModel) tag; |
|||
if (!cookieModel.isSelected()) { |
|||
settingsHelper.putString(Constants.COOKIE, cookieModel.getCookie()); |
|||
CookieUtils.setupCookies(cookieModel.getCookie()); |
|||
cookieChanged = true; |
|||
} |
|||
dismiss(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public boolean onLongClick(@NonNull final View v) { |
|||
final Object tag = v.getTag(); |
|||
|
|||
if (tag instanceof DataBox.FavoriteModel) { |
|||
final DataBox.FavoriteModel favoriteModel = (DataBox.FavoriteModel) tag; |
|||
|
|||
new AlertDialog.Builder(activity).setPositiveButton(R.string.yes, (d, which) -> { |
|||
Utils.dataBox.delFavorite(favoriteModel); |
|||
favoritesAdapter.setItems(Utils.dataBox.getAllFavorites()); |
|||
}) |
|||
.setNegativeButton(R.string.no, null).setMessage(getString(R.string.quick_access_confirm_delete, |
|||
favoriteModel.getQuery())).show(); |
|||
|
|||
} else if (tag instanceof DataBox.CookieModel) { |
|||
final DataBox.CookieModel cookieModel = (DataBox.CookieModel) tag; |
|||
|
|||
if (cookieModel.isSelected()) |
|||
Toast.makeText(v.getContext(), R.string.quick_access_cannot_delete_curr, Toast.LENGTH_SHORT).show(); |
|||
else |
|||
new AlertDialog.Builder(activity) |
|||
.setMessage(getString(R.string.quick_access_confirm_delete, cookieModel.getUsername())) |
|||
.setPositiveButton(R.string.yes, (d, which) -> { |
|||
Utils.dataBox.delUserCookie(cookieModel); |
|||
rvQuickAccess.findViewWithTag(cookieModel).setVisibility(View.GONE); |
|||
}) |
|||
.setNegativeButton(R.string.no, null) |
|||
.show(); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
@Override |
|||
public void onDismiss(@NonNull final DialogInterface dialog) { |
|||
super.onDismiss(dialog); |
|||
if (cookieChanged && activity != null) activity.recreate(); |
|||
} |
|||
|
|||
@Override |
|||
public void onShow(final DialogInterface dialog) { |
|||
if (settingsHelper.getBoolean(Constants.SHOW_QUICK_ACCESS_DIALOG)) |
|||
new AlertDialog.Builder(activity) |
|||
.setMessage(R.string.quick_access_info_dialog) |
|||
.setPositiveButton(R.string.ok, null) |
|||
.setNeutralButton(R.string.dont_show_again, (d, which) -> |
|||
settingsHelper.putBoolean(Constants.SHOW_QUICK_ACCESS_DIALOG, false)).show(); |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
} |
@ -0,0 +1,215 @@ |
|||
package awais.instagrabber.fragments; |
|||
|
|||
import android.content.Context; |
|||
import android.os.Bundle; |
|||
import android.util.Log; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.fragment.app.Fragment; |
|||
import androidx.lifecycle.ViewModelProvider; |
|||
import androidx.navigation.NavController; |
|||
import androidx.navigation.fragment.NavHostFragment; |
|||
import androidx.recyclerview.widget.LinearLayoutManager; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import com.google.android.material.dialog.MaterialAlertDialogBuilder; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.concurrent.BrokenBarrierException; |
|||
import java.util.concurrent.CyclicBarrier; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.adapters.FavoritesAdapter; |
|||
import awais.instagrabber.asyncs.LocationFetcher; |
|||
import awais.instagrabber.asyncs.ProfileFetcher; |
|||
import awais.instagrabber.databinding.FragmentFavoritesBinding; |
|||
import awais.instagrabber.utils.DataBox; |
|||
import awais.instagrabber.utils.TextUtils; |
|||
import awais.instagrabber.utils.Utils; |
|||
import awais.instagrabber.viewmodels.FavoritesViewModel; |
|||
|
|||
public class FavoritesFragment extends Fragment { |
|||
private static final String TAG = "FavoritesFragment"; |
|||
|
|||
private boolean shouldRefresh = true; |
|||
private FragmentFavoritesBinding binding; |
|||
private RecyclerView root; |
|||
private FavoritesViewModel favoritesViewModel; |
|||
private FavoritesAdapter adapter; |
|||
|
|||
@NonNull |
|||
@Override |
|||
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { |
|||
if (root != null) { |
|||
shouldRefresh = false; |
|||
return root; |
|||
} |
|||
binding = FragmentFavoritesBinding.inflate(getLayoutInflater()); |
|||
root = binding.getRoot(); |
|||
binding.favoriteList.setLayoutManager(new LinearLayoutManager(getContext())); |
|||
return root; |
|||
} |
|||
|
|||
@Override |
|||
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { |
|||
if (!shouldRefresh) return; |
|||
init(); |
|||
shouldRefresh = false; |
|||
} |
|||
|
|||
@Override |
|||
public void onResume() { |
|||
super.onResume(); |
|||
if (favoritesViewModel == null || adapter == null) return; |
|||
// refresh list every time in onViewStateRestored since it is cheaper than implementing pull down to refresh |
|||
favoritesViewModel.getList().observe(getViewLifecycleOwner(), adapter::submitList); |
|||
final List<DataBox.FavoriteModel> allFavorites = Utils.dataBox.getAllFavorites(); |
|||
favoritesViewModel.getList().postValue(allFavorites); |
|||
fetchMissingInfo(allFavorites); |
|||
} |
|||
|
|||
private void init() { |
|||
favoritesViewModel = new ViewModelProvider(this).get(FavoritesViewModel.class); |
|||
adapter = new FavoritesAdapter(model -> { |
|||
// navigate |
|||
switch (model.getType()) { |
|||
case USER: { |
|||
final String username = model.getQuery(); |
|||
// Log.d(TAG, "username: " + username); |
|||
final NavController navController = NavHostFragment.findNavController(this); |
|||
final Bundle bundle = new Bundle(); |
|||
bundle.putString("username", "@" + username); |
|||
navController.navigate(R.id.action_global_profileFragment, bundle); |
|||
break; |
|||
} |
|||
case LOCATION: { |
|||
final String locationId = model.getQuery(); |
|||
// Log.d(TAG, "locationId: " + locationId); |
|||
final NavController navController = NavHostFragment.findNavController(this); |
|||
final Bundle bundle = new Bundle(); |
|||
bundle.putString("locationId", locationId); |
|||
navController.navigate(R.id.action_global_locationFragment, bundle); |
|||
break; |
|||
} |
|||
case HASHTAG: { |
|||
final String hashtag = model.getQuery(); |
|||
// Log.d(TAG, "hashtag: " + hashtag); |
|||
final NavController navController = NavHostFragment.findNavController(this); |
|||
final Bundle bundle = new Bundle(); |
|||
bundle.putString("hashtag", "#" + hashtag); |
|||
navController.navigate(R.id.action_global_hashTagFragment, bundle); |
|||
break; |
|||
} |
|||
default: |
|||
// do nothing |
|||
} |
|||
}, model -> { |
|||
// delete |
|||
final Context context = getContext(); |
|||
if (context == null) return false; |
|||
new MaterialAlertDialogBuilder(context) |
|||
.setMessage(getString(R.string.quick_access_confirm_delete, model.getQuery())) |
|||
.setPositiveButton(R.string.yes, (d, which) -> { |
|||
Utils.dataBox.deleteFavorite(model.getQuery(), model.getType()); |
|||
d.dismiss(); |
|||
favoritesViewModel.getList().postValue(Utils.dataBox.getAllFavorites()); |
|||
}) |
|||
.setNegativeButton(R.string.no, null) |
|||
.show(); |
|||
return true; |
|||
}); |
|||
binding.favoriteList.setAdapter(adapter); |
|||
// favoritesViewModel.getList().observe(getViewLifecycleOwner(), adapter::submitList); |
|||
|
|||
} |
|||
|
|||
private void fetchMissingInfo(final List<DataBox.FavoriteModel> allFavorites) { |
|||
final Runnable runnable = () -> { |
|||
final List<DataBox.FavoriteModel> updatedList = new ArrayList<>(allFavorites); |
|||
// cyclic barrier is to make the async calls synchronous |
|||
final CyclicBarrier cyclicBarrier = new CyclicBarrier(2, () -> { |
|||
// Log.d(TAG, "fetchMissingInfo: barrier action"); |
|||
favoritesViewModel.getList().postValue(new ArrayList<>(updatedList)); |
|||
}); |
|||
try { |
|||
for (final DataBox.FavoriteModel model : allFavorites) { |
|||
cyclicBarrier.reset(); |
|||
// if the model has missing pic or display name (for user and location), fetch those details |
|||
switch (model.getType()) { |
|||
case LOCATION: |
|||
if (TextUtils.isEmpty(model.getDisplayName()) |
|||
|| TextUtils.isEmpty(model.getPicUrl())) { |
|||
new LocationFetcher(model.getQuery(), result -> { |
|||
try { |
|||
if (result == null) return; |
|||
final int i = updatedList.indexOf(model); |
|||
updatedList.remove(i); |
|||
final DataBox.FavoriteModel updated = new DataBox.FavoriteModel( |
|||
model.getId(), |
|||
model.getQuery(), |
|||
model.getType(), |
|||
result.getName(), |
|||
result.getSdProfilePic(), |
|||
model.getDateAdded() |
|||
); |
|||
Utils.dataBox.addOrUpdateFavorite(updated); |
|||
updatedList.add(i, updated); |
|||
} finally { |
|||
try { |
|||
cyclicBarrier.await(); |
|||
} catch (BrokenBarrierException | InterruptedException e) { |
|||
Log.e(TAG, "fetchMissingInfo: ", e); |
|||
} |
|||
} |
|||
}).execute(); |
|||
cyclicBarrier.await(); |
|||
} |
|||
break; |
|||
case USER: |
|||
if (TextUtils.isEmpty(model.getDisplayName()) |
|||
|| TextUtils.isEmpty(model.getPicUrl())) { |
|||
new ProfileFetcher(model.getQuery(), result -> { |
|||
try { |
|||
if (result == null) return; |
|||
final int i = updatedList.indexOf(model); |
|||
updatedList.remove(i); |
|||
final DataBox.FavoriteModel updated = new DataBox.FavoriteModel( |
|||
model.getId(), |
|||
model.getQuery(), |
|||
model.getType(), |
|||
result.getName(), |
|||
result.getSdProfilePic(), |
|||
model.getDateAdded() |
|||
); |
|||
Utils.dataBox.addOrUpdateFavorite(updated); |
|||
updatedList.add(i, updated); |
|||
} finally { |
|||
try { |
|||
cyclicBarrier.await(); |
|||
} catch (BrokenBarrierException | InterruptedException e) { |
|||
Log.e(TAG, "fetchMissingInfo: ", e); |
|||
} |
|||
} |
|||
}).execute(); |
|||
cyclicBarrier.await(); |
|||
} |
|||
break; |
|||
case HASHTAG: |
|||
default: |
|||
// hashtags don't require displayName or pic |
|||
// updatedList.add(model); |
|||
} |
|||
} |
|||
} catch (Exception e) { |
|||
Log.e(TAG, "fetchMissingInfo: ", e); |
|||
} |
|||
favoritesViewModel.getList().postValue(updatedList); |
|||
}; |
|||
new Thread(runnable).start(); |
|||
} |
|||
} |
@ -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(); |
|||
} |
|||
} |
@ -0,0 +1,7 @@ |
|||
package awais.instagrabber.models.enums; |
|||
|
|||
public enum FavoriteType { |
|||
USER, |
|||
HASHTAG, |
|||
LOCATION |
|||
} |
@ -0,0 +1,19 @@ |
|||
package awais.instagrabber.repositories; |
|||
|
|||
import retrofit2.Call; |
|||
import retrofit2.http.Header; |
|||
import retrofit2.http.POST; |
|||
import retrofit2.http.Path; |
|||
|
|||
public interface TagsRepository { |
|||
|
|||
@POST("/web/tags/follow/{tag}/") |
|||
Call<String> follow(@Header("User-Agent") String userAgent, |
|||
@Header("x-csrftoken") String csrfToken, |
|||
@Path("tag") String tag); |
|||
|
|||
@POST("/web/tags/unfollow/{tag}/") |
|||
Call<String> unfollow(@Header("User-Agent") String userAgent, |
|||
@Header("x-csrftoken") String csrfToken, |
|||
@Path("tag") String tag); |
|||
} |
@ -0,0 +1,76 @@ |
|||
package awais.instagrabber.utils; |
|||
|
|||
import android.os.Build; |
|||
import android.os.Environment; |
|||
|
|||
import java.io.File; |
|||
import java.util.Collections; |
|||
import java.util.HashSet; |
|||
import java.util.Set; |
|||
import java.util.regex.Pattern; |
|||
|
|||
public class DirectoryUtils { |
|||
private static final Pattern DIR_SEPORATOR = Pattern.compile("/"); |
|||
|
|||
/** |
|||
* From: https://stackoverflow.com/a/18871043/1436766 |
|||
* |
|||
* Returns all available SD-Cards in the system (include emulated) |
|||
* <p> |
|||
* Warning: Hack! Based on Android source code of version 4.3 (API 18) |
|||
* Because there is no standard way to get it. |
|||
* TODO: Test on future Android versions 4.4+ |
|||
* |
|||
* @return paths to all available SD-Cards in the system (include emulated) |
|||
*/ |
|||
public static Set<String> getStorageDirectories() { |
|||
// Final set of paths |
|||
final Set<String> rv = new HashSet<>(); |
|||
// Primary physical SD-CARD (not emulated) |
|||
final String rawExternalStorage = System.getenv("EXTERNAL_STORAGE"); |
|||
// All Secondary SD-CARDs (all exclude primary) separated by ":" |
|||
final String rawSecondaryStoragesStr = System.getenv("SECONDARY_STORAGE"); |
|||
// Primary emulated SD-CARD |
|||
final String rawEmulatedStorageTarget = System.getenv("EMULATED_STORAGE_TARGET"); |
|||
if (TextUtils.isEmpty(rawEmulatedStorageTarget)) { |
|||
// Device has physical external storage; use plain paths. |
|||
if (TextUtils.isEmpty(rawExternalStorage)) { |
|||
// EXTERNAL_STORAGE undefined; falling back to default. |
|||
rv.add("/storage/sdcard0"); |
|||
} else { |
|||
rv.add(rawExternalStorage); |
|||
} |
|||
} else { |
|||
// Device has emulated storage; external storage paths should have |
|||
// userId burned into them. |
|||
final String rawUserId; |
|||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { |
|||
rawUserId = ""; |
|||
} else { |
|||
final String path = Environment.getExternalStorageDirectory().getAbsolutePath(); |
|||
final String[] folders = DIR_SEPORATOR.split(path); |
|||
final String lastFolder = folders[folders.length - 1]; |
|||
boolean isDigit = false; |
|||
try { |
|||
Integer.valueOf(lastFolder); |
|||
isDigit = true; |
|||
} catch (NumberFormatException ignored) { |
|||
} |
|||
rawUserId = isDigit ? lastFolder : ""; |
|||
} |
|||
// /storage/emulated/0[1,2,...] |
|||
if (TextUtils.isEmpty(rawUserId)) { |
|||
rv.add(rawEmulatedStorageTarget); |
|||
} else { |
|||
rv.add(rawEmulatedStorageTarget + File.separator + rawUserId); |
|||
} |
|||
} |
|||
// Add all secondary storages |
|||
if (!TextUtils.isEmpty(rawSecondaryStoragesStr)) { |
|||
// All Secondary SD-CARDs splited into array |
|||
final String[] rawSecondaryStorages = rawSecondaryStoragesStr.split(File.pathSeparator); |
|||
Collections.addAll(rv, rawSecondaryStorages); |
|||
} |
|||
return rv; |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,19 @@ |
|||
package awais.instagrabber.viewmodels; |
|||
|
|||
import androidx.lifecycle.MutableLiveData; |
|||
import androidx.lifecycle.ViewModel; |
|||
|
|||
import java.util.List; |
|||
|
|||
import awais.instagrabber.utils.DataBox; |
|||
|
|||
public class FavoritesViewModel extends ViewModel { |
|||
private MutableLiveData<List<DataBox.FavoriteModel>> list; |
|||
|
|||
public MutableLiveData<List<DataBox.FavoriteModel>> getList() { |
|||
if (list == null) { |
|||
list = new MutableLiveData<>(); |
|||
} |
|||
return list; |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -0,0 +1,101 @@ |
|||
package awais.instagrabber.webservices; |
|||
|
|||
import android.util.Log; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
|
|||
import org.json.JSONException; |
|||
import org.json.JSONObject; |
|||
|
|||
import awais.instagrabber.repositories.TagsRepository; |
|||
import awais.instagrabber.utils.Constants; |
|||
import retrofit2.Call; |
|||
import retrofit2.Callback; |
|||
import retrofit2.Response; |
|||
import retrofit2.Retrofit; |
|||
|
|||
public class TagsService extends BaseService { |
|||
|
|||
private static final String TAG = "TagsService"; |
|||
|
|||
// web for www.instagram.com |
|||
private final TagsRepository webRepository; |
|||
|
|||
private static TagsService instance; |
|||
|
|||
private TagsService() { |
|||
final Retrofit webRetrofit = getRetrofitBuilder() |
|||
.baseUrl("https://www.instagram.com/") |
|||
.build(); |
|||
webRepository = webRetrofit.create(TagsRepository.class); |
|||
} |
|||
|
|||
public static TagsService getInstance() { |
|||
if (instance == null) { |
|||
instance = new TagsService(); |
|||
} |
|||
return instance; |
|||
} |
|||
|
|||
public void follow(@NonNull final String tag, |
|||
@NonNull final String csrfToken, |
|||
final ServiceCallback<Boolean> callback) { |
|||
final Call<String> request = webRepository.follow(Constants.USER_AGENT, |
|||
csrfToken, |
|||
tag); |
|||
request.enqueue(new Callback<String>() { |
|||
@Override |
|||
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) { |
|||
final String body = response.body(); |
|||
if (body == null) { |
|||
callback.onFailure(new RuntimeException("body is null")); |
|||
return; |
|||
} |
|||
try { |
|||
final JSONObject jsonObject = new JSONObject(body); |
|||
final String status = jsonObject.optString("status"); |
|||
callback.onSuccess(status.equals("ok")); |
|||
} catch (JSONException e) { |
|||
Log.e(TAG, "onResponse: ", e); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) { |
|||
// Log.e(TAG, "onFailure: ", t); |
|||
callback.onFailure(t); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public void unfollow(@NonNull final String tag, |
|||
@NonNull final String csrfToken, |
|||
final ServiceCallback<Boolean> callback) { |
|||
final Call<String> request = webRepository.unfollow(Constants.USER_AGENT, |
|||
csrfToken, |
|||
tag); |
|||
request.enqueue(new Callback<String>() { |
|||
@Override |
|||
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) { |
|||
final String body = response.body(); |
|||
if (body == null) { |
|||
callback.onFailure(new RuntimeException("body is null")); |
|||
return; |
|||
} |
|||
try { |
|||
final JSONObject jsonObject = new JSONObject(body); |
|||
final String status = jsonObject.optString("status"); |
|||
callback.onSuccess(status.equals("ok")); |
|||
} catch (JSONException e) { |
|||
Log.e(TAG, "onResponse: ", e); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) { |
|||
// Log.e(TAG, "onFailure: ", t); |
|||
callback.onFailure(t); |
|||
} |
|||
}); |
|||
} |
|||
} |
Before Width: 200 | Height: 200 | Size: 4.4 KiB |
Before Width: 200 | Height: 200 | Size: 4.2 KiB |
@ -0,0 +1,10 @@ |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:tint="?attr/colorControlNormal" |
|||
android:viewportWidth="24" |
|||
android:viewportHeight="24"> |
|||
<path |
|||
android:fillColor="#000" |
|||
android:pathData="M24 17V19H21V17C21 15.45 20.3 14.06 19.18 13.06C24 13.55 24 17 24 17M18 5C19.66 5 21 6.34 21 8C21 9.66 19.66 11 18 11C17.69 11 17.38 10.95 17.1 10.86C17.67 10.05 18 9.07 18 8C18 6.94 17.67 5.95 17.1 5.14C17.38 5.05 17.69 5 18 5M13 5C14.66 5 16 6.34 16 8C16 9.66 14.66 11 13 11C11.34 11 10 9.66 10 8C10 6.34 11.34 5 13 5M19 17V19H7V17C7 14.79 9.69 13 13 13C16.31 13 19 14.79 19 17M.464 13.12L2.59 11L.464 8.88L1.88 7.46L4 9.59L6.12 7.46L7.54 8.88L5.41 11L7.54 13.12L6.12 14.54L4 12.41L1.88 14.54Z" /> |
|||
</vector> |
@ -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="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM4,12c0,-4.42 3.58,-8 8,-8 1.85,0 3.55,0.63 4.9,1.69L5.69,16.9C4.63,15.55 4,13.85 4,12zM12,20c-1.85,0 -3.55,-0.63 -4.9,-1.69L18.31,7.1C19.37,8.45 20,10.15 20,12c0,4.42 -3.58,8 -8,8z"/> |
|||
</vector> |
@ -0,0 +1,10 @@ |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:tint="?attr/colorControlNormal" |
|||
android:viewportWidth="24" |
|||
android:viewportHeight="24"> |
|||
<path |
|||
android:fillColor="#000" |
|||
android:pathData="M11 7V13L16.2 16.1L17 14.9L12.5 12.2V7H11M20 12V18H22V12H20M20 20V22H22V20H20M18 20C16.3 21.3 14.3 22 12 22C6.5 22 2 17.5 2 12S6.5 2 12 2C16.8 2 20.9 5.4 21.8 10H19.7C18.8 6.6 15.7 4 12 4C7.6 4 4 7.6 4 12S7.6 20 12 20C14.4 20 16.5 18.9 18 17.3V20Z" /> |
|||
</vector> |
@ -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> |
@ -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.59,8L12,10.59 9.41,8 8,9.41 10.59,12 8,14.59 9.41,16 12,13.41 14.59,16 16,14.59 13.41,12 16,9.41 14.59,8zM12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/> |
|||
</vector> |
@ -0,0 +1,10 @@ |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:tint="?attr/colorControlNormal" |
|||
android:viewportWidth="24" |
|||
android:viewportHeight="24"> |
|||
<path |
|||
android:fillColor="#000" |
|||
android:pathData="M16,17V14H9V10H16V7L21,12L16,17M14,2A2,2 0 0,1 16,4V6H14V4H5V20H14V18H16V20A2,2 0 0,1 14,22H5A2,2 0 0,1 3,20V4A2,2 0 0,1 5,2H14Z" /> |
|||
</vector> |
@ -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="M18,2L6,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,4c0,-1.1 -0.9,-2 -2,-2zM9,4h2v5l-1,-0.75L9,9L9,4zM18,20L6,20L6,4h1v9l3,-2.25L13,13L13,4h5v16z"/> |
|||
</vector> |
@ -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="M20.5,3l-0.16,0.03L15,5.1 9,3 3.36,4.9c-0.21,0.07 -0.36,0.25 -0.36,0.48L3,20.5c0,0.28 0.22,0.5 0.5,0.5l0.16,-0.03L9,18.9l6,2.1 5.64,-1.9c0.21,-0.07 0.36,-0.25 0.36,-0.48L21,3.5c0,-0.28 -0.22,-0.5 -0.5,-0.5zM10,5.47l4,1.4v11.66l-4,-1.4L10,5.47zM5,6.46l3,-1.01v11.7l-3,1.16L5,6.46zM19,17.54l-3,1.01L16,6.86l3,-1.16v11.84z"/> |
|||
</vector> |
@ -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="M15,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM15,6c1.1,0 2,0.9 2,2s-0.9,2 -2,2 -2,-0.9 -2,-2 0.9,-2 2,-2zM15,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4zM9,18c0.22,-0.72 3.31,-2 6,-2 2.7,0 5.8,1.29 6,2L9,18zM6,15v-3h3v-2L6,10L6,7L4,7v3L1,10v2h3v3z"/> |
|||
</vector> |
@ -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="M15,6c1.1,0 2,0.9 2,2 0,0.99 -0.73,1.82 -1.67,1.97l-2.31,-2.31C13.19,6.72 14.01,6 15,6m0,-2c-2.21,0 -4,1.79 -4,4 0,0.18 0.03,0.35 0.05,0.52l3.43,3.43c0.17,0.02 0.34,0.05 0.52,0.05 2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4zM16.69,14.16L22.53,20L23,20v-2c0,-2.14 -3.56,-3.5 -6.31,-3.84zM13.01,16.13L14.88,18L9,18c0.08,-0.24 0.88,-1.01 2.91,-1.57l1.1,-0.3M1.41,1.71L0,3.12l4,4L4,10L1,10v2h3v3h2v-3h2.88l2.51,2.51C9.19,15.11 7,16.3 7,18v2h9.88l4,4 1.41,-1.41L1.41,1.71zM6,10v-0.88l0.88,0.88L6,10z"/> |
|||
</vector> |
@ -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="M19,2L5,2c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h4l3,3 3,-3h4c1.1,0 2,-0.9 2,-2L21,4c0,-1.1 -0.9,-2 -2,-2zM19,18h-4.83l-0.59,0.59L12,20.17l-1.59,-1.59 -0.58,-0.58L5,18L5,4h14v14zM12,11c1.65,0 3,-1.35 3,-3s-1.35,-3 -3,-3 -3,1.35 -3,3 1.35,3 3,3zM12,7c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM18,15.58c0,-2.5 -3.97,-3.58 -6,-3.58s-6,1.08 -6,3.58L6,17h12v-1.42zM8.48,15c0.74,-0.51 2.23,-1 3.52,-1s2.78,0.49 3.52,1L8.48,15z"/> |
|||
</vector> |
@ -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="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/> |
|||
</vector> |
@ -0,0 +1,10 @@ |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:tint="?attr/colorControlNormal" |
|||
android:viewportWidth="24" |
|||
android:viewportHeight="24"> |
|||
<path |
|||
android:fillColor="#000" |
|||
android:pathData="M5.8 21L7.4 14L2 9.2L9.2 8.6L12 2L14.8 8.6L22 9.2L18.8 12H18C17.3 12 16.6 12.1 15.9 12.4L18.1 10.5L13.7 10.1L12 6.1L10.3 10.1L5.9 10.5L9.2 13.4L8.2 17.7L12 15.4L12.5 15.7C12.3 16.2 12.1 16.8 12.1 17.3L5.8 21M17 14V17H14V19H17V22H19V19H22V17H19V14H17Z" /> |
|||
</vector> |
@ -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> |
@ -0,0 +1,8 @@ |
|||
<!-- drawable/star_check.xml --> |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:height="24dp" |
|||
android:width="24dp" |
|||
android:viewportWidth="24" |
|||
android:viewportHeight="24"> |
|||
<path android:fillColor="#000" android:pathData="M5.8 21L7.4 14L2 9.2L9.2 8.6L12 2L14.8 8.6L22 9.2L18.8 12H18C14.9 12 12.4 14.3 12 17.3L5.8 21M17.8 21.2L22.6 16.4L21.3 15L17.7 18.6L16.2 17L15 18.2L17.8 21.2" /> |
|||
</vector> |
@ -0,0 +1,6 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<selector xmlns:android="http://schemas.android.com/apk/res/android"> |
|||
<item android:drawable="@drawable/ic_outline_star_plus_24" android:state_checked="false" /> |
|||
<item android:drawable="@drawable/ic_star_check_24" android:state_checked="true" /> |
|||
<item android:drawable="@drawable/ic_outline_star_plus_24" /> |
|||
</selector> |
@ -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> |
@ -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> |
@ -0,0 +1,7 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" |
|||
xmlns:tools="http://schemas.android.com/tools" |
|||
android:id="@+id/favorite_list" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
tools:listitem="@layout/item_suggestion" /> |
@ -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> |
@ -0,0 +1,13 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<androidx.appcompat.widget.AppCompatTextView xmlns:android="http://schemas.android.com/apk/res/android" |
|||
xmlns:tools="http://schemas.android.com/tools" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:paddingLeft="16dp" |
|||
android:paddingTop="16dp" |
|||
android:paddingRight="16dp" |
|||
android:paddingBottom="8dp" |
|||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2" |
|||
android:textColor="?colorAccent" |
|||
android:textStyle="bold" |
|||
tools:text="HEADERS" /> |
@ -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> |
@ -1,12 +1,25 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<menu xmlns:android="http://schemas.android.com/apk/res/android" |
|||
xmlns:app="http://schemas.android.com/apk/res-auto"> |
|||
<!--<item--> |
|||
<!-- android:id="@+id/favourites"--> |
|||
<!-- android:enabled="true"--> |
|||
<!-- android:icon="@drawable/ic_star_24"--> |
|||
<!-- android:title="@string/title_favorites"--> |
|||
<!-- android:visible="false"--> |
|||
<!-- app:showAsAction="ifRoom" />--> |
|||
|
|||
<item |
|||
android:id="@+id/favourites" |
|||
android:enabled="true" |
|||
android:icon="@drawable/ic_star_24" |
|||
android:title="@string/title_favorites" |
|||
android:id="@+id/block" |
|||
android:icon="@drawable/ic_block_24" |
|||
android:title="@string/block" |
|||
android:visible="false" |
|||
app:showAsAction="ifRoom" /> |
|||
app:showAsAction="never" /> |
|||
|
|||
<item |
|||
android:id="@+id/restrict" |
|||
android:icon="@drawable/ic_highlight_off_24" |
|||
android:title="@string/restrict" |
|||
android:visible="false" |
|||
app:showAsAction="never" /> |
|||
</menu> |
Some files were not shown because too many files changed in this diff
Write
Preview
Loading…
Cancel
Save
Reference in new issue