Ammar Githam
4 years ago
14 changed files with 513 additions and 34 deletions
-
6app/src/main/java/awais/instagrabber/MainHelper.java
-
2app/src/main/java/awais/instagrabber/activities/CommentsViewer.java
-
3app/src/main/java/awais/instagrabber/activities/MainActivity.java
-
2app/src/main/java/awais/instagrabber/activities/NotificationsViewer.java
-
4app/src/main/java/awais/instagrabber/activities/PostViewer.java
-
2app/src/main/java/awais/instagrabber/activities/ProfileViewer.java
-
14app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedItemViewHolder.java
-
2app/src/main/java/awais/instagrabber/customviews/RamboTextView.java
-
341app/src/main/java/awais/instagrabber/fragments/LocationFragment.java
-
2app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java
-
20app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java
-
5app/src/main/java/awais/instagrabber/interfaces/MentionClickListener.java
-
131app/src/main/res/layout/fragment_location.xml
-
13app/src/main/res/navigation/feed_nav_graph.xml
@ -0,0 +1,341 @@ |
|||
package awais.instagrabber.fragments; |
|||
|
|||
import android.content.Intent; |
|||
import android.graphics.Typeface; |
|||
import android.net.Uri; |
|||
import android.os.AsyncTask; |
|||
import android.os.Bundle; |
|||
import android.text.SpannableStringBuilder; |
|||
import android.text.style.RelativeSizeSpan; |
|||
import android.text.style.StyleSpan; |
|||
import android.util.Log; |
|||
import android.view.ActionMode; |
|||
import android.view.LayoutInflater; |
|||
import android.view.MenuItem; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
import android.widget.TextView; |
|||
import android.widget.Toast; |
|||
|
|||
import androidx.activity.OnBackPressedCallback; |
|||
import androidx.activity.OnBackPressedDispatcher; |
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.appcompat.app.ActionBar; |
|||
import androidx.fragment.app.Fragment; |
|||
import androidx.lifecycle.ViewModelProvider; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Arrays; |
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.activities.MainActivity; |
|||
import awais.instagrabber.activities.PostViewer; |
|||
import awais.instagrabber.adapters.PostsAdapter; |
|||
import awais.instagrabber.asyncs.LocationFetcher; |
|||
import awais.instagrabber.asyncs.PostsFetcher; |
|||
import awais.instagrabber.asyncs.i.iStoryStatusFetcher; |
|||
import awais.instagrabber.customviews.PrimaryActionModeCallback; |
|||
import awais.instagrabber.customviews.helpers.GridAutofitLayoutManager; |
|||
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration; |
|||
import awais.instagrabber.customviews.helpers.NestedCoordinatorLayout; |
|||
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader; |
|||
import awais.instagrabber.databinding.FragmentLocationBinding; |
|||
import awais.instagrabber.fragments.main.viewmodels.PostsViewModel; |
|||
import awais.instagrabber.interfaces.FetchListener; |
|||
import awais.instagrabber.models.LocationModel; |
|||
import awais.instagrabber.models.PostModel; |
|||
import awais.instagrabber.models.StoryModel; |
|||
import awais.instagrabber.models.enums.DownloadMethod; |
|||
import awais.instagrabber.models.enums.ItemGetType; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.Utils; |
|||
import awaisomereport.LogCollector; |
|||
|
|||
import static awais.instagrabber.utils.Utils.logCollector; |
|||
import static awais.instagrabber.utils.Utils.settingsHelper; |
|||
|
|||
public class LocationFragment extends Fragment { |
|||
private static final String TAG = "LocationFragment"; |
|||
|
|||
private MainActivity fragmentActivity; |
|||
private FragmentLocationBinding binding; |
|||
private NestedCoordinatorLayout root; |
|||
private boolean shouldRefresh = true; |
|||
private String location; |
|||
private LocationModel locationModel; |
|||
private PostsViewModel postsViewModel; |
|||
private PostsAdapter postsAdapter; |
|||
private ActionMode actionMode; |
|||
private boolean hasNextPage; |
|||
private String endCursor; |
|||
private AsyncTask<?, ?, ?> currentlyExecuting; |
|||
private boolean isLoggedIn; |
|||
private StoryModel[] storyModels; |
|||
|
|||
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) { |
|||
@Override |
|||
public void handleOnBackPressed() { |
|||
if (postsAdapter == null) { |
|||
setEnabled(false); |
|||
remove(); |
|||
return; |
|||
} |
|||
postsAdapter.clearSelection(); |
|||
setEnabled(false); |
|||
remove(); |
|||
} |
|||
}; |
|||
private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback( |
|||
R.menu.multi_select_download_menu, |
|||
new PrimaryActionModeCallback.CallbacksHelper() { |
|||
@Override |
|||
public void onDestroy(final ActionMode mode) { |
|||
onBackPressedCallback.handleOnBackPressed(); |
|||
} |
|||
|
|||
@Override |
|||
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) { |
|||
if (item.getItemId() == R.id.action_download) { |
|||
if (postsAdapter == null || location == null) { |
|||
return false; |
|||
} |
|||
Utils.batchDownload(requireContext(), |
|||
location, |
|||
DownloadMethod.DOWNLOAD_MAIN, |
|||
postsAdapter.getSelectedModels()); |
|||
checkAndResetAction(); |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
}); |
|||
private final FetchListener<PostModel[]> postsFetchListener = new FetchListener<PostModel[]>() { |
|||
@Override |
|||
public void onResult(final PostModel[] result) { |
|||
binding.swipeRefreshLayout.setRefreshing(false); |
|||
if (result == null) return; |
|||
binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE)); |
|||
final List<PostModel> postModels = postsViewModel.getList().getValue(); |
|||
final List<PostModel> finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>() : new ArrayList<>(postModels); |
|||
finalList.addAll(Arrays.asList(result)); |
|||
postsViewModel.getList().postValue(finalList); |
|||
PostModel model = null; |
|||
if (result.length != 0) { |
|||
model = result[result.length - 1]; |
|||
} |
|||
if (model == null) return; |
|||
endCursor = model.getEndCursor(); |
|||
hasNextPage = model.hasNextPage(); |
|||
model.setPageCursor(false, null); |
|||
} |
|||
}; |
|||
|
|||
@Override |
|||
public void onCreate(@Nullable final Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
fragmentActivity = (MainActivity) requireActivity(); |
|||
} |
|||
|
|||
@Nullable |
|||
@Override |
|||
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { |
|||
if (root != null) { |
|||
shouldRefresh = false; |
|||
return root; |
|||
} |
|||
binding = FragmentLocationBinding.inflate(inflater, container, false); |
|||
root = binding.getRoot(); |
|||
return root; |
|||
} |
|||
|
|||
@Override |
|||
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { |
|||
if (!shouldRefresh) return; |
|||
init(); |
|||
shouldRefresh = false; |
|||
} |
|||
|
|||
@Override |
|||
public void onDestroy() { |
|||
super.onDestroy(); |
|||
if (postsViewModel != null) { |
|||
postsViewModel.getList().postValue(Collections.emptyList()); |
|||
} |
|||
} |
|||
|
|||
private void init() { |
|||
if (getArguments() == null) return; |
|||
final String cookie = settingsHelper.getString(Constants.COOKIE); |
|||
isLoggedIn = !Utils.isEmpty(cookie) && Utils.getUserIdFromCookie(cookie) != null; |
|||
final LocationFragmentArgs fragmentArgs = LocationFragmentArgs.fromBundle(getArguments()); |
|||
location = fragmentArgs.getLocation(); |
|||
setTitle(); |
|||
setupPosts(); |
|||
fetchLocationModel(); |
|||
} |
|||
|
|||
private void setupPosts() { |
|||
postsViewModel = new ViewModelProvider(this).get(PostsViewModel.class); |
|||
final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(requireContext(), Utils.convertDpToPx(110)); |
|||
binding.mainPosts.setLayoutManager(layoutManager); |
|||
binding.mainPosts.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4))); |
|||
postsAdapter = new PostsAdapter((postModel, position) -> { |
|||
if (postsAdapter.isSelecting()) { |
|||
if (actionMode == null) return; |
|||
final String title = getString(R.string.number_selected, postsAdapter.getSelectedModels().size()); |
|||
actionMode.setTitle(title); |
|||
return; |
|||
} |
|||
if (checkAndResetAction()) return; |
|||
startActivity(new Intent(requireContext(), PostViewer.class) |
|||
.putExtra(Constants.EXTRAS_INDEX, position) |
|||
.putExtra(Constants.EXTRAS_POST, postModel) |
|||
.putExtra(Constants.EXTRAS_USER, location) |
|||
.putExtra(Constants.EXTRAS_TYPE, ItemGetType.MAIN_ITEMS)); |
|||
|
|||
}, (model, position) -> { |
|||
if (!postsAdapter.isSelecting()) { |
|||
checkAndResetAction(); |
|||
return true; |
|||
} |
|||
if (onBackPressedCallback.isEnabled()) { |
|||
return true; |
|||
} |
|||
final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher(); |
|||
onBackPressedCallback.setEnabled(true); |
|||
actionMode = fragmentActivity.startActionMode(multiSelectAction); |
|||
final String title = getString(R.string.number_selected, 1); |
|||
actionMode.setTitle(title); |
|||
onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback); |
|||
return true; |
|||
}); |
|||
postsViewModel.getList().observe(fragmentActivity, postsAdapter::submitList); |
|||
binding.mainPosts.setAdapter(postsAdapter); |
|||
final RecyclerLazyLoader lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> { |
|||
if (!hasNextPage) return; |
|||
binding.swipeRefreshLayout.setRefreshing(true); |
|||
fetchPosts(); |
|||
endCursor = null; |
|||
}); |
|||
binding.mainPosts.addOnScrollListener(lazyLoader); |
|||
} |
|||
|
|||
private void fetchLocationModel() { |
|||
stopCurrentExecutor(); |
|||
binding.swipeRefreshLayout.setRefreshing(true); |
|||
currentlyExecuting = new LocationFetcher(location.split("/")[0], result -> { |
|||
locationModel = result; |
|||
binding.swipeRefreshLayout.setRefreshing(false); |
|||
if (locationModel == null) { |
|||
Toast.makeText(requireContext(), R.string.error_loading_profile, Toast.LENGTH_SHORT).show(); |
|||
return; |
|||
} |
|||
setTitle(); |
|||
setupLocationDetails(); |
|||
fetchPosts(); |
|||
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
} |
|||
|
|||
private void setupLocationDetails() { |
|||
final String locationId = locationModel.getId(); |
|||
binding.swipeRefreshLayout.setRefreshing(true); |
|||
if (isLoggedIn) { |
|||
new iStoryStatusFetcher(locationId.split("/")[0], null, true, false, false, false, stories -> { |
|||
storyModels = stories; |
|||
if (stories != null && stories.length > 0) { |
|||
binding.mainLocationImage.setStoriesBorder(); |
|||
} |
|||
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
} |
|||
binding.mainLocationImage.setImageURI(locationModel.getSdProfilePic()); |
|||
final String postCount = String.valueOf(locationModel.getPostCount()); |
|||
final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count, postCount)); |
|||
span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0); |
|||
span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0); |
|||
binding.mainLocPostCount.setText(span); |
|||
binding.mainLocPostCount.setVisibility(View.VISIBLE); |
|||
binding.locationFullName.setText(locationModel.getName()); |
|||
CharSequence biography = locationModel.getBio(); |
|||
binding.locationBiography.setCaptionIsExpandable(true); |
|||
binding.locationBiography.setCaptionIsExpanded(true); |
|||
|
|||
if (Utils.isEmpty(biography)) { |
|||
binding.locationBiography.setVisibility(View.GONE); |
|||
} else if (Utils.hasMentions(biography)) { |
|||
binding.locationBiography.setVisibility(View.VISIBLE); |
|||
biography = Utils.getMentionText(biography); |
|||
binding.locationBiography.setText(biography, TextView.BufferType.SPANNABLE); |
|||
// binding.locationBiography.setMentionClickListener(mentionClickListener); |
|||
} else { |
|||
binding.locationBiography.setVisibility(View.VISIBLE); |
|||
binding.locationBiography.setText(biography); |
|||
binding.locationBiography.setMentionClickListener(null); |
|||
} |
|||
|
|||
if (!locationModel.getGeo().startsWith("geo:0.0,0.0?z=17")) { |
|||
binding.btnMap.setVisibility(View.VISIBLE); |
|||
binding.btnMap.setOnClickListener(v -> { |
|||
final Intent intent = new Intent(Intent.ACTION_VIEW); |
|||
intent.setData(Uri.parse(locationModel.getGeo())); |
|||
startActivity(intent); |
|||
}); |
|||
} else { |
|||
binding.btnMap.setVisibility(View.GONE); |
|||
binding.btnMap.setOnClickListener(null); |
|||
} |
|||
|
|||
final String url = locationModel.getUrl(); |
|||
if (Utils.isEmpty(url)) { |
|||
binding.locationUrl.setVisibility(View.GONE); |
|||
} else if (!url.startsWith("http")) { |
|||
binding.locationUrl.setVisibility(View.VISIBLE); |
|||
binding.locationUrl.setText(Utils.getSpannableUrl("http://" + url)); |
|||
} else { |
|||
binding.locationUrl.setVisibility(View.VISIBLE); |
|||
binding.locationUrl.setText(Utils.getSpannableUrl(url)); |
|||
} |
|||
} |
|||
|
|||
private void fetchPosts() { |
|||
stopCurrentExecutor(); |
|||
currentlyExecuting = new PostsFetcher(locationModel.getId(), endCursor, postsFetchListener) |
|||
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
} |
|||
|
|||
public void stopCurrentExecutor() { |
|||
if (currentlyExecuting != null) { |
|||
try { |
|||
currentlyExecuting.cancel(true); |
|||
} catch (final Exception e) { |
|||
if (logCollector != null) |
|||
logCollector.appendException(e, LogCollector.LogFile.MAIN_HELPER, "stopCurrentExecutor"); |
|||
Log.e(TAG, "", e); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void setTitle() { |
|||
final ActionBar actionBar = fragmentActivity.getSupportActionBar(); |
|||
if (actionBar != null && locationModel != null) { |
|||
actionBar.setTitle(locationModel.getName()); |
|||
} |
|||
} |
|||
|
|||
private boolean checkAndResetAction() { |
|||
if (!onBackPressedCallback.isEnabled() && actionMode == null) { |
|||
return false; |
|||
} |
|||
if (onBackPressedCallback.isEnabled()) { |
|||
onBackPressedCallback.setEnabled(false); |
|||
onBackPressedCallback.remove(); |
|||
} |
|||
if (actionMode != null) { |
|||
actionMode.finish(); |
|||
actionMode = null; |
|||
} |
|||
return true; |
|||
} |
|||
} |
@ -0,0 +1,131 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<awais.instagrabber.customviews.helpers.NestedCoordinatorLayout 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:background="?attr/colorOnPrimarySurface"> |
|||
|
|||
<com.google.android.material.appbar.AppBarLayout |
|||
android:id="@+id/appBarLayout" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:background="?attr/colorOnPrimarySurface"> |
|||
|
|||
<com.google.android.material.appbar.CollapsingToolbarLayout |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
app:layout_scrollFlags="scroll|exitUntilCollapsed"> |
|||
|
|||
<RelativeLayout |
|||
android:id="@+id/locInfoContainer" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:animateLayoutChanges="true" |
|||
android:background="@null" |
|||
android:orientation="vertical" |
|||
android:paddingBottom="5dp"> |
|||
|
|||
<LinearLayout |
|||
android:id="@+id/locInfo" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:orientation="horizontal" |
|||
android:padding="@dimen/profile_info_container_bottom_space"> |
|||
|
|||
<awais.instagrabber.customviews.CircularImageView |
|||
android:id="@+id/mainLocationImage" |
|||
android:layout_width="@dimen/profile_picture_size" |
|||
android:layout_height="@dimen/profile_picture_size" |
|||
android:background="?selectableItemBackgroundBorderless" |
|||
app:actualImageScaleType="fitCenter" /> |
|||
|
|||
<androidx.appcompat.widget.AppCompatTextView |
|||
android:id="@+id/mainLocPostCount" |
|||
android:layout_width="0dp" |
|||
android:layout_height="match_parent" |
|||
android:layout_marginStart="12dp" |
|||
android:layout_marginLeft="12dp" |
|||
android:layout_marginEnd="12dp" |
|||
android:layout_marginRight="12dp" |
|||
android:layout_weight="1" |
|||
android:gravity="center" |
|||
android:textAppearance="@style/TextAppearance.AppCompat" |
|||
android:textSize="15sp" |
|||
tools:text="35\nPosts" /> |
|||
|
|||
<androidx.appcompat.widget.AppCompatButton |
|||
android:id="@+id/btnMap" |
|||
android:layout_width="0dp" |
|||
android:layout_height="wrap_content" |
|||
android:layout_gravity="center_vertical" |
|||
android:layout_weight="2" |
|||
android:text="@string/map" |
|||
android:textColor="@color/btn_green_text_color" |
|||
android:textSize="20sp" |
|||
android:visibility="gone" |
|||
app:backgroundTint="@color/btn_green_background" /> |
|||
</LinearLayout> |
|||
|
|||
<androidx.appcompat.widget.AppCompatTextView |
|||
android:id="@+id/locationFullName" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_below="@id/locInfo" |
|||
android:ellipsize="marquee" |
|||
android:paddingStart="10dp" |
|||
android:paddingLeft="10dp" |
|||
android:paddingEnd="10dp" |
|||
android:paddingRight="10dp" |
|||
android:singleLine="true" |
|||
android:textAppearance="@style/TextAppearance.AppCompat.Body2" |
|||
android:textSize="16sp" |
|||
tools:text="OUR HOUSE" /> |
|||
|
|||
<awais.instagrabber.customviews.RamboTextView |
|||
android:id="@+id/locationBiography" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_below="@id/locationFullName" |
|||
android:background="?android:selectableItemBackground" |
|||
android:paddingStart="10dp" |
|||
android:paddingLeft="10dp" |
|||
android:paddingEnd="10dp" |
|||
android:paddingRight="10dp" |
|||
android:textAppearance="@style/TextAppearance.AppCompat.Body1" |
|||
android:textSize="16sp" |
|||
android:visibility="gone" |
|||
tools:text="IN THE MIDDLE OF OUR STREET" /> |
|||
|
|||
<awais.instagrabber.customviews.RamboTextView |
|||
android:id="@+id/locationUrl" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_below="@id/locationBiography" |
|||
android:ellipsize="marquee" |
|||
android:paddingStart="10dp" |
|||
android:paddingLeft="10dp" |
|||
android:paddingEnd="10dp" |
|||
android:paddingRight="10dp" |
|||
android:textAppearance="@style/TextAppearance.AppCompat.Body1" |
|||
android:textSize="16sp" |
|||
android:visibility="gone" |
|||
tools:text="https://austinhuang.me/" /> |
|||
</RelativeLayout> |
|||
</com.google.android.material.appbar.CollapsingToolbarLayout> |
|||
</com.google.android.material.appbar.AppBarLayout> |
|||
|
|||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout |
|||
android:id="@+id/swipeRefreshLayout" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> |
|||
|
|||
<androidx.recyclerview.widget.RecyclerView |
|||
android:id="@+id/mainPosts" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:clipToPadding="false" |
|||
tools:listitem="@layout/item_post" /> |
|||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> |
|||
</awais.instagrabber.customviews.helpers.NestedCoordinatorLayout> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue