mirror of
https://github.com/KokaKiwi/BarInsta
synced 2024-11-26 00:27:30 +00:00
wip: archive and feed stories as a list
This commit is contained in:
parent
2cc8a6e6b0
commit
6e4fa9fdbf
97
app/src/main/java/awais/instagrabber/adapters/FeedStoriesListAdapter.java
Executable file
97
app/src/main/java/awais/instagrabber/adapters/FeedStoriesListAdapter.java
Executable file
@ -0,0 +1,97 @@
|
|||||||
|
package awais.instagrabber.adapters;
|
||||||
|
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.recyclerview.widget.DiffUtil;
|
||||||
|
import androidx.recyclerview.widget.ListAdapter;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import awais.instagrabber.adapters.viewholder.StoryListViewHolder;
|
||||||
|
import awais.instagrabber.databinding.ItemNotificationBinding;
|
||||||
|
import awais.instagrabber.models.FeedStoryModel;
|
||||||
|
import awais.instagrabber.utils.Constants;
|
||||||
|
import awais.instagrabber.utils.Utils;
|
||||||
|
|
||||||
|
public final class FeedStoriesListAdapter extends ListAdapter<FeedStoryModel, StoryListViewHolder> {
|
||||||
|
private final OnFeedStoryClickListener listener;
|
||||||
|
|
||||||
|
private static final DiffUtil.ItemCallback<FeedStoryModel> diffCallback = new DiffUtil.ItemCallback<FeedStoryModel>() {
|
||||||
|
@Override
|
||||||
|
public boolean areItemsTheSame(@NonNull final FeedStoryModel oldItem, @NonNull final FeedStoryModel newItem) {
|
||||||
|
return oldItem.getStoryMediaId().equals(newItem.getStoryMediaId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean areContentsTheSame(@NonNull final FeedStoryModel oldItem, @NonNull final FeedStoryModel newItem) {
|
||||||
|
return oldItem.getStoryMediaId().equals(newItem.getStoryMediaId()) && oldItem.isFullyRead().equals(newItem.isFullyRead());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public FeedStoriesListAdapter(final OnFeedStoryClickListener listener) {
|
||||||
|
super(diffCallback);
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public StoryListViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
|
||||||
|
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
|
||||||
|
final ItemNotificationBinding binding = ItemNotificationBinding.inflate(layoutInflater, parent, false);
|
||||||
|
return new StoryListViewHolder(binding);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull final StoryListViewHolder holder, final int position) {
|
||||||
|
final FeedStoryModel model = getItem(position);
|
||||||
|
holder.bind(model, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void submitList(@Nullable final List<FeedStoryModel> list, @Nullable final Runnable commitCallback) {
|
||||||
|
if (list == null) {
|
||||||
|
super.submitList(null, commitCallback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.submitList(sort(list), commitCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void submitList(@Nullable final List<FeedStoryModel> list) {
|
||||||
|
if (list == null) {
|
||||||
|
super.submitList(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.submitList(sort(list));
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<FeedStoryModel> sort(final List<FeedStoryModel> list) {
|
||||||
|
final List<FeedStoryModel> listCopy = new ArrayList<>(list);
|
||||||
|
Collections.sort(listCopy, (o1, o2) -> {
|
||||||
|
int result;
|
||||||
|
switch (Utils.settingsHelper.getString(Constants.STORY_SORT)) {
|
||||||
|
case "1":
|
||||||
|
result = o1.getTimestamp() > o2.getTimestamp() ? -1 : (o1.getTimestamp() == o2.getTimestamp() ? 0 : 1);
|
||||||
|
break;
|
||||||
|
case "2":
|
||||||
|
result = o1.getTimestamp() > o2.getTimestamp() ? 1 : (o1.getTimestamp() == o2.getTimestamp() ? 0 : -1);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
result = 0;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
return listCopy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnFeedStoryClickListener {
|
||||||
|
void onFeedStoryClick(final FeedStoryModel model);
|
||||||
|
|
||||||
|
void onProfileClick(final String username);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
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 awais.instagrabber.adapters.viewholder.StoryListViewHolder;
|
||||||
|
import awais.instagrabber.databinding.ItemNotificationBinding;
|
||||||
|
import awais.instagrabber.models.HighlightModel;
|
||||||
|
|
||||||
|
public final class HighlightStoriesListAdapter extends ListAdapter<HighlightModel, StoryListViewHolder> {
|
||||||
|
private final OnHighlightStoryClickListener listener;
|
||||||
|
|
||||||
|
private static final DiffUtil.ItemCallback<HighlightModel> diffCallback = new DiffUtil.ItemCallback<HighlightModel>() {
|
||||||
|
@Override
|
||||||
|
public boolean areItemsTheSame(@NonNull final HighlightModel oldItem, @NonNull final HighlightModel newItem) {
|
||||||
|
return oldItem.getId().equals(newItem.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean areContentsTheSame(@NonNull final HighlightModel oldItem, @NonNull final HighlightModel newItem) {
|
||||||
|
return oldItem.getId().equals(newItem.getId());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public HighlightStoriesListAdapter(final OnHighlightStoryClickListener listener) {
|
||||||
|
super(diffCallback);
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public StoryListViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
|
||||||
|
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
|
||||||
|
final ItemNotificationBinding binding = ItemNotificationBinding.inflate(layoutInflater, parent, false);
|
||||||
|
return new StoryListViewHolder(binding);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull final StoryListViewHolder holder, final int position) {
|
||||||
|
final HighlightModel model = getItem(position);
|
||||||
|
holder.bind(model, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnHighlightStoryClickListener {
|
||||||
|
void onHighlightClick(HighlightModel model);
|
||||||
|
|
||||||
|
void onProfileClick(String username);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
package awais.instagrabber.adapters.viewholder;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import awais.instagrabber.R;
|
||||||
|
import awais.instagrabber.adapters.FeedStoriesListAdapter.OnFeedStoryClickListener;
|
||||||
|
import awais.instagrabber.adapters.HighlightStoriesListAdapter.OnHighlightStoryClickListener;
|
||||||
|
import awais.instagrabber.databinding.ItemNotificationBinding;
|
||||||
|
import awais.instagrabber.models.FeedStoryModel;
|
||||||
|
import awais.instagrabber.models.HighlightModel;
|
||||||
|
|
||||||
|
public final class StoryListViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
private final ItemNotificationBinding binding;
|
||||||
|
|
||||||
|
public StoryListViewHolder(final ItemNotificationBinding binding) {
|
||||||
|
super(binding.getRoot());
|
||||||
|
this.binding = binding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bind(final FeedStoryModel model,
|
||||||
|
final OnFeedStoryClickListener notificationClickListener) {
|
||||||
|
if (model == null) return;
|
||||||
|
binding.tvComment.setVisibility(View.GONE);
|
||||||
|
binding.tvSubComment.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
binding.tvDate.setText(model.getDateTime());
|
||||||
|
|
||||||
|
binding.tvUsername.setText(model.getProfileModel().getUsername());
|
||||||
|
binding.ivProfilePic.setImageURI(model.getProfileModel().getSdProfilePic());
|
||||||
|
binding.ivProfilePic.setOnClickListener(v -> {
|
||||||
|
if (notificationClickListener == null) return;
|
||||||
|
notificationClickListener.onProfileClick(model.getProfileModel().getUsername());
|
||||||
|
});
|
||||||
|
|
||||||
|
binding.ivPreviewPic.setVisibility(View.VISIBLE);
|
||||||
|
binding.ivPreviewPic.setImageURI(model.getFirstStoryModel().getThumbnail());
|
||||||
|
binding.ivPreviewPic.setOnClickListener(v -> {
|
||||||
|
if (notificationClickListener == null) return;
|
||||||
|
notificationClickListener.onFeedStoryClick(model);
|
||||||
|
});
|
||||||
|
|
||||||
|
itemView.setOnClickListener(v -> {
|
||||||
|
if (notificationClickListener == null) return;
|
||||||
|
notificationClickListener.onFeedStoryClick(model);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bind(final HighlightModel model,
|
||||||
|
final OnHighlightStoryClickListener notificationClickListener) {
|
||||||
|
if (model == null) return;
|
||||||
|
binding.tvComment.setVisibility(View.GONE);
|
||||||
|
binding.tvSubComment.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
binding.tvUsername.setText(model.getDateTime());
|
||||||
|
|
||||||
|
binding.ivProfilePic.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
binding.ivPreviewPic.setVisibility(View.VISIBLE);
|
||||||
|
binding.ivPreviewPic.setImageURI(model.getThumbnailUrl());
|
||||||
|
|
||||||
|
itemView.setOnClickListener(v -> {
|
||||||
|
if (notificationClickListener == null) return;
|
||||||
|
notificationClickListener.onHighlightClick(model);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,108 @@
|
|||||||
|
package awais.instagrabber.fragments;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
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.NavDirections;
|
||||||
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
|
|
||||||
|
import awais.instagrabber.adapters.FeedStoriesListAdapter;
|
||||||
|
import awais.instagrabber.adapters.FeedStoriesListAdapter.OnFeedStoryClickListener;
|
||||||
|
import awais.instagrabber.databinding.FragmentStoryListViewerBinding;
|
||||||
|
import awais.instagrabber.fragments.settings.MorePreferencesFragmentDirections;
|
||||||
|
import awais.instagrabber.models.FeedStoryModel;
|
||||||
|
import awais.instagrabber.utils.Constants;
|
||||||
|
import awais.instagrabber.viewmodels.StoriesViewModel;
|
||||||
|
import awais.instagrabber.webservices.StoriesService;
|
||||||
|
|
||||||
|
import static awais.instagrabber.utils.Utils.settingsHelper;
|
||||||
|
|
||||||
|
public final class StoryListViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
|
||||||
|
private static final String TAG = "StoryListViewerFragment";
|
||||||
|
|
||||||
|
private FragmentStoryListViewerBinding binding;
|
||||||
|
private SwipeRefreshLayout root;
|
||||||
|
private boolean shouldRefresh = true;
|
||||||
|
private StoriesViewModel storiesViewModel;
|
||||||
|
private StoriesService storiesService;
|
||||||
|
private Context context;
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
private final OnFeedStoryClickListener clickListener = new OnFeedStoryClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onFeedStoryClick(final FeedStoryModel model) {
|
||||||
|
if (model == null) return;
|
||||||
|
// final NavDirections action = StoryListNavGraphDirections.actionStoryListFragmentToStoryViewerFragment(position, null, false, false, null, null);
|
||||||
|
// NavHostFragment.findNavController(StoryListViewerFragment.this).navigate(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProfileClick(final String username) {
|
||||||
|
openProfile(username);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
context = getContext();
|
||||||
|
if (context == null) return;
|
||||||
|
storiesService = StoriesService.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 = FragmentStoryListViewerBinding.inflate(getLayoutInflater());
|
||||||
|
root = binding.getRoot();
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
|
||||||
|
if (!shouldRefresh) return;
|
||||||
|
init();
|
||||||
|
shouldRefresh = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
final Context context = getContext();
|
||||||
|
if (getArguments() == null) return;
|
||||||
|
final StoryListViewerFragmentArgs fragmentArgs = StoryListViewerFragmentArgs.fromBundle(getArguments());
|
||||||
|
type = fragmentArgs.getType();
|
||||||
|
binding.swipeRefreshLayout.setOnRefreshListener(this);
|
||||||
|
storiesViewModel = new ViewModelProvider(this).get(StoriesViewModel.class);
|
||||||
|
// final NotificationsAdapter adapter = new NotificationsAdapter(clickListener, mentionClickListener);
|
||||||
|
binding.rvStories.setLayoutManager(new LinearLayoutManager(context));
|
||||||
|
// binding.rvStories.setAdapter(adapter);
|
||||||
|
// storiesViewModel.getList().observe(getViewLifecycleOwner(), adapter::submitList);
|
||||||
|
onRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRefresh() {
|
||||||
|
binding.swipeRefreshLayout.setRefreshing(true);
|
||||||
|
|
||||||
|
binding.swipeRefreshLayout.setRefreshing(false);
|
||||||
|
// storiesViewModel.getList().postValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openProfile(final String username) {
|
||||||
|
final NavDirections action = MorePreferencesFragmentDirections
|
||||||
|
.actionGlobalProfileFragment("@" + username);
|
||||||
|
NavHostFragment.findNavController(this).navigate(action);
|
||||||
|
}
|
||||||
|
}
|
@ -279,7 +279,11 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
|
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
|
||||||
if (item.getItemId() == R.id.layout) {
|
if (item.getItemId() == R.id.storyList) {
|
||||||
|
final NavDirections action = FeedFragmentDirections.actionGlobalStoryListViewerFragment("feed");
|
||||||
|
NavHostFragment.findNavController(FeedFragment.this).navigate(action);
|
||||||
|
}
|
||||||
|
else if (item.getItemId() == R.id.layout) {
|
||||||
showPostsLayoutPreferences();
|
showPostsLayoutPreferences();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -358,7 +362,6 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
|
|||||||
new FeedStoriesAdapter.OnFeedStoryClickListener() {
|
new FeedStoriesAdapter.OnFeedStoryClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onFeedStoryClick(FeedStoryModel model, int position) {
|
public void onFeedStoryClick(FeedStoryModel model, int position) {
|
||||||
Log.d("austin_debug", "read status is "+model.isFullyRead());
|
|
||||||
final NavDirections action = FeedFragmentDirections.actionFeedFragmentToStoryViewerFragment(position, null, false, false, null, null);
|
final NavDirections action = FeedFragmentDirections.actionFeedFragmentToStoryViewerFragment(position, null, false, false, null, null);
|
||||||
NavHostFragment.findNavController(FeedFragment.this).navigate(action);
|
NavHostFragment.findNavController(FeedFragment.this).navigate(action);
|
||||||
}
|
}
|
||||||
|
@ -137,6 +137,11 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
|
|||||||
NavHostFragment.findNavController(this).navigate(R.id.action_global_notificationsViewerFragment);
|
NavHostFragment.findNavController(this).navigate(R.id.action_global_notificationsViewerFragment);
|
||||||
return true;
|
return true;
|
||||||
}));
|
}));
|
||||||
|
screen.addPreference(getPreference(R.string.action_archive, R.drawable.ic_archive, preference -> {
|
||||||
|
final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalStoryListViewerFragment("archive");
|
||||||
|
NavHostFragment.findNavController(this).navigate(navDirections);
|
||||||
|
return true;
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
screen.addPreference(getPreference(R.string.title_favorites, R.drawable.ic_star_24, preference -> {
|
screen.addPreference(getPreference(R.string.title_favorites, R.drawable.ic_star_24, preference -> {
|
||||||
final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToFavoritesFragment();
|
final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToFavoritesFragment();
|
||||||
|
@ -2,21 +2,27 @@ package awais.instagrabber.models;
|
|||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import awais.instagrabber.utils.Utils;
|
||||||
|
|
||||||
public final class FeedStoryModel implements Serializable {
|
public final class FeedStoryModel implements Serializable {
|
||||||
private final String storyMediaId;
|
private final String storyMediaId;
|
||||||
private final ProfileModel profileModel;
|
private final ProfileModel profileModel;
|
||||||
// private StoryModel[] storyModels;
|
private final StoryModel firstStoryModel;
|
||||||
private Boolean fullyRead;
|
private Boolean fullyRead;
|
||||||
private final long timestamp;
|
private final long timestamp;
|
||||||
|
|
||||||
public FeedStoryModel(final String storyMediaId, final ProfileModel profileModel,
|
public FeedStoryModel(final String storyMediaId, final ProfileModel profileModel,
|
||||||
final boolean fullyRead, final long timestamp) {
|
final boolean fullyRead, final long timestamp, final StoryModel firstStoryModel) {
|
||||||
this.storyMediaId = storyMediaId;
|
this.storyMediaId = storyMediaId;
|
||||||
this.profileModel = profileModel;
|
this.profileModel = profileModel;
|
||||||
this.fullyRead = fullyRead;
|
this.fullyRead = fullyRead;
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
|
this.firstStoryModel = firstStoryModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getStoryMediaId() {
|
public String getStoryMediaId() {
|
||||||
@ -27,17 +33,22 @@ public final class FeedStoryModel implements Serializable {
|
|||||||
return timestamp;
|
return timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getDateTime() {
|
||||||
|
return Utils.datetimeParser.format(new Date(timestamp * 1000L));
|
||||||
|
}
|
||||||
|
|
||||||
public ProfileModel getProfileModel() {
|
public ProfileModel getProfileModel() {
|
||||||
return profileModel;
|
return profileModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
// public void setStoryModels(final StoryModel[] storyModels) {
|
// public void setFirstStoryModel(final StoryModel firstStoryModel) {
|
||||||
// this.storyModels = storyModels;
|
// this.firstStoryModel = firstStoryModel;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// public StoryModel[] getStoryModels() {
|
public StoryModel getFirstStoryModel() {
|
||||||
// return storyModels;
|
return firstStoryModel;
|
||||||
// }
|
}
|
||||||
|
|
||||||
public Boolean isFullyRead() {
|
public Boolean isFullyRead() {
|
||||||
return fullyRead;
|
return fullyRead;
|
||||||
|
@ -1,16 +1,25 @@
|
|||||||
package awais.instagrabber.models;
|
package awais.instagrabber.models;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import awais.instagrabber.utils.Utils;
|
||||||
|
|
||||||
public final class HighlightModel {
|
public final class HighlightModel {
|
||||||
private final String title;
|
private final String title;
|
||||||
private final String id;
|
private final String id;
|
||||||
private final String thumbnailUrl;
|
private final String thumbnailUrl;
|
||||||
|
private final long timestamp;
|
||||||
|
|
||||||
public HighlightModel(final String title,
|
public HighlightModel(final String title,
|
||||||
final String id,
|
final String id,
|
||||||
final String thumbnailUrl) {
|
final String thumbnailUrl,
|
||||||
|
final long timestamp) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.thumbnailUrl = thumbnailUrl;
|
this.thumbnailUrl = thumbnailUrl;
|
||||||
|
this.timestamp = timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTitle() {
|
public String getTitle() {
|
||||||
@ -24,4 +33,13 @@ public final class HighlightModel {
|
|||||||
public String getThumbnailUrl() {
|
public String getThumbnailUrl() {
|
||||||
return thumbnailUrl;
|
return thumbnailUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getDateTime() {
|
||||||
|
return Utils.datetimeParser.format(new Date(timestamp * 1000L));
|
||||||
|
}
|
||||||
}
|
}
|
@ -11,7 +11,7 @@ import awais.instagrabber.models.stickers.SwipeUpModel;
|
|||||||
|
|
||||||
public final class StoryModel implements Serializable {
|
public final class StoryModel implements Serializable {
|
||||||
private final String storyMediaId;
|
private final String storyMediaId;
|
||||||
private final String storyUrl;
|
private final String storyUrl, thumbnail;
|
||||||
private final String username;
|
private final String username;
|
||||||
private final String userId;
|
private final String userId;
|
||||||
private final MediaItemType itemType;
|
private final MediaItemType itemType;
|
||||||
@ -30,10 +30,11 @@ public final class StoryModel implements Serializable {
|
|||||||
private boolean isCurrentSlide = false;
|
private boolean isCurrentSlide = false;
|
||||||
private final boolean canReply;
|
private final boolean canReply;
|
||||||
|
|
||||||
public StoryModel(final String storyMediaId, final String storyUrl, final MediaItemType itemType,
|
public StoryModel(final String storyMediaId, final String storyUrl, final String thumbnail, final MediaItemType itemType,
|
||||||
final long timestamp, final String username, final String userId, final boolean canReply) {
|
final long timestamp, final String username, final String userId, final boolean canReply) {
|
||||||
this.storyMediaId = storyMediaId;
|
this.storyMediaId = storyMediaId;
|
||||||
this.storyUrl = storyUrl;
|
this.storyUrl = storyUrl;
|
||||||
|
this.thumbnail = thumbnail;
|
||||||
this.itemType = itemType;
|
this.itemType = itemType;
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
this.username = username;
|
this.username = username;
|
||||||
@ -45,6 +46,10 @@ public final class StoryModel implements Serializable {
|
|||||||
return storyUrl;
|
return storyUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getThumbnail() {
|
||||||
|
return thumbnail;
|
||||||
|
}
|
||||||
|
|
||||||
public String getStoryMediaId() {
|
public String getStoryMediaId() {
|
||||||
return storyMediaId;
|
return storyMediaId;
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,9 @@ public interface StoriesRepository {
|
|||||||
@GET("/api/v1/highlights/{uid}/highlights_tray/")
|
@GET("/api/v1/highlights/{uid}/highlights_tray/")
|
||||||
Call<String> fetchHighlights(@Path("uid") final String uid);
|
Call<String> fetchHighlights(@Path("uid") final String uid);
|
||||||
|
|
||||||
|
@GET("/api/v1/archive/reel/day_shells/")
|
||||||
|
Call<String> fetchArchive(@QueryMap Map<String, String> queryParams);
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
Call<String> getUserStory(@Header("User-Agent") String userAgent, @Url String url);
|
Call<String> getUserStory(@Header("User-Agent") String userAgent, @Url String url);
|
||||||
|
|
||||||
|
@ -890,6 +890,8 @@ public final class ResponseBodyUtils {
|
|||||||
final StoryModel model = new StoryModel(data.getString("id"),
|
final StoryModel model = new StoryModel(data.getString("id"),
|
||||||
data.getJSONObject("image_versions2").getJSONArray("candidates").getJSONObject(0)
|
data.getJSONObject("image_versions2").getJSONArray("candidates").getJSONObject(0)
|
||||||
.getString("url"),
|
.getString("url"),
|
||||||
|
data.getJSONObject("image_versions2").getJSONArray("candidates").getJSONObject(1)
|
||||||
|
.getString("url"),
|
||||||
isVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE,
|
isVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE,
|
||||||
data.optLong("taken_at", 0),
|
data.optLong("taken_at", 0),
|
||||||
(isLoc || isHashtag)
|
(isLoc || isHashtag)
|
||||||
|
@ -12,6 +12,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.TimeZone;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import awais.instagrabber.models.FeedStoryModel;
|
import awais.instagrabber.models.FeedStoryModel;
|
||||||
@ -124,7 +125,9 @@ public class StoriesService extends BaseService {
|
|||||||
final String id = node.getString("id");
|
final String id = node.getString("id");
|
||||||
final long timestamp = node.getLong("latest_reel_media");
|
final long timestamp = node.getLong("latest_reel_media");
|
||||||
final boolean fullyRead = !node.isNull("seen") && node.getLong("seen") == timestamp;
|
final boolean fullyRead = !node.isNull("seen") && node.getLong("seen") == timestamp;
|
||||||
feedStoryModels.add(new FeedStoryModel(id, profileModel, fullyRead, timestamp));
|
final JSONObject itemJson = node.getJSONArray("items").getJSONObject(0);
|
||||||
|
final StoryModel firstStoryModel = ResponseBodyUtils.parseStoryItem(itemJson, false, false, null);
|
||||||
|
feedStoryModels.add(new FeedStoryModel(id, profileModel, fullyRead, timestamp, firstStoryModel));
|
||||||
}
|
}
|
||||||
callback.onSuccess(feedStoryModels);
|
callback.onSuccess(feedStoryModels);
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
@ -179,6 +182,62 @@ public class StoriesService extends BaseService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void fetchArchive(final String maxId,
|
||||||
|
final ServiceCallback<ArchiveFetchResponse> callback) {
|
||||||
|
final Map<String, String> form = new HashMap<>();
|
||||||
|
form.put("include_suggested_highlights", "false");
|
||||||
|
form.put("is_in_archive_home", "true");
|
||||||
|
form.put("include_cover", "1");
|
||||||
|
form.put("timezone_offset", String.valueOf(TimeZone.getDefault().getRawOffset() / 1000));
|
||||||
|
if (!TextUtils.isEmpty(maxId)) {
|
||||||
|
form.put("max_id", maxId); // NOT TESTED
|
||||||
|
}
|
||||||
|
final Call<String> request = repository.fetchArchive(form);
|
||||||
|
request.enqueue(new Callback<String>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
|
||||||
|
try {
|
||||||
|
if (callback == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final String body = response.body();
|
||||||
|
if (TextUtils.isEmpty(body)) {
|
||||||
|
callback.onSuccess(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final JSONObject data = new JSONObject(body);
|
||||||
|
final JSONArray highlightsReel = data.getJSONArray("items");
|
||||||
|
|
||||||
|
final int length = highlightsReel.length();
|
||||||
|
final List<HighlightModel> highlightModels = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < length; ++i) {
|
||||||
|
final JSONObject highlightNode = highlightsReel.getJSONObject(i);
|
||||||
|
highlightModels.add(new HighlightModel(
|
||||||
|
null,
|
||||||
|
highlightNode.getString(Constants.EXTRAS_ID),
|
||||||
|
highlightNode.getJSONObject("cover_image_version").getString("url"),
|
||||||
|
highlightNode.getLong("timestamp")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
callback.onSuccess(new ArchiveFetchResponse(highlightModels,
|
||||||
|
data.getBoolean("more_available"),
|
||||||
|
data.getString("max_id")));
|
||||||
|
} catch (JSONException e) {
|
||||||
|
Log.e(TAG, "onResponse", e);
|
||||||
|
callback.onFailure(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onFailure(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public void getUserStory(final String id,
|
public void getUserStory(final String id,
|
||||||
final String username,
|
final String username,
|
||||||
final boolean isLoc,
|
final boolean isLoc,
|
||||||
@ -335,11 +394,11 @@ public class StoriesService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class ArchiveFetchResponse {
|
public class ArchiveFetchResponse {
|
||||||
private List<HighlightModel> archives;
|
private final List<HighlightModel> archives;
|
||||||
private final boolean hasNextPage;
|
private final boolean hasNextPage;
|
||||||
private final String nextCursor;
|
private final String nextCursor;
|
||||||
|
|
||||||
public ArchiveFetchResponse(final List<HighlightModel> highlightModels, final boolean hasNextPage, final String nextCursor) {
|
public ArchiveFetchResponse(final List<HighlightModel> archives, final boolean hasNextPage, final String nextCursor) {
|
||||||
this.archives = archives;
|
this.archives = archives;
|
||||||
this.hasNextPage = hasNextPage;
|
this.hasNextPage = hasNextPage;
|
||||||
this.nextCursor = nextCursor;
|
this.nextCursor = nextCursor;
|
||||||
|
10
app/src/main/res/drawable/ic_archive.xml
Normal file
10
app/src/main/res/drawable/ic_archive.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M20.54,5.23l-1.39,-1.68C18.88,3.21 18.47,3 18,3H6c-0.47,0 -0.88,0.21 -1.16,0.55L3.46,5.23C3.17,5.57 3,6.02 3,6.5V19c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V6.5c0,-0.48 -0.17,-0.93 -0.46,-1.27zM12,17.5L6.5,12H10v-2h4v2h3.5L12,17.5zM5.12,5l0.81,-1h12l0.94,1H5.12z"/>
|
||||||
|
</vector>
|
10
app/src/main/res/drawable/ic_story_list.xml
Normal file
10
app/src/main/res/drawable/ic_story_list.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M13.05,9.79L10,7.5v9l3.05,-2.29L16,12zM13.05,9.79L10,7.5v9l3.05,-2.29L16,12zM13.05,9.79L10,7.5v9l3.05,-2.29L16,12zM11,4.07L11,2.05c-2.01,0.2 -3.84,1 -5.32,2.21L7.1,5.69c1.11,-0.86 2.44,-1.44 3.9,-1.62zM5.69,7.1L4.26,5.68C3.05,7.16 2.25,8.99 2.05,11h2.02c0.18,-1.46 0.76,-2.79 1.62,-3.9zM4.07,13L2.05,13c0.2,2.01 1,3.84 2.21,5.32l1.43,-1.43c-0.86,-1.1 -1.44,-2.43 -1.62,-3.89zM5.68,19.74C7.16,20.95 9,21.75 11,21.95v-2.02c-1.46,-0.18 -2.79,-0.76 -3.9,-1.62l-1.42,1.43zM22,12c0,5.16 -3.92,9.42 -8.95,9.95v-2.02C16.97,19.41 20,16.05 20,12s-3.03,-7.41 -6.95,-7.93L13.05,2.05C18.08,2.58 22,6.84 22,12z"/>
|
||||||
|
</vector>
|
15
app/src/main/res/layout/fragment_story_list_viewer.xml
Normal file
15
app/src/main/res/layout/fragment_story_list_viewer.xml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/swipeRefreshLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".fragments.StoryListViewerFragment">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rvStories"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
tools:listitem="@layout/item_notification" />
|
||||||
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
@ -1,6 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item
|
||||||
|
android:id="@+id/storyList"
|
||||||
|
android:icon="@drawable/ic_story_list"
|
||||||
|
android:title="@string/feed_stories"
|
||||||
|
app:showAsAction="always" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/layout"
|
android:id="@+id/layout"
|
||||||
android:title="@string/layout"
|
android:title="@string/layout"
|
||||||
|
@ -78,6 +78,17 @@
|
|||||||
android:id="@+id/action_global_notificationsViewerFragment"
|
android:id="@+id/action_global_notificationsViewerFragment"
|
||||||
app:destination="@id/notification_viewer_nav_graph" />
|
app:destination="@id/notification_viewer_nav_graph" />
|
||||||
|
|
||||||
|
<include app:graph="@navigation/story_list_nav_graph" />
|
||||||
|
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_global_storyListViewerFragment"
|
||||||
|
app:destination="@id/story_list_nav_graph">
|
||||||
|
<argument
|
||||||
|
android:name="type"
|
||||||
|
app:argType="string"
|
||||||
|
app:nullable="false" />
|
||||||
|
</action>
|
||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/feedFragment"
|
android:id="@+id/feedFragment"
|
||||||
android:name="awais.instagrabber.fragments.main.FeedFragment"
|
android:name="awais.instagrabber.fragments.main.FeedFragment"
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
<include app:graph="@navigation/comments_nav_graph" />
|
<include app:graph="@navigation/comments_nav_graph" />
|
||||||
<include app:graph="@navigation/likes_nav_graph" />
|
<include app:graph="@navigation/likes_nav_graph" />
|
||||||
<include app:graph="@navigation/notification_viewer_nav_graph" />
|
<include app:graph="@navigation/notification_viewer_nav_graph" />
|
||||||
|
<include app:graph="@navigation/story_list_nav_graph" />
|
||||||
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_global_profileFragment"
|
android:id="@+id/action_global_profileFragment"
|
||||||
@ -38,6 +39,15 @@
|
|||||||
app:nullable="false" />
|
app:nullable="false" />
|
||||||
</action>
|
</action>
|
||||||
|
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_global_storyListViewerFragment"
|
||||||
|
app:destination="@id/story_list_nav_graph">
|
||||||
|
<argument
|
||||||
|
android:name="type"
|
||||||
|
app:argType="string"
|
||||||
|
app:nullable="false" />
|
||||||
|
</action>
|
||||||
|
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_global_notificationsViewerFragment"
|
android:id="@+id/action_global_notificationsViewerFragment"
|
||||||
app:destination="@id/notification_viewer_nav_graph" />
|
app:destination="@id/notification_viewer_nav_graph" />
|
||||||
|
68
app/src/main/res/navigation/story_list_nav_graph.xml
Normal file
68
app/src/main/res/navigation/story_list_nav_graph.xml
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<?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"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/story_list_nav_graph"
|
||||||
|
app:startDestination="@id/storyListViewerFragment">
|
||||||
|
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_global_profileFragment"
|
||||||
|
app:destination="@id/profile_nav_graph">
|
||||||
|
<argument
|
||||||
|
android:name="username"
|
||||||
|
app:argType="string"
|
||||||
|
app:nullable="true" />
|
||||||
|
</action>
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/storyListViewerFragment"
|
||||||
|
android:name="awais.instagrabber.fragments.StoryListViewerFragment"
|
||||||
|
android:label="Stories"
|
||||||
|
tools:layout="@layout/fragment_story_list_viewer">
|
||||||
|
<argument
|
||||||
|
android:name="type"
|
||||||
|
app:argType="string"
|
||||||
|
app:nullable="false" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_storyListFragment_to_storyViewerFragment"
|
||||||
|
app:destination="@id/storyViewerFragment" />
|
||||||
|
</fragment>
|
||||||
|
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_global_storyListViewerFragment"
|
||||||
|
app:destination="@id/storyListViewerFragment">
|
||||||
|
<argument
|
||||||
|
android:name="type"
|
||||||
|
app:argType="string"
|
||||||
|
app:nullable="false" />
|
||||||
|
</action>
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/storyViewerFragment"
|
||||||
|
android:name="awais.instagrabber.fragments.StoryViewerFragment"
|
||||||
|
android:label="StoryViewerFragment"
|
||||||
|
tools:layout="@layout/fragment_story_viewer">
|
||||||
|
<argument
|
||||||
|
android:name="feedStoryIndex"
|
||||||
|
app:argType="integer"
|
||||||
|
app:nullable="false" />
|
||||||
|
<argument
|
||||||
|
android:name="highlight"
|
||||||
|
app:argType="string"
|
||||||
|
app:nullable="true" />
|
||||||
|
<argument
|
||||||
|
android:name="isHashtag"
|
||||||
|
app:argType="boolean" />
|
||||||
|
<argument
|
||||||
|
android:name="isLoc"
|
||||||
|
app:argType="boolean" />
|
||||||
|
<argument
|
||||||
|
android:name="profileId"
|
||||||
|
app:argType="string"
|
||||||
|
app:nullable="true" />
|
||||||
|
<argument
|
||||||
|
android:name="username"
|
||||||
|
app:argType="string"
|
||||||
|
app:nullable="true" />
|
||||||
|
</fragment>
|
||||||
|
</navigation>
|
@ -231,6 +231,7 @@
|
|||||||
<string name="crash_title">App crashed</string>
|
<string name="crash_title">App crashed</string>
|
||||||
<string name="crash_descr">Oops.. the app crashed, but don\'t worry you can send error report to the developer to help him fix the issue. (:</string>
|
<string name="crash_descr">Oops.. the app crashed, but don\'t worry you can send error report to the developer to help him fix the issue. (:</string>
|
||||||
<string name="action_notif">Activity</string>
|
<string name="action_notif">Activity</string>
|
||||||
|
<string name="action_archive">Story archive</string>
|
||||||
<string name="license" translatable="false">Copyright (C) 2019 AWAiS\nCopyright (C) 2020 Austin Huang, Ammar Githam\nThis program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. See https://www.gnu.org/licenses/.</string>
|
<string name="license" translatable="false">Copyright (C) 2019 AWAiS\nCopyright (C) 2020 Austin Huang, Ammar Githam\nThis program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. See https://www.gnu.org/licenses/.</string>
|
||||||
<string name="liability" translatable="false">This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.</string>
|
<string name="liability" translatable="false">This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.</string>
|
||||||
<string name="select_picture">Select Picture</string>
|
<string name="select_picture">Select Picture</string>
|
||||||
@ -321,6 +322,7 @@
|
|||||||
<string name="delete">Delete</string>
|
<string name="delete">Delete</string>
|
||||||
<string name="comment">Comment</string>
|
<string name="comment">Comment</string>
|
||||||
<string name="layout">Layout</string>
|
<string name="layout">Layout</string>
|
||||||
|
<string name="feed_stories">Feed stories</string>
|
||||||
<string name="opening_post">Opening post...</string>
|
<string name="opening_post">Opening post...</string>
|
||||||
<string name="share">Share</string>
|
<string name="share">Share</string>
|
||||||
<string name="layout_style">Layout style</string>
|
<string name="layout_style">Layout style</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user