Browse Source
Merge branch 'master' into support-android-11
renovate/org.robolectric-robolectric-4.x
Merge branch 'master' into support-android-11
renovate/org.robolectric-robolectric-4.x
106 changed files with 2161 additions and 626 deletions
-
3app/build.gradle
-
64app/src/fdroid/java/awais/instagrabber/utils/UpdateChecker.java
-
61app/src/github/java/awais/instagrabber/utils/UpdateChecker.java
-
3app/src/main/AndroidManifest.xml
-
3app/src/main/java/awais/instagrabber/InstaGrabberApplication.java
-
25app/src/main/java/awais/instagrabber/activities/Login.java
-
208app/src/main/java/awais/instagrabber/activities/MainActivity.java
-
1app/src/main/java/awais/instagrabber/adapters/SuggestionsAdapter.java
-
156app/src/main/java/awais/instagrabber/adapters/TabsAdapter.java
-
88app/src/main/java/awais/instagrabber/adapters/viewholder/TabViewHolder.java
-
1app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemRavenMediaViewHolder.java
-
2app/src/main/java/awais/instagrabber/db/datasources/FavoriteDataSource.java
-
14app/src/main/java/awais/instagrabber/dialogs/AccountSwitcherDialogFragment.java
-
275app/src/main/java/awais/instagrabber/dialogs/TabOrderPreferenceDialogFragment.java
-
5app/src/main/java/awais/instagrabber/fragments/LocationFragment.java
-
11app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java
-
82app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java
-
10app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java
-
12app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java
-
9app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java
-
64app/src/main/java/awais/instagrabber/fragments/settings/GeneralPreferencesFragment.java
-
66app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java
-
1app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.java
-
8app/src/main/java/awais/instagrabber/fragments/settings/StoriesPreferencesFragment.java
-
110app/src/main/java/awais/instagrabber/models/Tab.java
-
1app/src/main/java/awais/instagrabber/repositories/SearchRepository.java
-
2app/src/main/java/awais/instagrabber/repositories/responses/notification/NotificationCounts.java
-
2app/src/main/java/awais/instagrabber/repositories/responses/search/SearchResponse.java
-
4app/src/main/java/awais/instagrabber/utils/Constants.java
-
17app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java
-
118app/src/main/java/awais/instagrabber/utils/FlavorTown.java
-
2app/src/main/java/awais/instagrabber/utils/MediaUploader.java
-
111app/src/main/java/awais/instagrabber/utils/ProcessPhoenix.java
-
15app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java
-
11app/src/main/java/awais/instagrabber/utils/SettingsHelper.java
-
7app/src/main/java/awais/instagrabber/utils/TextUtils.java
-
38app/src/main/java/awais/instagrabber/utils/UpdateCheckCommon.java
-
58app/src/main/java/awais/instagrabber/utils/UpdateChecker.java
-
128app/src/main/java/awais/instagrabber/utils/Utils.java
-
42app/src/main/java/awais/instagrabber/webservices/BaseService.java
-
12app/src/main/java/awais/instagrabber/webservices/CollectionService.java
-
8app/src/main/java/awais/instagrabber/webservices/DirectMessagesService.java
-
8app/src/main/java/awais/instagrabber/webservices/DiscoverService.java
-
12app/src/main/java/awais/instagrabber/webservices/FeedService.java
-
8app/src/main/java/awais/instagrabber/webservices/FriendshipService.java
-
8app/src/main/java/awais/instagrabber/webservices/GifService.java
-
8app/src/main/java/awais/instagrabber/webservices/GraphQLService.java
-
8app/src/main/java/awais/instagrabber/webservices/LocationService.java
-
8app/src/main/java/awais/instagrabber/webservices/MediaService.java
-
29app/src/main/java/awais/instagrabber/webservices/NewsService.java
-
8app/src/main/java/awais/instagrabber/webservices/ProfileService.java
-
112app/src/main/java/awais/instagrabber/webservices/RetrofitFactory.java
-
13app/src/main/java/awais/instagrabber/webservices/SearchService.java
-
40app/src/main/java/awais/instagrabber/webservices/StoriesService.java
-
8app/src/main/java/awais/instagrabber/webservices/TagsService.java
-
8app/src/main/java/awais/instagrabber/webservices/UserService.java
-
2app/src/main/java/awais/instagrabber/webservices/interceptors/AddCookiesInterceptor.java
-
136app/src/main/java/awais/instagrabber/webservices/interceptors/IgErrorsInterceptor.java
-
2app/src/main/java/awais/instagrabber/webservices/interceptors/LoggingInterceptor.java
-
9app/src/main/res/drawable/ic_discover.xml
-
10app/src/main/res/drawable/ic_explore_24.xml
-
9app/src/main/res/drawable/ic_feed.xml
-
10app/src/main/res/drawable/ic_home_24.xml
-
10app/src/main/res/drawable/ic_message_24.xml
-
10app/src/main/res/drawable/ic_person_24.xml
-
10app/src/main/res/drawable/ic_round_add_circle_24.xml
-
10app/src/main/res/drawable/ic_round_drag_handle_24.xml
-
10app/src/main/res/drawable/ic_round_remove_circle_24.xml
-
5app/src/main/res/layout/activity_main.xml
-
45app/src/main/res/layout/item_tab_order_pref.xml
-
1app/src/main/res/layout/layout_profile_details.xml
-
12app/src/main/res/menu/logged_out_bottom_navigation_menu.xml
-
30app/src/main/res/menu/main_bottom_navigation_menu.xml
-
13app/src/main/res/menu/story_menu.xml
-
43app/src/main/res/navigation/favorites_nav_graph.xml
-
1app/src/main/res/navigation/more_nav_graph.xml
-
42app/src/main/res/navigation/notification_viewer_nav_graph.xml
-
20app/src/main/res/navigation/profile_nav_graph.xml
-
1app/src/main/res/values-ca/strings.xml
-
1app/src/main/res/values-cs/strings.xml
-
1app/src/main/res/values-de/strings.xml
-
1app/src/main/res/values-el/strings.xml
-
1app/src/main/res/values-es/strings.xml
-
1app/src/main/res/values-eu/strings.xml
-
1app/src/main/res/values-fa/strings.xml
-
1app/src/main/res/values-fr/strings.xml
-
1app/src/main/res/values-hi/strings.xml
-
1app/src/main/res/values-in/strings.xml
-
1app/src/main/res/values-it/strings.xml
-
1app/src/main/res/values-ja/strings.xml
-
1app/src/main/res/values-kn/strings.xml
-
1app/src/main/res/values-mk/strings.xml
-
1app/src/main/res/values-nl/strings.xml
-
1app/src/main/res/values-or/strings.xml
-
1app/src/main/res/values-pl/strings.xml
-
1app/src/main/res/values-pt/strings.xml
-
1app/src/main/res/values-ru/strings.xml
-
1app/src/main/res/values-sk/strings.xml
-
1app/src/main/res/values-tr/strings.xml
-
1app/src/main/res/values-vi/strings.xml
@ -0,0 +1,64 @@ |
|||||
|
package awais.instagrabber.utils; |
||||
|
|
||||
|
import android.util.Log; |
||||
|
|
||||
|
import androidx.annotation.NonNull; |
||||
|
import androidx.annotation.Nullable; |
||||
|
import androidx.appcompat.app.AppCompatActivity; |
||||
|
|
||||
|
import org.json.JSONObject; |
||||
|
|
||||
|
import java.net.HttpURLConnection; |
||||
|
import java.net.URL; |
||||
|
|
||||
|
public class UpdateChecker { |
||||
|
private static final Object LOCK = new Object(); |
||||
|
private static final String TAG = UpdateChecker.class.getSimpleName(); |
||||
|
|
||||
|
private static UpdateChecker instance; |
||||
|
|
||||
|
public static UpdateChecker getInstance() { |
||||
|
if (instance == null) { |
||||
|
synchronized (LOCK) { |
||||
|
if (instance == null) { |
||||
|
instance = new UpdateChecker(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return instance; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Needs to be called asynchronously |
||||
|
* |
||||
|
* @return the latest version from f-droid |
||||
|
*/ |
||||
|
@Nullable |
||||
|
public String getLatestVersion() { |
||||
|
HttpURLConnection conn = null; |
||||
|
try { |
||||
|
conn = (HttpURLConnection) new URL("https://f-droid.org/api/v1/packages/me.austinhuang.instagrabber").openConnection(); |
||||
|
conn.setUseCaches(false); |
||||
|
conn.setRequestProperty("User-Agent", "https://Barinsta.AustinHuang.me / mailto:[email protected]"); |
||||
|
conn.connect(); |
||||
|
final int responseCode = conn.getResponseCode(); |
||||
|
if (responseCode == HttpURLConnection.HTTP_OK) { |
||||
|
final JSONObject data = new JSONObject(NetworkUtils.readFromConnection(conn)); |
||||
|
return "v" + data.getJSONArray("packages").getJSONObject(0).getString("versionName"); |
||||
|
// if (BuildConfig.VERSION_CODE < data.getInt("suggestedVersionCode")) { |
||||
|
// } |
||||
|
} |
||||
|
} catch (final Exception e) { |
||||
|
Log.e(TAG, "", e); |
||||
|
} finally { |
||||
|
if (conn != null) { |
||||
|
conn.disconnect(); |
||||
|
} |
||||
|
} |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
public void onDownload(@NonNull final AppCompatActivity context) { |
||||
|
Utils.openURL(context, "https://f-droid.org/packages/me.austinhuang.instagrabber/"); |
||||
|
} |
||||
|
} |
@ -0,0 +1,61 @@ |
|||||
|
package awais.instagrabber.utils; |
||||
|
|
||||
|
import android.content.Context; |
||||
|
import android.util.Log; |
||||
|
|
||||
|
import androidx.annotation.NonNull; |
||||
|
import androidx.annotation.Nullable; |
||||
|
|
||||
|
import java.net.HttpURLConnection; |
||||
|
import java.net.URL; |
||||
|
|
||||
|
public class UpdateChecker { |
||||
|
private static final Object LOCK = new Object(); |
||||
|
private static final String TAG = UpdateChecker.class.getSimpleName(); |
||||
|
|
||||
|
private static UpdateChecker instance; |
||||
|
|
||||
|
public static UpdateChecker getInstance() { |
||||
|
if (instance == null) { |
||||
|
synchronized (LOCK) { |
||||
|
if (instance == null) { |
||||
|
instance = new UpdateChecker(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return instance; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Needs to be called asynchronously |
||||
|
* |
||||
|
* @return the latest version from Github |
||||
|
*/ |
||||
|
@Nullable |
||||
|
public String getLatestVersion() { |
||||
|
HttpURLConnection conn = null; |
||||
|
try { |
||||
|
conn = (HttpURLConnection) new URL("https://github.com/austinhuang0131/barinsta/releases/latest").openConnection(); |
||||
|
conn.setInstanceFollowRedirects(false); |
||||
|
conn.setUseCaches(false); |
||||
|
conn.setRequestProperty("User-Agent", "https://Barinsta.AustinHuang.me / mailto:[email protected]"); |
||||
|
conn.connect(); |
||||
|
final int responseCode = conn.getResponseCode(); |
||||
|
if (responseCode == HttpURLConnection.HTTP_MOVED_TEMP) { |
||||
|
return "v" + conn.getHeaderField("Location").split("/v")[1]; |
||||
|
// return !version.equals(BuildConfig.VERSION_NAME); |
||||
|
} |
||||
|
} catch (final Exception e) { |
||||
|
Log.e(TAG, "", e); |
||||
|
} finally { |
||||
|
if (conn != null) { |
||||
|
conn.disconnect(); |
||||
|
} |
||||
|
} |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
public void onDownload(@NonNull final Context context) { |
||||
|
Utils.openURL(context, "https://github.com/austinhuang0131/instagrabber/releases/latest"); |
||||
|
} |
||||
|
} |
@ -0,0 +1,156 @@ |
|||||
|
package awais.instagrabber.adapters; |
||||
|
|
||||
|
import android.view.LayoutInflater; |
||||
|
import android.view.ViewGroup; |
||||
|
|
||||
|
import androidx.annotation.NonNull; |
||||
|
import androidx.annotation.StringRes; |
||||
|
import androidx.recyclerview.widget.DiffUtil; |
||||
|
import androidx.recyclerview.widget.ListAdapter; |
||||
|
import androidx.recyclerview.widget.RecyclerView; |
||||
|
|
||||
|
import com.google.common.collect.ImmutableList; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
import java.util.Objects; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
import awais.instagrabber.R; |
||||
|
import awais.instagrabber.adapters.viewholder.TabViewHolder; |
||||
|
import awais.instagrabber.databinding.ItemFavSectionHeaderBinding; |
||||
|
import awais.instagrabber.databinding.ItemTabOrderPrefBinding; |
||||
|
import awais.instagrabber.models.Tab; |
||||
|
import awais.instagrabber.utils.Utils; |
||||
|
|
||||
|
public class TabsAdapter extends ListAdapter<TabsAdapter.TabOrHeader, RecyclerView.ViewHolder> { |
||||
|
private static final DiffUtil.ItemCallback<TabOrHeader> DIFF_CALLBACK = new DiffUtil.ItemCallback<TabOrHeader>() { |
||||
|
@Override |
||||
|
public boolean areItemsTheSame(@NonNull final TabOrHeader oldItem, @NonNull final TabOrHeader newItem) { |
||||
|
if (oldItem.isHeader() && newItem.isHeader()) { |
||||
|
return oldItem.header == newItem.header; |
||||
|
} |
||||
|
if (!oldItem.isHeader() && !newItem.isHeader()) { |
||||
|
final Tab oldTab = oldItem.tab; |
||||
|
final Tab newTab = newItem.tab; |
||||
|
return oldTab.getIconResId() == newTab.getIconResId() |
||||
|
&& Objects.equals(oldTab.getTitle(), newTab.getTitle()); |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean areContentsTheSame(@NonNull final TabOrHeader oldItem, @NonNull final TabOrHeader newItem) { |
||||
|
if (oldItem.isHeader() && newItem.isHeader()) { |
||||
|
return oldItem.header == newItem.header; |
||||
|
} |
||||
|
if (!oldItem.isHeader() && !newItem.isHeader()) { |
||||
|
final Tab oldTab = oldItem.tab; |
||||
|
final Tab newTab = newItem.tab; |
||||
|
return oldTab.getIconResId() == newTab.getIconResId() |
||||
|
&& Objects.equals(oldTab.getTitle(), newTab.getTitle()); |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
private final TabAdapterCallback tabAdapterCallback; |
||||
|
|
||||
|
private List<Tab> current = new ArrayList<>(); |
||||
|
private List<Tab> others = new ArrayList<>(); |
||||
|
|
||||
|
public TabsAdapter(@NonNull final TabAdapterCallback tabAdapterCallback) { |
||||
|
super(DIFF_CALLBACK); |
||||
|
this.tabAdapterCallback = tabAdapterCallback; |
||||
|
} |
||||
|
|
||||
|
@NonNull |
||||
|
@Override |
||||
|
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { |
||||
|
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); |
||||
|
if (viewType == 1) { |
||||
|
final ItemTabOrderPrefBinding binding = ItemTabOrderPrefBinding.inflate(layoutInflater, parent, false); |
||||
|
return new TabViewHolder(binding, tabAdapterCallback); |
||||
|
} |
||||
|
final ItemFavSectionHeaderBinding headerBinding = ItemFavSectionHeaderBinding.inflate(layoutInflater, parent, false); |
||||
|
return new DirectUsersAdapter.HeaderViewHolder(headerBinding); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) { |
||||
|
if (holder instanceof DirectUsersAdapter.HeaderViewHolder) { |
||||
|
((DirectUsersAdapter.HeaderViewHolder) holder).bind(R.string.other_tabs); |
||||
|
return; |
||||
|
} |
||||
|
if (holder instanceof TabViewHolder) { |
||||
|
final Tab tab = getItem(position).tab; |
||||
|
((TabViewHolder) holder).bind(tab, others.contains(tab), current.size() == 5); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public int getItemViewType(final int position) { |
||||
|
return getItem(position).isHeader() ? 0 : 1; |
||||
|
} |
||||
|
|
||||
|
public void submitList(final List<Tab> current, final List<Tab> others, final Runnable commitCallback) { |
||||
|
final ImmutableList.Builder<TabOrHeader> builder = ImmutableList.builder(); |
||||
|
if (current != null) { |
||||
|
builder.addAll(current.stream() |
||||
|
.map(TabOrHeader::new) |
||||
|
.collect(Collectors.toList())); |
||||
|
} |
||||
|
builder.add(new TabOrHeader(R.string.other_tabs)); |
||||
|
if (others != null) { |
||||
|
builder.addAll(others.stream() |
||||
|
.map(TabOrHeader::new) |
||||
|
.collect(Collectors.toList())); |
||||
|
} |
||||
|
// Mutable non-null copies |
||||
|
this.current = current != null ? new ArrayList<>(current) : new ArrayList<>(); |
||||
|
this.others = others != null ? new ArrayList<>(others) : new ArrayList<>(); |
||||
|
submitList(builder.build(), commitCallback); |
||||
|
} |
||||
|
|
||||
|
public void submitList(final List<Tab> current, final List<Tab> others) { |
||||
|
submitList(current, others, null); |
||||
|
} |
||||
|
|
||||
|
public void moveItem(final int from, final int to) { |
||||
|
final List<Tab> currentCopy = new ArrayList<>(current); |
||||
|
Utils.moveItem(from, to, currentCopy); |
||||
|
submitList(currentCopy, others); |
||||
|
tabAdapterCallback.onOrderChange(currentCopy); |
||||
|
} |
||||
|
|
||||
|
public int getCurrentCount() { |
||||
|
return current.size(); |
||||
|
} |
||||
|
|
||||
|
public static class TabOrHeader { |
||||
|
Tab tab; |
||||
|
int header; |
||||
|
|
||||
|
public TabOrHeader(final Tab tab) { |
||||
|
this.tab = tab; |
||||
|
} |
||||
|
|
||||
|
public TabOrHeader(@StringRes final int header) { |
||||
|
this.header = header; |
||||
|
} |
||||
|
|
||||
|
boolean isHeader() { |
||||
|
return header != 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public interface TabAdapterCallback { |
||||
|
void onStartDrag(TabViewHolder viewHolder); |
||||
|
|
||||
|
void onOrderChange(List<Tab> newOrderTabs); |
||||
|
|
||||
|
void onAdd(Tab tab); |
||||
|
|
||||
|
void onRemove(Tab tab); |
||||
|
} |
||||
|
} |
@ -0,0 +1,88 @@ |
|||||
|
package awais.instagrabber.adapters.viewholder; |
||||
|
|
||||
|
import android.annotation.SuppressLint; |
||||
|
import android.content.res.ColorStateList; |
||||
|
import android.graphics.drawable.Drawable; |
||||
|
import android.view.MotionEvent; |
||||
|
import android.view.View; |
||||
|
|
||||
|
import androidx.annotation.NonNull; |
||||
|
import androidx.core.content.ContextCompat; |
||||
|
import androidx.core.widget.ImageViewCompat; |
||||
|
import androidx.recyclerview.widget.RecyclerView; |
||||
|
|
||||
|
import com.google.android.material.color.MaterialColors; |
||||
|
|
||||
|
import awais.instagrabber.R; |
||||
|
import awais.instagrabber.adapters.TabsAdapter; |
||||
|
import awais.instagrabber.databinding.ItemTabOrderPrefBinding; |
||||
|
import awais.instagrabber.models.Tab; |
||||
|
|
||||
|
public class TabViewHolder extends RecyclerView.ViewHolder { |
||||
|
private final ItemTabOrderPrefBinding binding; |
||||
|
private final TabsAdapter.TabAdapterCallback tabAdapterCallback; |
||||
|
private final int highlightColor; |
||||
|
private final Drawable originalBgColor; |
||||
|
|
||||
|
private boolean draggable = true; |
||||
|
|
||||
|
@SuppressLint("ClickableViewAccessibility") |
||||
|
public TabViewHolder(@NonNull final ItemTabOrderPrefBinding binding, |
||||
|
@NonNull final TabsAdapter.TabAdapterCallback tabAdapterCallback) { |
||||
|
super(binding.getRoot()); |
||||
|
this.binding = binding; |
||||
|
this.tabAdapterCallback = tabAdapterCallback; |
||||
|
highlightColor = MaterialColors.getColor(itemView.getContext(), R.attr.colorControlHighlight, 0); |
||||
|
originalBgColor = itemView.getBackground(); |
||||
|
binding.handle.setOnTouchListener((v, event) -> { |
||||
|
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { |
||||
|
tabAdapterCallback.onStartDrag(this); |
||||
|
} |
||||
|
return true; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public void bind(@NonNull final Tab tab, |
||||
|
final boolean isInOthers, |
||||
|
final boolean isCurrentFull) { |
||||
|
draggable = !isInOthers; |
||||
|
binding.icon.setImageResource(tab.getIconResId()); |
||||
|
binding.title.setText(tab.getTitle()); |
||||
|
binding.handle.setVisibility(isInOthers ? View.GONE : View.VISIBLE); |
||||
|
binding.addRemove.setImageResource(isInOthers ? R.drawable.ic_round_add_circle_24 |
||||
|
: R.drawable.ic_round_remove_circle_24); |
||||
|
final ColorStateList tintList = ColorStateList.valueOf(ContextCompat.getColor( |
||||
|
itemView.getContext(), |
||||
|
isInOthers ? R.color.green_500 |
||||
|
: R.color.red_500)); |
||||
|
ImageViewCompat.setImageTintList(binding.addRemove, tintList); |
||||
|
binding.addRemove.setOnClickListener(v -> { |
||||
|
if (isInOthers) { |
||||
|
tabAdapterCallback.onAdd(tab); |
||||
|
return; |
||||
|
} |
||||
|
tabAdapterCallback.onRemove(tab); |
||||
|
}); |
||||
|
final boolean enabled = tab.isRemovable() |
||||
|
&& !(isInOthers && isCurrentFull); // All slots are full in current |
||||
|
binding.addRemove.setEnabled(enabled); |
||||
|
binding.addRemove.setAlpha(enabled ? 1 : 0.5F); |
||||
|
} |
||||
|
|
||||
|
public boolean isDraggable() { |
||||
|
return draggable; |
||||
|
} |
||||
|
|
||||
|
public void setDragging(final boolean isDragging) { |
||||
|
if (isDragging) { |
||||
|
if (highlightColor != 0) { |
||||
|
itemView.setBackgroundColor(highlightColor); |
||||
|
} else { |
||||
|
itemView.setAlpha(0.5F); |
||||
|
} |
||||
|
return; |
||||
|
} |
||||
|
itemView.setAlpha(1); |
||||
|
itemView.setBackground(originalBgColor); |
||||
|
} |
||||
|
} |
@ -0,0 +1,275 @@ |
|||||
|
package awais.instagrabber.dialogs; |
||||
|
|
||||
|
import android.app.Dialog; |
||||
|
import android.content.Context; |
||||
|
import android.graphics.Canvas; |
||||
|
import android.os.Bundle; |
||||
|
import android.util.Pair; |
||||
|
import android.view.View; |
||||
|
|
||||
|
import androidx.annotation.NonNull; |
||||
|
import androidx.annotation.Nullable; |
||||
|
import androidx.appcompat.app.AlertDialog; |
||||
|
import androidx.fragment.app.DialogFragment; |
||||
|
import androidx.recyclerview.widget.ItemTouchHelper; |
||||
|
import androidx.recyclerview.widget.ItemTouchHelper.SimpleCallback; |
||||
|
import androidx.recyclerview.widget.LinearLayoutManager; |
||||
|
import androidx.recyclerview.widget.RecyclerView; |
||||
|
|
||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder; |
||||
|
import com.google.common.collect.ImmutableList; |
||||
|
|
||||
|
import java.util.List; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
import awais.instagrabber.R; |
||||
|
import awais.instagrabber.adapters.DirectUsersAdapter; |
||||
|
import awais.instagrabber.adapters.TabsAdapter; |
||||
|
import awais.instagrabber.adapters.viewholder.TabViewHolder; |
||||
|
import awais.instagrabber.fragments.settings.PreferenceKeys; |
||||
|
import awais.instagrabber.models.Tab; |
||||
|
import awais.instagrabber.utils.Utils; |
||||
|
|
||||
|
import static androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_DRAG; |
||||
|
import static androidx.recyclerview.widget.ItemTouchHelper.DOWN; |
||||
|
import static androidx.recyclerview.widget.ItemTouchHelper.UP; |
||||
|
|
||||
|
public class TabOrderPreferenceDialogFragment extends DialogFragment { |
||||
|
private Callback callback; |
||||
|
private Context context; |
||||
|
private List<Tab> tabsInPref; |
||||
|
private ItemTouchHelper itemTouchHelper; |
||||
|
private AlertDialog dialog; |
||||
|
private List<Tab> newOrderTabs; |
||||
|
private List<Tab> newOtherTabs; |
||||
|
|
||||
|
private final TabsAdapter.TabAdapterCallback tabAdapterCallback = new TabsAdapter.TabAdapterCallback() { |
||||
|
@Override |
||||
|
public void onStartDrag(final TabViewHolder viewHolder) { |
||||
|
if (itemTouchHelper == null || viewHolder == null) return; |
||||
|
itemTouchHelper.startDrag(viewHolder); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onOrderChange(final List<Tab> newOrderTabs) { |
||||
|
if (newOrderTabs == null || tabsInPref == null || dialog == null) return; |
||||
|
TabOrderPreferenceDialogFragment.this.newOrderTabs = newOrderTabs; |
||||
|
setSaveButtonState(newOrderTabs); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onAdd(final Tab tab) { |
||||
|
// Add this tab to newOrderTabs |
||||
|
newOrderTabs = ImmutableList.<Tab>builder() |
||||
|
.addAll(newOrderTabs) |
||||
|
.add(tab) |
||||
|
.build(); |
||||
|
// Remove this tab from newOtherTabs |
||||
|
if (newOtherTabs != null) { |
||||
|
newOtherTabs = newOtherTabs.stream() |
||||
|
.filter(t -> !t.equals(tab)) |
||||
|
.collect(Collectors.toList()); |
||||
|
} |
||||
|
setSaveButtonState(newOrderTabs); |
||||
|
// submit these tab lists to adapter |
||||
|
if (adapter == null) return; |
||||
|
adapter.submitList(newOrderTabs, newOtherTabs, () -> list.postDelayed(() -> adapter.notifyDataSetChanged(), 300)); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onRemove(final Tab tab) { |
||||
|
// Remove this tab from newOrderTabs |
||||
|
newOrderTabs = newOrderTabs.stream() |
||||
|
.filter(t -> !t.equals(tab)) |
||||
|
.collect(Collectors.toList()); |
||||
|
// Add this tab to newOtherTabs |
||||
|
if (newOtherTabs != null) { |
||||
|
newOtherTabs = ImmutableList.<Tab>builder() |
||||
|
.addAll(newOtherTabs) |
||||
|
.add(tab) |
||||
|
.build(); |
||||
|
} |
||||
|
setSaveButtonState(newOrderTabs); |
||||
|
// submit these tab lists to adapter |
||||
|
if (adapter == null) return; |
||||
|
adapter.submitList(newOrderTabs, newOtherTabs, () -> list.postDelayed(() -> { |
||||
|
adapter.notifyDataSetChanged(); |
||||
|
if (tab.getNavigationRootId() == R.id.direct_messages_nav_graph) { |
||||
|
final ConfirmDialogFragment dialogFragment = ConfirmDialogFragment.newInstance( |
||||
|
111, 0, R.string.dm_remove_warning, R.string.ok, 0, 0 |
||||
|
); |
||||
|
dialogFragment.show(getChildFragmentManager(), "dm_warning_dialog"); |
||||
|
} |
||||
|
}, 500)); |
||||
|
} |
||||
|
|
||||
|
private void setSaveButtonState(final List<Tab> newOrderTabs) { |
||||
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE) |
||||
|
.setEnabled(!newOrderTabs.equals(tabsInPref)); |
||||
|
} |
||||
|
}; |
||||
|
private final SimpleCallback simpleCallback = new SimpleCallback(UP | DOWN, 0) { |
||||
|
private int movePosition = RecyclerView.NO_POSITION; |
||||
|
|
||||
|
@Override |
||||
|
public int getMovementFlags(@NonNull final RecyclerView recyclerView, @NonNull final RecyclerView.ViewHolder viewHolder) { |
||||
|
if (viewHolder instanceof DirectUsersAdapter.HeaderViewHolder) return 0; |
||||
|
if (viewHolder instanceof TabViewHolder && !((TabViewHolder) viewHolder).isDraggable()) return 0; |
||||
|
return super.getMovementFlags(recyclerView, viewHolder); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onChildDraw(@NonNull final Canvas c, |
||||
|
@NonNull final RecyclerView recyclerView, |
||||
|
@NonNull final RecyclerView.ViewHolder viewHolder, |
||||
|
final float dX, |
||||
|
final float dY, |
||||
|
final int actionState, |
||||
|
final boolean isCurrentlyActive) { |
||||
|
if (actionState != ACTION_STATE_DRAG) { |
||||
|
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); |
||||
|
return; |
||||
|
} |
||||
|
final TabsAdapter adapter = (TabsAdapter) recyclerView.getAdapter(); |
||||
|
if (adapter == null) { |
||||
|
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); |
||||
|
return; |
||||
|
} |
||||
|
// Do not allow dragging into 'Other tabs' category |
||||
|
float edgeY = dY; |
||||
|
final int lastPosition = adapter.getCurrentCount() - 1; |
||||
|
final View view = viewHolder.itemView; |
||||
|
// final int topEdge = recyclerView.getTop(); |
||||
|
final int bottomEdge = view.getHeight() * adapter.getCurrentCount() - view.getBottom(); |
||||
|
// if (movePosition == 0 && dY < topEdge) { |
||||
|
// edgeY = topEdge; |
||||
|
// } else |
||||
|
if (movePosition >= lastPosition && dY >= bottomEdge) { |
||||
|
edgeY = bottomEdge; |
||||
|
} |
||||
|
super.onChildDraw(c, recyclerView, viewHolder, dX, edgeY, actionState, isCurrentlyActive); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean onMove(@NonNull final RecyclerView recyclerView, |
||||
|
@NonNull final RecyclerView.ViewHolder viewHolder, |
||||
|
@NonNull final RecyclerView.ViewHolder target) { |
||||
|
final TabsAdapter adapter = (TabsAdapter) recyclerView.getAdapter(); |
||||
|
if (adapter == null) return false; |
||||
|
movePosition = target.getBindingAdapterPosition(); |
||||
|
if (movePosition >= adapter.getCurrentCount()) { |
||||
|
return false; |
||||
|
} |
||||
|
final int from = viewHolder.getBindingAdapterPosition(); |
||||
|
final int to = target.getBindingAdapterPosition(); |
||||
|
adapter.moveItem(from, to); |
||||
|
// adapter.notifyItemMoved(from, to); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onSwiped(@NonNull final RecyclerView.ViewHolder viewHolder, final int direction) {} |
||||
|
|
||||
|
@Override |
||||
|
public void onSelectedChanged(@Nullable final RecyclerView.ViewHolder viewHolder, final int actionState) { |
||||
|
super.onSelectedChanged(viewHolder, actionState); |
||||
|
if (!(viewHolder instanceof TabViewHolder)) { |
||||
|
movePosition = RecyclerView.NO_POSITION; |
||||
|
return; |
||||
|
} |
||||
|
if (actionState == ACTION_STATE_DRAG) { |
||||
|
((TabViewHolder) viewHolder).setDragging(true); |
||||
|
movePosition = viewHolder.getBindingAdapterPosition(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void clearView(@NonNull final RecyclerView recyclerView, |
||||
|
@NonNull final RecyclerView.ViewHolder viewHolder) { |
||||
|
super.clearView(recyclerView, viewHolder); |
||||
|
((TabViewHolder) viewHolder).setDragging(false); |
||||
|
movePosition = RecyclerView.NO_POSITION; |
||||
|
} |
||||
|
}; |
||||
|
private TabsAdapter adapter; |
||||
|
private RecyclerView list; |
||||
|
|
||||
|
public static TabOrderPreferenceDialogFragment newInstance() { |
||||
|
final Bundle args = new Bundle(); |
||||
|
final TabOrderPreferenceDialogFragment fragment = new TabOrderPreferenceDialogFragment(); |
||||
|
fragment.setArguments(args); |
||||
|
return fragment; |
||||
|
} |
||||
|
|
||||
|
public TabOrderPreferenceDialogFragment() {} |
||||
|
|
||||
|
@Override |
||||
|
public void onAttach(@NonNull final Context context) { |
||||
|
super.onAttach(context); |
||||
|
try { |
||||
|
callback = (Callback) getParentFragment(); |
||||
|
} catch (ClassCastException e) { |
||||
|
// throw new ClassCastException("Calling fragment must implement TabOrderPreferenceDialogFragment.Callback interface"); |
||||
|
} |
||||
|
this.context = context; |
||||
|
} |
||||
|
|
||||
|
@NonNull |
||||
|
@Override |
||||
|
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { |
||||
|
return new MaterialAlertDialogBuilder(context) |
||||
|
.setView(createView()) |
||||
|
.setPositiveButton(R.string.save, (d, w) -> { |
||||
|
final boolean hasChanged = newOrderTabs != null && !newOrderTabs.equals(tabsInPref); |
||||
|
if (hasChanged) { |
||||
|
saveNewOrder(); |
||||
|
} |
||||
|
if (callback == null) return; |
||||
|
callback.onSave(hasChanged); |
||||
|
}) |
||||
|
.setNegativeButton(R.string.cancel, (dialog, which) -> { |
||||
|
if (callback == null) return; |
||||
|
callback.onCancel(); |
||||
|
}) |
||||
|
.create(); |
||||
|
} |
||||
|
|
||||
|
private void saveNewOrder() { |
||||
|
final String newOrderString = newOrderTabs.stream() |
||||
|
.map(Tab::getGraphName) |
||||
|
.collect(Collectors.joining(",")); |
||||
|
Utils.settingsHelper.putString(PreferenceKeys.PREF_TAB_ORDER, newOrderString); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onStart() { |
||||
|
super.onStart(); |
||||
|
final Dialog dialog = getDialog(); |
||||
|
if (!(dialog instanceof AlertDialog)) return; |
||||
|
this.dialog = (AlertDialog) dialog; |
||||
|
this.dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); |
||||
|
} |
||||
|
|
||||
|
@NonNull |
||||
|
private View createView() { |
||||
|
list = new RecyclerView(context); |
||||
|
list.setLayoutManager(new LinearLayoutManager(context)); |
||||
|
itemTouchHelper = new ItemTouchHelper(simpleCallback); |
||||
|
itemTouchHelper.attachToRecyclerView(list); |
||||
|
adapter = new TabsAdapter(tabAdapterCallback); |
||||
|
list.setAdapter(adapter); |
||||
|
final Pair<List<Tab>, List<Tab>> navTabListPair = Utils.getNavTabList(context); |
||||
|
tabsInPref = navTabListPair.first; |
||||
|
// initially set newOrderTabs and newOtherTabs same as current tabs |
||||
|
newOrderTabs = navTabListPair.first; |
||||
|
newOtherTabs = navTabListPair.second; |
||||
|
adapter.submitList(navTabListPair.first, navTabListPair.second); |
||||
|
return list; |
||||
|
} |
||||
|
|
||||
|
public interface Callback { |
||||
|
void onSave(final boolean orderHasChanged); |
||||
|
|
||||
|
void onCancel(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,110 @@ |
|||||
|
package awais.instagrabber.models; |
||||
|
|
||||
|
import androidx.annotation.DrawableRes; |
||||
|
import androidx.annotation.IdRes; |
||||
|
import androidx.annotation.NavigationRes; |
||||
|
import androidx.annotation.NonNull; |
||||
|
|
||||
|
import java.util.Objects; |
||||
|
|
||||
|
public class Tab { |
||||
|
private final int iconResId; |
||||
|
private final String title; |
||||
|
private final boolean removable; |
||||
|
|
||||
|
/** |
||||
|
* This is name part of the navigation resource |
||||
|
* eg: @navigation/<b>graphName</b> |
||||
|
*/ |
||||
|
private final String graphName; |
||||
|
|
||||
|
/** |
||||
|
* This is the actual resource id of the navigation resource (R.navigation.graphName = navigationResId) |
||||
|
*/ |
||||
|
private final int navigationResId; |
||||
|
|
||||
|
/** |
||||
|
* This is the resource id of the root navigation tag of the navigation resource. |
||||
|
* <p>eg: inside R.navigation.direct_messages_nav_graph, the id of the root tag is R.id.direct_messages_nav_graph. |
||||
|
* <p>So this field would equal to the value of R.id.direct_messages_nav_graph |
||||
|
*/ |
||||
|
private final int navigationRootId; |
||||
|
|
||||
|
/** |
||||
|
* This is the start destination of the nav graph |
||||
|
*/ |
||||
|
private final int startDestinationFragmentId; |
||||
|
|
||||
|
public Tab(@DrawableRes final int iconResId, |
||||
|
@NonNull final String title, |
||||
|
final boolean removable, |
||||
|
@NonNull final String graphName, |
||||
|
@NavigationRes final int navigationResId, |
||||
|
@IdRes final int navigationRootId, |
||||
|
@IdRes final int startDestinationFragmentId) { |
||||
|
this.iconResId = iconResId; |
||||
|
this.title = title; |
||||
|
this.removable = removable; |
||||
|
this.graphName = graphName; |
||||
|
this.navigationResId = navigationResId; |
||||
|
this.navigationRootId = navigationRootId; |
||||
|
this.startDestinationFragmentId = startDestinationFragmentId; |
||||
|
} |
||||
|
|
||||
|
public int getIconResId() { |
||||
|
return iconResId; |
||||
|
} |
||||
|
|
||||
|
public String getTitle() { |
||||
|
return title; |
||||
|
} |
||||
|
|
||||
|
public boolean isRemovable() { |
||||
|
return removable; |
||||
|
} |
||||
|
|
||||
|
public String getGraphName() { |
||||
|
return graphName; |
||||
|
} |
||||
|
|
||||
|
public int getNavigationResId() { |
||||
|
return navigationResId; |
||||
|
} |
||||
|
|
||||
|
public int getNavigationRootId() { |
||||
|
return navigationRootId; |
||||
|
} |
||||
|
|
||||
|
public int getStartDestinationFragmentId() { |
||||
|
return startDestinationFragmentId; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean equals(final Object o) { |
||||
|
if (this == o) return true; |
||||
|
if (o == null || getClass() != o.getClass()) return false; |
||||
|
final Tab tab = (Tab) o; |
||||
|
return iconResId == tab.iconResId && |
||||
|
removable == tab.removable && |
||||
|
navigationResId == tab.navigationResId && |
||||
|
navigationRootId == tab.navigationRootId && |
||||
|
startDestinationFragmentId == tab.startDestinationFragmentId && |
||||
|
Objects.equals(title, tab.title) && |
||||
|
Objects.equals(graphName, tab.graphName); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public int hashCode() { |
||||
|
return Objects.hash(iconResId, title, removable, graphName, navigationResId, navigationRootId, startDestinationFragmentId); |
||||
|
} |
||||
|
|
||||
|
@NonNull |
||||
|
@Override |
||||
|
public String toString() { |
||||
|
return "Tab{" + |
||||
|
"title='" + title + '\'' + |
||||
|
", removable=" + removable + |
||||
|
", graphName='" + graphName + '\'' + |
||||
|
'}'; |
||||
|
} |
||||
|
} |
@ -0,0 +1,111 @@ |
|||||
|
/* |
||||
|
* Copyright (C) 2014 Jake Wharton |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package awais.instagrabber.utils; |
||||
|
|
||||
|
import android.app.Activity; |
||||
|
import android.app.ActivityManager; |
||||
|
import android.content.Context; |
||||
|
import android.content.Intent; |
||||
|
import android.os.Bundle; |
||||
|
import android.os.Process; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.Arrays; |
||||
|
import java.util.List; |
||||
|
|
||||
|
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; |
||||
|
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; |
||||
|
|
||||
|
/** |
||||
|
* Process Phoenix facilitates restarting your application process. This should only be used for |
||||
|
* things like fundamental state changes in your debug builds (e.g., changing from staging to |
||||
|
* production). |
||||
|
* <p> |
||||
|
* Trigger process recreation by calling {@link #triggerRebirth} with a {@link Context} instance. |
||||
|
*/ |
||||
|
public final class ProcessPhoenix extends Activity { |
||||
|
private static final String KEY_RESTART_INTENTS = "phoenix_restart_intents"; |
||||
|
|
||||
|
/** |
||||
|
* Call to restart the application process using the {@linkplain Intent#CATEGORY_DEFAULT default} |
||||
|
* activity as an intent. |
||||
|
* <p> |
||||
|
* Behavior of the current process after invoking this method is undefined. |
||||
|
*/ |
||||
|
public static void triggerRebirth(Context context) { |
||||
|
triggerRebirth(context, getRestartIntent(context)); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Call to restart the application process using the specified intents. |
||||
|
* <p> |
||||
|
* Behavior of the current process after invoking this method is undefined. |
||||
|
*/ |
||||
|
public static void triggerRebirth(Context context, Intent... nextIntents) { |
||||
|
Intent intent = new Intent(context, ProcessPhoenix.class); |
||||
|
intent.addFlags(FLAG_ACTIVITY_NEW_TASK); // In case we are called with non-Activity context. |
||||
|
intent.putParcelableArrayListExtra(KEY_RESTART_INTENTS, new ArrayList<>(Arrays.asList(nextIntents))); |
||||
|
context.startActivity(intent); |
||||
|
if (context instanceof Activity) { |
||||
|
((Activity) context).finish(); |
||||
|
} |
||||
|
Runtime.getRuntime().exit(0); // Kill kill kill! |
||||
|
} |
||||
|
|
||||
|
private static Intent getRestartIntent(Context context) { |
||||
|
String packageName = context.getPackageName(); |
||||
|
Intent defaultIntent = context.getPackageManager().getLaunchIntentForPackage(packageName); |
||||
|
if (defaultIntent != null) { |
||||
|
defaultIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK); |
||||
|
return defaultIntent; |
||||
|
} |
||||
|
|
||||
|
throw new IllegalStateException("Unable to determine default activity for " |
||||
|
+ packageName |
||||
|
+ ". Does an activity specify the DEFAULT category in its intent filter?"); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void onCreate(Bundle savedInstanceState) { |
||||
|
super.onCreate(savedInstanceState); |
||||
|
|
||||
|
ArrayList<Intent> intents = getIntent().getParcelableArrayListExtra(KEY_RESTART_INTENTS); |
||||
|
startActivities(intents.toArray(new Intent[intents.size()])); |
||||
|
finish(); |
||||
|
Runtime.getRuntime().exit(0); // Kill kill kill! |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Checks if the current process is a temporary Phoenix Process. |
||||
|
* This can be used to avoid initialisation of unused resources or to prevent running code that |
||||
|
* is not multi-process ready. |
||||
|
* |
||||
|
* @return true if the current process is a temporary Phoenix Process |
||||
|
*/ |
||||
|
public static boolean isPhoenixProcess(Context context) { |
||||
|
int currentPid = Process.myPid(); |
||||
|
ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); |
||||
|
List<ActivityManager.RunningAppProcessInfo> runningProcesses = manager.getRunningAppProcesses(); |
||||
|
if (runningProcesses != null) { |
||||
|
for (ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) { |
||||
|
if (processInfo.pid == currentPid && processInfo.processName.endsWith(":phoenix")) { |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
} |
@ -0,0 +1,38 @@ |
|||||
|
package awais.instagrabber.utils; |
||||
|
|
||||
|
import android.content.Context; |
||||
|
import android.content.DialogInterface; |
||||
|
|
||||
|
import androidx.annotation.NonNull; |
||||
|
|
||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder; |
||||
|
|
||||
|
import awais.instagrabber.BuildConfig; |
||||
|
import awais.instagrabber.R; |
||||
|
|
||||
|
import static awais.instagrabber.utils.Utils.settingsHelper; |
||||
|
|
||||
|
public final class UpdateCheckCommon { |
||||
|
|
||||
|
public static boolean shouldShowUpdateDialog(final boolean force, |
||||
|
@NonNull final String version) { |
||||
|
final String skippedVersion = settingsHelper.getString(Constants.SKIPPED_VERSION); |
||||
|
return force || (!BuildConfig.DEBUG && !skippedVersion.equals(version)); |
||||
|
} |
||||
|
|
||||
|
public static void showUpdateDialog(@NonNull final Context context, |
||||
|
@NonNull final String version, |
||||
|
@NonNull final DialogInterface.OnClickListener onDownloadClickListener) { |
||||
|
AppExecutors.getInstance().mainThread().execute(() -> { |
||||
|
new MaterialAlertDialogBuilder(context) |
||||
|
.setTitle(context.getString(R.string.update_available, version)) |
||||
|
.setNeutralButton(R.string.skip_update, (dialog, which) -> { |
||||
|
settingsHelper.putString(Constants.SKIPPED_VERSION, version); |
||||
|
dialog.dismiss(); |
||||
|
}) |
||||
|
.setPositiveButton(R.string.action_download, onDownloadClickListener) |
||||
|
.setNegativeButton(R.string.cancel, null) |
||||
|
.show(); |
||||
|
}); |
||||
|
} |
||||
|
} |
@ -1,58 +0,0 @@ |
|||||
package awais.instagrabber.utils; |
|
||||
|
|
||||
import android.os.AsyncTask; |
|
||||
import android.util.Log; |
|
||||
|
|
||||
import androidx.annotation.NonNull; |
|
||||
|
|
||||
import org.json.JSONObject; |
|
||||
|
|
||||
import java.net.HttpURLConnection; |
|
||||
import java.net.URL; |
|
||||
|
|
||||
import awais.instagrabber.BuildConfig; |
|
||||
import awais.instagrabber.interfaces.FetchListener; |
|
||||
|
|
||||
public final class UpdateChecker extends AsyncTask<Void, Void, Boolean> { |
|
||||
private final FetchListener<String> fetchListener; |
|
||||
private String version; |
|
||||
|
|
||||
public UpdateChecker(final FetchListener<String> fetchListener) { |
|
||||
this.fetchListener = fetchListener; |
|
||||
} |
|
||||
|
|
||||
@NonNull |
|
||||
@Override |
|
||||
protected Boolean doInBackground(final Void... voids) { |
|
||||
try { |
|
||||
version = ""; |
|
||||
|
|
||||
HttpURLConnection conn = |
|
||||
(HttpURLConnection) new URL("https://f-droid.org/api/v1/packages/me.austinhuang.instagrabber").openConnection(); |
|
||||
conn.setUseCaches(false); |
|
||||
conn.setRequestProperty("User-Agent", "https://Barinsta.AustinHuang.me / mailto:[email protected]"); |
|
||||
conn.connect(); |
|
||||
|
|
||||
final int responseCode = conn.getResponseCode(); |
|
||||
if (responseCode == HttpURLConnection.HTTP_OK) { |
|
||||
final JSONObject data = new JSONObject(NetworkUtils.readFromConnection(conn)); |
|
||||
if (BuildConfig.VERSION_CODE < data.getInt("suggestedVersionCode")) { |
|
||||
version = data.getJSONArray("packages").getJSONObject(0).getString("versionName"); |
|
||||
return true; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
conn.disconnect(); |
|
||||
} catch (final Exception e) { |
|
||||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); |
|
||||
} |
|
||||
|
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected void onPostExecute(final Boolean result) { |
|
||||
if (result != null && result && fetchListener != null) |
|
||||
fetchListener.onResult("v"+version); |
|
||||
} |
|
||||
} |
|
@ -0,0 +1,112 @@ |
|||||
|
package awais.instagrabber.webservices; |
||||
|
|
||||
|
import androidx.annotation.NonNull; |
||||
|
|
||||
|
import com.google.gson.FieldNamingPolicy; |
||||
|
import com.google.gson.Gson; |
||||
|
import com.google.gson.GsonBuilder; |
||||
|
|
||||
|
import java.io.File; |
||||
|
|
||||
|
import awais.instagrabber.BuildConfig; |
||||
|
import awais.instagrabber.activities.MainActivity; |
||||
|
import awais.instagrabber.repositories.responses.Caption; |
||||
|
import awais.instagrabber.utils.Utils; |
||||
|
import awais.instagrabber.webservices.interceptors.AddCookiesInterceptor; |
||||
|
import awais.instagrabber.webservices.interceptors.IgErrorsInterceptor; |
||||
|
import okhttp3.Cache; |
||||
|
import okhttp3.OkHttpClient; |
||||
|
import retrofit2.Retrofit; |
||||
|
import retrofit2.converter.gson.GsonConverterFactory; |
||||
|
import retrofit2.converter.scalars.ScalarsConverterFactory; |
||||
|
|
||||
|
public final class RetrofitFactory { |
||||
|
private static final Object LOCK = new Object(); |
||||
|
|
||||
|
private static RetrofitFactory instance; |
||||
|
|
||||
|
private final int cacheSize = 10 * 1024 * 1024; // 10 MB |
||||
|
private final Cache cache = new Cache(new File(Utils.cacheDir), cacheSize); |
||||
|
|
||||
|
private IgErrorsInterceptor igErrorsInterceptor; |
||||
|
private MainActivity mainActivity; |
||||
|
private Retrofit.Builder builder; |
||||
|
private Retrofit retrofit; |
||||
|
private Retrofit retrofitWeb; |
||||
|
|
||||
|
public static void setup(@NonNull final MainActivity mainActivity) { |
||||
|
if (instance == null) { |
||||
|
synchronized (LOCK) { |
||||
|
if (instance == null) { |
||||
|
instance = new RetrofitFactory(mainActivity); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static RetrofitFactory getInstance() { |
||||
|
if (instance == null) { |
||||
|
throw new RuntimeException("Setup not done!"); |
||||
|
} |
||||
|
return instance; |
||||
|
} |
||||
|
|
||||
|
private RetrofitFactory(@NonNull final MainActivity mainActivity) { |
||||
|
this.mainActivity = mainActivity; |
||||
|
} |
||||
|
|
||||
|
private Retrofit.Builder getRetrofitBuilder() { |
||||
|
if (builder == null) { |
||||
|
igErrorsInterceptor = new IgErrorsInterceptor(mainActivity); |
||||
|
final OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder() |
||||
|
.followRedirects(false) |
||||
|
.followSslRedirects(false) |
||||
|
.cache(cache); |
||||
|
if (BuildConfig.DEBUG) { |
||||
|
// clientBuilder.addInterceptor(new LoggingInterceptor()); |
||||
|
} |
||||
|
clientBuilder.addInterceptor(new AddCookiesInterceptor()) |
||||
|
.addInterceptor(igErrorsInterceptor); |
||||
|
final Gson gson = new GsonBuilder() |
||||
|
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) |
||||
|
.registerTypeAdapter(Caption.class, new Caption.CaptionDeserializer()) |
||||
|
.setLenient() |
||||
|
.create(); |
||||
|
builder = new Retrofit.Builder() |
||||
|
.addConverterFactory(ScalarsConverterFactory.create()) |
||||
|
.addConverterFactory(GsonConverterFactory.create(gson)) |
||||
|
.client(clientBuilder.build()); |
||||
|
} |
||||
|
return builder; |
||||
|
} |
||||
|
|
||||
|
public Retrofit getRetrofit() { |
||||
|
if (retrofit == null) { |
||||
|
retrofit = getRetrofitBuilder() |
||||
|
.baseUrl("https://i.instagram.com") |
||||
|
.build(); |
||||
|
} |
||||
|
return retrofit; |
||||
|
} |
||||
|
|
||||
|
public Retrofit getRetrofitWeb() { |
||||
|
if (retrofitWeb == null) { |
||||
|
retrofitWeb = getRetrofitBuilder() |
||||
|
.baseUrl("https://www.instagram.com") |
||||
|
.build(); |
||||
|
} |
||||
|
return retrofitWeb; |
||||
|
} |
||||
|
|
||||
|
public void destroy() { |
||||
|
if (igErrorsInterceptor != null) { |
||||
|
igErrorsInterceptor.destroy(); |
||||
|
} |
||||
|
igErrorsInterceptor = null; |
||||
|
mainActivity = null; |
||||
|
retrofit = null; |
||||
|
retrofitWeb = null; |
||||
|
builder = null; |
||||
|
instance = null; |
||||
|
} |
||||
|
} |
@ -1,4 +1,4 @@ |
|||||
package awais.instagrabber.webservices; |
|
||||
|
package awais.instagrabber.webservices.interceptors; |
||||
|
|
||||
import androidx.annotation.NonNull; |
import androidx.annotation.NonNull; |
||||
|
|
@ -0,0 +1,136 @@ |
|||||
|
package awais.instagrabber.webservices.interceptors; |
||||
|
|
||||
|
import android.util.Log; |
||||
|
import android.view.View; |
||||
|
|
||||
|
import androidx.annotation.NonNull; |
||||
|
import androidx.annotation.StringRes; |
||||
|
|
||||
|
import com.google.android.material.snackbar.Snackbar; |
||||
|
|
||||
|
import org.json.JSONObject; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
|
||||
|
import awais.instagrabber.R; |
||||
|
import awais.instagrabber.activities.MainActivity; |
||||
|
import awais.instagrabber.dialogs.ConfirmDialogFragment; |
||||
|
import awais.instagrabber.utils.Constants; |
||||
|
import awais.instagrabber.utils.TextUtils; |
||||
|
import okhttp3.Interceptor; |
||||
|
import okhttp3.Request; |
||||
|
import okhttp3.Response; |
||||
|
import okhttp3.ResponseBody; |
||||
|
|
||||
|
public class IgErrorsInterceptor implements Interceptor { |
||||
|
private static final String TAG = IgErrorsInterceptor.class.getSimpleName(); |
||||
|
|
||||
|
private MainActivity mainActivity; |
||||
|
|
||||
|
public IgErrorsInterceptor(@NonNull final MainActivity mainActivity) { |
||||
|
this.mainActivity = mainActivity; |
||||
|
} |
||||
|
|
||||
|
@NonNull |
||||
|
@Override |
||||
|
public Response intercept(@NonNull final Chain chain) throws IOException { |
||||
|
final Request request = chain.request(); |
||||
|
final Response response = chain.proceed(request); |
||||
|
if (response.isSuccessful()) { |
||||
|
return response; |
||||
|
} |
||||
|
checkError(response); |
||||
|
return response; |
||||
|
} |
||||
|
|
||||
|
private void checkError(@NonNull final Response response) { |
||||
|
final int errorCode = response.code(); |
||||
|
switch (errorCode) { |
||||
|
case 429: // "429 Too Many Requests" |
||||
|
// ('Throttled by Instagram because of too many API requests.'); |
||||
|
showErrorDialog(R.string.throttle_error); |
||||
|
return; |
||||
|
case 431: // "431 Request Header Fields Too Large" |
||||
|
// show dialog? |
||||
|
Log.e(TAG, "Network error: " + getMessage(errorCode, "The request start-line and/or headers are too large to process.")); |
||||
|
return; |
||||
|
case 404: |
||||
|
showErrorDialog(R.string.not_found); |
||||
|
return; |
||||
|
case 302: // redirect |
||||
|
final String location = response.header("location"); |
||||
|
if (location.equals("https://www.instagram.com/accounts/login/")) { |
||||
|
// rate limited |
||||
|
showErrorDialog(R.string.rate_limit); |
||||
|
} |
||||
|
return; |
||||
|
} |
||||
|
final ResponseBody body = response.body(); |
||||
|
if (body == null) return; |
||||
|
try { |
||||
|
final String bodyString = body.string(); |
||||
|
final JSONObject jsonObject = new JSONObject(bodyString); |
||||
|
String message = jsonObject.optString("message", null); |
||||
|
if (!TextUtils.isEmpty(message)) { |
||||
|
message = message.toLowerCase(); |
||||
|
switch (message) { |
||||
|
case "user_has_logged_out": |
||||
|
showErrorDialog(R.string.account_logged_out); |
||||
|
return; |
||||
|
case "login_required": |
||||
|
showErrorDialog(R.string.login_required); |
||||
|
return; |
||||
|
case "execution failure": |
||||
|
showSnackbar(message); |
||||
|
return; |
||||
|
case "not authorized to view user": // Do we handle this in profile view fragment? |
||||
|
case "challenge_required": // Since we make users login using browser, we should not be getting this error in api requests |
||||
|
default: |
||||
|
showSnackbar(message); |
||||
|
Log.e(TAG, "checkError: " + bodyString); |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
final String errorType = jsonObject.optString("error_type", null); |
||||
|
if (TextUtils.isEmpty(errorType)) return; |
||||
|
if (errorType.equals("sentry_block")) { |
||||
|
showErrorDialog(R.string.sentry_block); |
||||
|
return; |
||||
|
} |
||||
|
if (errorType.equals("inactive user")) { |
||||
|
showErrorDialog(R.string.inactive_user); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
Log.e(TAG, "checkError: ", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void showSnackbar(final String message) { |
||||
|
final View view = mainActivity.getRootView(); |
||||
|
if (view == null) return; |
||||
|
Snackbar.make(view, message, Snackbar.LENGTH_LONG).show(); |
||||
|
} |
||||
|
|
||||
|
@NonNull |
||||
|
private String getMessage(final int errorCode, final String message) { |
||||
|
return String.format("code: %s, internalMessage: %s", errorCode, message); |
||||
|
} |
||||
|
|
||||
|
private void showErrorDialog(@StringRes final int messageResId) { |
||||
|
if (mainActivity == null) return; |
||||
|
if (messageResId == 0) return; |
||||
|
final ConfirmDialogFragment dialogFragment = ConfirmDialogFragment.newInstance( |
||||
|
Constants.GLOBAL_NETWORK_ERROR_DIALOG_REQUEST_CODE, |
||||
|
R.string.error, |
||||
|
messageResId, |
||||
|
R.string.ok, |
||||
|
0, |
||||
|
0 |
||||
|
); |
||||
|
dialogFragment.show(mainActivity.getSupportFragmentManager(), "network_error_dialog"); |
||||
|
} |
||||
|
|
||||
|
public void destroy() { |
||||
|
mainActivity = null; |
||||
|
} |
||||
|
} |
@ -1,4 +1,4 @@ |
|||||
package awais.instagrabber.webservices; |
|
||||
|
package awais.instagrabber.webservices.interceptors; |
||||
|
|
||||
import android.util.Log; |
import android.util.Log; |
||||
|
|
@ -1,9 +0,0 @@ |
|||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|
||||
android:width="24dp" |
|
||||
android:height="24dp" |
|
||||
android:viewportWidth="24" |
|
||||
android:viewportHeight="24"> |
|
||||
<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,2zM11,19.93c-3.95,-0.49 -7,-3.85 -7,-7.93 0,-0.62 0.08,-1.21 0.21,-1.79L9,15v1c0,1.1 0.9,2 2,2v1.93zM17.9,17.39c-0.26,-0.81 -1,-1.39 -1.9,-1.39h-1v-3c0,-0.55 -0.45,-1 -1,-1L8,12v-2h2c0.55,0 1,-0.45 1,-1L11,7h2c1.1,0 2,-0.9 2,-2v-0.41c2.93,1.19 5,4.06 5,7.41 0,2.08 -0.8,3.97 -2.1,5.39z" /> |
|
||||
</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,10.9c-0.61,0 -1.1,0.49 -1.1,1.1s0.49,1.1 1.1,1.1c0.61,0 1.1,-0.49 1.1,-1.1s-0.49,-1.1 -1.1,-1.1zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM14.19,14.19L6,18l3.81,-8.19L18,6l-3.81,8.19z"/> |
||||
|
</vector> |
@ -1,9 +0,0 @@ |
|||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|
||||
android:width="24dp" |
|
||||
android:height="24dp" |
|
||||
android:viewportWidth="24" |
|
||||
android:viewportHeight="24"> |
|
||||
<path |
|
||||
android:fillColor="@android:color/white" |
|
||||
android:pathData="M13.5,0.67s0.74,2.65 0.74,4.8c0,2.06 -1.35,3.73 -3.41,3.73 -2.07,0 -3.63,-1.67 -3.63,-3.73l0.03,-0.36C5.21,7.51 4,10.62 4,14c0,4.42 3.58,8 8,8s8,-3.58 8,-8C20,8.61 17.41,3.8 13.5,0.67zM11.71,19c-1.78,0 -3.22,-1.4 -3.22,-3.14 0,-1.62 1.05,-2.76 2.81,-3.12 1.77,-0.36 3.6,-1.21 4.62,-2.58 0.39,1.29 0.59,2.65 0.59,4.04 0,2.65 -2.15,4.8 -4.8,4.8z" /> |
|
||||
</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="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/> |
||||
|
</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,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM18,14L6,14v-2h12v2zM18,11L6,11L6,9h12v2zM18,8L6,8L6,6h12v2z"/> |
||||
|
</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,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/> |
||||
|
</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,2zM16,13h-3v3c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1v-3L8,13c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1h3L11,8c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v3h3c0.55,0 1,0.45 1,1s-0.45,1 -1,1z"/> |
||||
|
</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,9H5c-0.55,0 -1,0.45 -1,1s0.45,1 1,1h14c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1zM5,15h14c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1H5c-0.55,0 -1,0.45 -1,1s0.45,1 1,1z"/> |
||||
|
</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,2zM16,13L8,13c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1h8c0.55,0 1,0.45 1,1s-0.45,1 -1,1z"/> |
||||
|
</vector> |
@ -0,0 +1,45 @@ |
|||||
|
<?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="56dp" |
||||
|
android:orientation="horizontal"> |
||||
|
|
||||
|
<androidx.appcompat.widget.AppCompatImageView |
||||
|
android:id="@+id/add_remove" |
||||
|
android:layout_width="24dp" |
||||
|
android:layout_height="match_parent" |
||||
|
android:layout_marginStart="16dp" |
||||
|
android:layout_marginEnd="8dp" |
||||
|
android:scaleType="centerInside" |
||||
|
tools:srcCompat="@drawable/ic_round_add_circle_24" |
||||
|
tools:tint="@color/green_500" /> |
||||
|
|
||||
|
<androidx.appcompat.widget.AppCompatImageView |
||||
|
android:id="@+id/icon" |
||||
|
android:layout_width="24dp" |
||||
|
android:layout_height="match_parent" |
||||
|
android:layout_marginStart="8dp" |
||||
|
android:layout_marginEnd="16dp" |
||||
|
android:scaleType="centerInside" |
||||
|
tools:srcCompat="@drawable/ic_home_24" /> |
||||
|
|
||||
|
<androidx.appcompat.widget.AppCompatTextView |
||||
|
android:id="@+id/title" |
||||
|
android:layout_width="0dp" |
||||
|
android:layout_height="match_parent" |
||||
|
android:layout_weight="1" |
||||
|
android:gravity="center_vertical" |
||||
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" |
||||
|
tools:text="@string/feed" /> |
||||
|
|
||||
|
<androidx.appcompat.widget.AppCompatImageView |
||||
|
android:id="@+id/handle" |
||||
|
android:layout_width="24dp" |
||||
|
android:layout_height="match_parent" |
||||
|
android:layout_marginStart="16dp" |
||||
|
android:layout_marginEnd="16dp" |
||||
|
android:scaleType="centerInside" |
||||
|
app:srcCompat="@drawable/ic_round_drag_handle_24" /> |
||||
|
</LinearLayout> |
@ -1,12 +0,0 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"> |
|
||||
<item |
|
||||
android:id="@+id/profile_nav_graph" |
|
||||
android:icon="@drawable/ic_profile_24" |
|
||||
android:title="@string/profile" /> |
|
||||
|
|
||||
<item |
|
||||
android:id="@+id/more_nav_graph" |
|
||||
android:icon="@drawable/ic_more_horiz_24" |
|
||||
android:title="@string/more" /> |
|
||||
</menu> |
|
@ -1,30 +0,0 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"> |
|
||||
<item |
|
||||
android:id="@+id/direct_messages_nav_graph" |
|
||||
android:icon="@drawable/ic_round_send_24" |
|
||||
android:title="@string/title_dm" /> |
|
||||
<item |
|
||||
android:id="@+id/feed_nav_graph" |
|
||||
android:icon="@drawable/ic_feed" |
|
||||
android:title="@string/feed" /> |
|
||||
<item |
|
||||
android:id="@+id/profile_nav_graph" |
|
||||
android:icon="@drawable/ic_profile_24" |
|
||||
android:title="@string/profile" /> |
|
||||
|
|
||||
<item |
|
||||
android:id="@+id/discover_nav_graph" |
|
||||
android:icon="@drawable/ic_discover" |
|
||||
android:title="@string/title_discover" /> |
|
||||
|
|
||||
<!--<item--> |
|
||||
<!-- android:id="@+id/favouritesFragment"--> |
|
||||
<!-- android:icon="@drawable/ic_star_24"--> |
|
||||
<!-- android:title="@string/title_favorites"/>--> |
|
||||
|
|
||||
<item |
|
||||
android:id="@+id/more_nav_graph" |
|
||||
android:icon="@drawable/ic_more_horiz_24" |
|
||||
android:title="@string/more" /> |
|
||||
</menu> |
|
@ -0,0 +1,43 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<navigation xmlns:android="http://schemas.android.com/apk/res/android" |
||||
|
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||
|
android:id="@+id/favorites_nav_graph" |
||||
|
app:startDestination="@id/favoritesFragment"> |
||||
|
|
||||
|
<include app:graph="@navigation/profile_nav_graph" /> |
||||
|
<include app:graph="@navigation/hashtag_nav_graph" /> |
||||
|
<include app:graph="@navigation/location_nav_graph" /> |
||||
|
<include app:graph="@navigation/comments_nav_graph" /> |
||||
|
<include app:graph="@navigation/likes_nav_graph" /> |
||||
|
|
||||
|
<action |
||||
|
android:id="@+id/action_global_profileFragment" |
||||
|
app:destination="@id/profile_nav_graph"> |
||||
|
<argument |
||||
|
android:name="username" |
||||
|
app:argType="string" |
||||
|
app:nullable="true" /> |
||||
|
</action> |
||||
|
|
||||
|
<action |
||||
|
android:id="@+id/action_global_hashTagFragment" |
||||
|
app:destination="@id/hashtag_nav_graph"> |
||||
|
<argument |
||||
|
android:name="hashtag" |
||||
|
app:argType="string" |
||||
|
app:nullable="false" /> |
||||
|
</action> |
||||
|
|
||||
|
<action |
||||
|
android:id="@+id/action_global_locationFragment" |
||||
|
app:destination="@id/location_nav_graph"> |
||||
|
<argument |
||||
|
android:name="locationId" |
||||
|
app:argType="long" /> |
||||
|
</action> |
||||
|
|
||||
|
<fragment |
||||
|
android:id="@+id/favoritesFragment" |
||||
|
android:name="awais.instagrabber.fragments.FavoritesFragment" |
||||
|
android:label="@string/title_favorites" /> |
||||
|
</navigation> |
Some files were not shown because too many files changed in this diff
Write
Preview
Loading…
Cancel
Save
Reference in new issue