From 06f679608c9cdd929fef94cf780727d93704dbed Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Mon, 5 Jul 2021 14:16:29 -0400 Subject: [PATCH 01/14] fix download extension parsing --- app/src/main/java/awais/instagrabber/utils/DownloadUtils.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.kt b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.kt index 14410239..d3021e32 100644 --- a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.kt +++ b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.kt @@ -209,9 +209,7 @@ object DownloadUtils { val extension = getFileExtensionFromUrl(displayUrl) val usernamePrepend = if (isEmpty(username)) "" else username + "_" val fileName = usernamePrepend + postId + sliderPostfix + extension - val mimeType = Utils.mimeTypeMap.getMimeTypeFromExtension( - if (extension.startsWith(".")) extension.substring(1) else extension - ) + val mimeType = Utils.mimeTypeMap.getMimeTypeFromExtension(extension) return Pair(fileName, mimeType!!) } @@ -269,7 +267,7 @@ object DownloadUtils { ) { val dotPos = filename.lastIndexOf('.') if (0 <= dotPos) { - return filename.substring(dotPos) + return filename.substring(dotPos + 1) } } } From 1bba8dc0657b45c11bc969832d92390fec6ce253 Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Mon, 5 Jul 2021 16:31:47 -0400 Subject: [PATCH 02/14] share story via dm (backend) --- .../managers/DirectMessagesManager.kt | 53 +++++++++++++++++-- .../StoryReplyBroadcastOptions.kt | 2 +- .../webservices/DirectMessagesRepository.kt | 11 ++++ 3 files changed, 61 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/managers/DirectMessagesManager.kt b/app/src/main/java/awais/instagrabber/managers/DirectMessagesManager.kt index 34df42d4..69d2afa9 100644 --- a/app/src/main/java/awais/instagrabber/managers/DirectMessagesManager.kt +++ b/app/src/main/java/awais/instagrabber/managers/DirectMessagesManager.kt @@ -102,8 +102,8 @@ object DirectMessagesManager { data.postValue(loading(null)) scope.launch(Dispatchers.IO) { try { - if (itemType == BroadcastItemType.MEDIA_SHARE) - directMessagesRepository.broadcastMediaShare( + when (itemType) { + BroadcastItemType.MEDIA_SHARE -> directMessagesRepository.broadcastMediaShare( csrfToken, viewerId, deviceUuid, @@ -112,8 +112,7 @@ object DirectMessagesManager { mediaId, secondId ) - if (itemType == BroadcastItemType.PROFILE) - directMessagesRepository.broadcastProfile( + BroadcastItemType.PROFILE -> directMessagesRepository.broadcastProfile( csrfToken, viewerId, deviceUuid, @@ -121,6 +120,16 @@ object DirectMessagesManager { ThreadIdsOrUserIds(threadIds, userIds), mediaId ) + BroadcastItemType.STORY -> directMessagesRepository.broadcastStory( + csrfToken, + viewerId, + deviceUuid, + UUID.randomUUID().toString(), + ThreadIdsOrUserIds(threadIds, userIds), + mediaId, + secondId!! + ) + } data.postValue(success(Any())) callback?.invoke() } catch (e: Exception) { @@ -132,6 +141,42 @@ object DirectMessagesManager { return data } + fun replyToStory( + recipientId: Long?, + reelId: String?, + mediaId: String?, + text: String, + scope: CoroutineScope + ): LiveData> { + Log.d("austin_debug", "replying") + val data = MutableLiveData>() + data.postValue(loading(null)) + if (recipientId == null || reelId == null || mediaId == null) { + data.postValue(error("arguments are null", null)) + return data + } + scope.launch(Dispatchers.IO) { + try { + directMessagesRepository.broadcastStoryReply( + csrfToken, + viewerId, + deviceUuid, + ThreadIdsOrUserIds.Companion.ofOneUser(recipientId.toString(10)), + text, + mediaId, + reelId + ) + inboxManager.refresh(scope) + data.postValue(success(null)) + } + catch (e: Exception) { + Log.e(TAG, "story reply: ", e) + data.postValue(error(e.message, null)) + } + } + return data + } + init { val cookie = Utils.settingsHelper.getString(Constants.COOKIE) viewerId = getUserIdFromCookie(cookie) diff --git a/app/src/main/java/awais/instagrabber/repositories/requests/directmessages/StoryReplyBroadcastOptions.kt b/app/src/main/java/awais/instagrabber/repositories/requests/directmessages/StoryReplyBroadcastOptions.kt index 48dec941..5de0991c 100644 --- a/app/src/main/java/awais/instagrabber/repositories/requests/directmessages/StoryReplyBroadcastOptions.kt +++ b/app/src/main/java/awais/instagrabber/repositories/requests/directmessages/StoryReplyBroadcastOptions.kt @@ -7,7 +7,7 @@ class StoryReplyBroadcastOptions( threadIdsOrUserIds: ThreadIdsOrUserIds, val text: String, val mediaId: String, - val reelId: String // or user id, usually same + val reelId: String ) : BroadcastOptions(clientContext, threadIdsOrUserIds, BroadcastItemType.REELSHARE) { override val formMap: Map get() = mapOf( diff --git a/app/src/main/java/awais/instagrabber/webservices/DirectMessagesRepository.kt b/app/src/main/java/awais/instagrabber/webservices/DirectMessagesRepository.kt index f21d35da..b3a12280 100644 --- a/app/src/main/java/awais/instagrabber/webservices/DirectMessagesRepository.kt +++ b/app/src/main/java/awais/instagrabber/webservices/DirectMessagesRepository.kt @@ -187,6 +187,17 @@ open class DirectMessagesRepository(private val service: DirectMessagesService) ): DirectThreadBroadcastResponse = broadcast(csrfToken, userId, deviceUuid, ProfileBroadcastOptions(clientContext, threadIdsOrUserIds, profileId)) + suspend fun broadcastStory( + csrfToken: String, + userId: Long, + deviceUuid: String, + clientContext: String, + threadIdsOrUserIds: ThreadIdsOrUserIds, + mediaId: String, + reelId: String, + ): DirectThreadBroadcastResponse = + broadcast(csrfToken, userId, deviceUuid, StoryBroadcastOptions(clientContext, threadIdsOrUserIds, mediaId, reelId)) + private suspend fun broadcast( csrfToken: String, userId: Long, From 31ea42d105ecdfc8410dbb09a5a6015faf9d2765 Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Mon, 5 Jul 2021 16:33:25 -0400 Subject: [PATCH 03/14] fix sticker properties --- .../responses/stories/QuestionSticker.kt | 2 +- .../repositories/responses/stories/QuizSticker.kt | 4 ++-- .../repositories/responses/stories/SliderSticker.kt | 6 +++--- .../responses/stories/StoryAppAttribution.kt | 13 +++++++++++-- .../repositories/responses/stories/Tally.kt | 4 ++-- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/stories/QuestionSticker.kt b/app/src/main/java/awais/instagrabber/repositories/responses/stories/QuestionSticker.kt index 7dfec43c..87801293 100644 --- a/app/src/main/java/awais/instagrabber/repositories/responses/stories/QuestionSticker.kt +++ b/app/src/main/java/awais/instagrabber/repositories/responses/stories/QuestionSticker.kt @@ -6,7 +6,7 @@ import awais.instagrabber.repositories.responses.Location import awais.instagrabber.repositories.responses.User data class QuestionSticker( - val questionType: String?, + val questionType: String, val questionId: Long, val question: String ) : Serializable \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/stories/QuizSticker.kt b/app/src/main/java/awais/instagrabber/repositories/responses/stories/QuizSticker.kt index 45f237f2..4158490f 100644 --- a/app/src/main/java/awais/instagrabber/repositories/responses/stories/QuizSticker.kt +++ b/app/src/main/java/awais/instagrabber/repositories/responses/stories/QuizSticker.kt @@ -6,8 +6,8 @@ import awais.instagrabber.repositories.responses.Location import awais.instagrabber.repositories.responses.User data class QuizSticker( - val quizId: Long?, - val question: String?, + val quizId: Long, + val question: String, val tallies: List, var viewerAnswer: Int?, val correctAnswer: Int diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/stories/SliderSticker.kt b/app/src/main/java/awais/instagrabber/repositories/responses/stories/SliderSticker.kt index 45356d85..ca6a536b 100644 --- a/app/src/main/java/awais/instagrabber/repositories/responses/stories/SliderSticker.kt +++ b/app/src/main/java/awais/instagrabber/repositories/responses/stories/SliderSticker.kt @@ -6,11 +6,11 @@ import awais.instagrabber.repositories.responses.Location import awais.instagrabber.repositories.responses.User data class SliderSticker( - val sliderId: Long?, - val question: String?, + val sliderId: Long, + val question: String, val emoji: String?, val viewerCanVote: Boolean?, - var viewerVote: Double?, + val viewerVote: Double?, val sliderVoteAverage: Double?, val sliderVoteCount: Int?, ) : Serializable \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/stories/StoryAppAttribution.kt b/app/src/main/java/awais/instagrabber/repositories/responses/stories/StoryAppAttribution.kt index 6ae27957..bf8e524d 100644 --- a/app/src/main/java/awais/instagrabber/repositories/responses/stories/StoryAppAttribution.kt +++ b/app/src/main/java/awais/instagrabber/repositories/responses/stories/StoryAppAttribution.kt @@ -1,9 +1,18 @@ package awais.instagrabber.repositories.responses.stories +import android.net.Uri import java.io.Serializable // https://github.com/austinhuang0131/barinsta/issues/1151 data class StoryAppAttribution( - val name: String?, // use name instead of app_action_text for button label + val name: String?, + val appActionText: String?, val contentUrl: String? -) : Serializable \ No newline at end of file +) : Serializable { + val url: String? + get() { + val uri = Uri.parse(contentUrl) + return if (uri.getHost().equals("open.spotify.com")) contentUrl?.split("?")?.get(0) + else contentUrl + } +} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/stories/Tally.kt b/app/src/main/java/awais/instagrabber/repositories/responses/stories/Tally.kt index 87c8c960..d71f1668 100644 --- a/app/src/main/java/awais/instagrabber/repositories/responses/stories/Tally.kt +++ b/app/src/main/java/awais/instagrabber/repositories/responses/stories/Tally.kt @@ -6,6 +6,6 @@ import awais.instagrabber.repositories.responses.Location import awais.instagrabber.repositories.responses.User data class Tally( - val text: String?, - val count: Int? + val text: String, + val count: Int ) : Serializable \ No newline at end of file From bb5244665b286bf6b43dc53e2b5d7c86af9bceba Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Mon, 5 Jul 2021 20:11:58 -0400 Subject: [PATCH 04/14] story viewmodel (wip) hiding the storylist doesn't work yet but everything else should be good --- .../instagrabber/adapters/StoriesAdapter.java | 12 + .../fragments/StoryViewerFragment.java | 1307 ----------------- .../fragments/StoryViewerFragment.kt | 902 ++++++++++++ .../fragments/main/ProfileFragment.kt | 4 +- .../models/enums/StoryPaginationType.kt | 7 + .../repositories/StoriesService.kt | 2 +- .../responses/stories/StoryMedia.kt | 4 +- .../viewmodels/ProfileFragmentViewModel.kt | 9 +- .../viewmodels/StoryFragmentViewModel.kt | 458 ++++++ .../webservices/StoriesRepository.kt | 26 +- .../main/res/drawable/ic_story_sticker.xml | 10 + .../res/drawable/ic_story_viewer_list.xml | 10 + .../main/res/layout/fragment_story_viewer.xml | 220 +-- app/src/main/res/menu/story_menu.xml | 12 - .../navigation/direct_messages_nav_graph.xml | 1 - .../main/res/navigation/feed_nav_graph.xml | 1 - .../main/res/navigation/hashtag_nav_graph.xml | 1 - .../res/navigation/location_nav_graph.xml | 1 - .../notification_viewer_nav_graph.xml | 1 - .../main/res/navigation/profile_nav_graph.xml | 1 - .../res/navigation/story_list_nav_graph.xml | 1 - app/src/main/res/values/ids.xml | 9 + app/src/main/res/values/strings.xml | 14 +- 23 files changed, 1555 insertions(+), 1458 deletions(-) delete mode 100644 app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java create mode 100644 app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.kt create mode 100644 app/src/main/java/awais/instagrabber/models/enums/StoryPaginationType.kt create mode 100644 app/src/main/java/awais/instagrabber/viewmodels/StoryFragmentViewModel.kt create mode 100644 app/src/main/res/drawable/ic_story_sticker.xml create mode 100644 app/src/main/res/drawable/ic_story_viewer_list.xml diff --git a/app/src/main/java/awais/instagrabber/adapters/StoriesAdapter.java b/app/src/main/java/awais/instagrabber/adapters/StoriesAdapter.java index 9a502f01..21a5b621 100755 --- a/app/src/main/java/awais/instagrabber/adapters/StoriesAdapter.java +++ b/app/src/main/java/awais/instagrabber/adapters/StoriesAdapter.java @@ -1,5 +1,7 @@ package awais.instagrabber.adapters; +import java.util.List; + import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -72,6 +74,16 @@ public final class StoriesAdapter extends ListAdapter list = getCurrentList(); + for (int i = 0; i < list.size(); i++) { + final StoryMedia item = list.get(i); + if (!item.isCurrentSlide() && i != newIndex) continue; + item.setCurrentSlide(i == newIndex); + notifyItemChanged(i, item); + } + } + public interface OnItemClickListener { void onItemClick(StoryMedia storyModel, int position); } diff --git a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java deleted file mode 100644 index d6400c7e..00000000 --- a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java +++ /dev/null @@ -1,1307 +0,0 @@ -package awais.instagrabber.fragments; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.drawable.Animatable; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.text.Editable; -import android.text.TextWatcher; -import android.util.Log; -import android.view.GestureDetector; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.EditText; -import android.widget.LinearLayout; -import android.widget.SeekBar; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.view.GestureDetectorCompat; -import androidx.fragment.app.Fragment; -import androidx.lifecycle.ViewModel; -import androidx.lifecycle.ViewModelProvider; -import androidx.navigation.NavController; -import androidx.navigation.NavDirections; -import androidx.navigation.fragment.NavHostFragment; -import androidx.recyclerview.widget.LinearLayoutManager; - -import com.facebook.drawee.backends.pipeline.Fresco; -import com.facebook.drawee.controller.BaseControllerListener; -import com.facebook.drawee.interfaces.DraweeController; -import com.facebook.imagepipeline.image.ImageInfo; -import com.facebook.imagepipeline.request.ImageRequest; -import com.facebook.imagepipeline.request.ImageRequestBuilder; -import com.google.android.exoplayer2.MediaItem; -import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.SimpleExoPlayer; -import com.google.android.exoplayer2.source.LoadEventInfo; -import com.google.android.exoplayer2.source.MediaLoadData; -import com.google.android.exoplayer2.source.MediaSource; -import com.google.android.exoplayer2.source.MediaSourceEventListener; -import com.google.android.exoplayer2.source.ProgressiveMediaSource; -import com.google.android.exoplayer2.source.dash.DashMediaSource; -import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; - -import java.io.IOException; -import java.text.NumberFormat; -import java.util.Arrays; -import java.util.ArrayList; -import java.util.Collections; -import java.util.stream.Collectors; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import awais.instagrabber.BuildConfig; -import awais.instagrabber.R; -import awais.instagrabber.adapters.StoriesAdapter; -import awais.instagrabber.customviews.helpers.SwipeGestureListener; -import awais.instagrabber.databinding.FragmentStoryViewerBinding; -import awais.instagrabber.fragments.main.ProfileFragmentDirections; -import awais.instagrabber.fragments.settings.PreferenceKeys; -import awais.instagrabber.interfaces.SwipeEvent; -import awais.instagrabber.models.enums.MediaItemType; -import awais.instagrabber.repositories.requests.StoryViewerOptions; -import awais.instagrabber.repositories.requests.StoryViewerOptions.Type; -import awais.instagrabber.repositories.requests.directmessages.ThreadIdsOrUserIds; -import awais.instagrabber.repositories.responses.stories.*; -import awais.instagrabber.utils.AppExecutors; -import awais.instagrabber.utils.Constants; -import awais.instagrabber.utils.CookieUtils; -import awais.instagrabber.utils.CoroutineUtilsKt; -import awais.instagrabber.utils.DownloadUtils; -import awais.instagrabber.utils.ResponseBodyUtils; -import awais.instagrabber.utils.TextUtils; -import awais.instagrabber.utils.Utils; -import awais.instagrabber.viewmodels.ArchivesViewModel; -import awais.instagrabber.viewmodels.FeedStoriesViewModel; -import awais.instagrabber.viewmodels.HighlightsViewModel; -import awais.instagrabber.viewmodels.StoriesViewModel; -import awais.instagrabber.webservices.DirectMessagesRepository; -import awais.instagrabber.webservices.MediaRepository; -import awais.instagrabber.webservices.ServiceCallback; -import awais.instagrabber.webservices.StoriesRepository; -import kotlinx.coroutines.Dispatchers; - -import static awais.instagrabber.customviews.helpers.SwipeGestureListener.SWIPE_THRESHOLD; -import static awais.instagrabber.customviews.helpers.SwipeGestureListener.SWIPE_VELOCITY_THRESHOLD; -import static awais.instagrabber.fragments.settings.PreferenceKeys.MARK_AS_SEEN; -import static awais.instagrabber.utils.Utils.settingsHelper; - -public class StoryViewerFragment extends Fragment { - private static final String TAG = "StoryViewerFragment"; - - private final String cookie = settingsHelper.getString(Constants.COOKIE); - - private AppCompatActivity fragmentActivity; - private View root; - private FragmentStoryViewerBinding binding; - private String currentStoryUsername; - private String highlightTitle; - private StoriesAdapter storiesAdapter; - private SwipeEvent swipeEvent; - private GestureDetectorCompat gestureDetector; - private StoriesRepository storiesRepository; - private MediaRepository mediaRepository; - private StoryMedia currentStory; - private Broadcast live; - private int slidePos; - private int lastSlidePos; - private String url; - private PollSticker poll; - private QuestionSticker question; - private List mentions = new ArrayList(); - private QuizSticker quiz; - private SliderSticker slider; - private MenuItem menuDownload, menuDm, menuProfile; - private SimpleExoPlayer player; - // private boolean isHashtag; - // private boolean isLoc; - // private String highlight; - private String actionBarTitle, actionBarSubtitle; - private boolean fetching = false, sticking = false, shouldRefresh = true; - private boolean downloadVisible = false, dmVisible = false, profileVisible = true; - private int currentFeedStoryIndex; - private double sliderValue; - private StoriesViewModel storiesViewModel; - private ViewModel viewModel; - // private boolean isHighlight; - // private boolean isArchive; - // private boolean isNotification; - private DirectMessagesRepository directMessagesRepository; - private StoryViewerOptions options; - private String csrfToken; - private String deviceId; - private long userId; - - @Override - public void onCreate(@Nullable final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie); - if (csrfToken == null) return; - userId = CookieUtils.getUserIdFromCookie(cookie); - deviceId = settingsHelper.getString(Constants.DEVICE_UUID); - fragmentActivity = (AppCompatActivity) requireActivity(); - storiesRepository = StoriesRepository.Companion.getInstance(); - mediaRepository = MediaRepository.Companion.getInstance(); - directMessagesRepository = DirectMessagesRepository.Companion.getInstance(); - setHasOptionsMenu(true); - } - - @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 = FragmentStoryViewerBinding.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 onCreateOptionsMenu(@NonNull final Menu menu, final MenuInflater menuInflater) { - menuInflater.inflate(R.menu.story_menu, menu); - menuDownload = menu.findItem(R.id.action_download); - menuDm = menu.findItem(R.id.action_dms); - menuProfile = menu.findItem(R.id.action_profile); - menuDownload.setVisible(downloadVisible); - menuDm.setVisible(dmVisible); - menuProfile.setVisible(profileVisible); - } - - @Override - public void onPrepareOptionsMenu(@NonNull final Menu menu) { - // hide menu items from activity - } - - @Override - public boolean onOptionsItemSelected(@NonNull final MenuItem item) { - final Context context = getContext(); - if (context == null) return false; - int itemId = item.getItemId(); - if (itemId == R.id.action_download) { - downloadStory(); - return true; - } - if (itemId == R.id.action_dms) { - final EditText input = new EditText(context); - input.setHint(R.string.reply_hint); - final AlertDialog ad = new AlertDialog.Builder(context) - .setTitle(R.string.reply_story) - .setView(input) - .setPositiveButton(R.string.confirm, (d, w) -> directMessagesRepository.broadcastStoryReply( - csrfToken, - userId, - deviceId, - ThreadIdsOrUserIds.Companion.ofOneUser(String.valueOf(currentStory.getUser().getPk())), - input.getText().toString(), - currentStory.getId(), - String.valueOf(currentStory.getUser().getPk()), - CoroutineUtilsKt.getContinuation( - (directThreadBroadcastResponse, throwable1) -> AppExecutors.INSTANCE.getMainThread().execute(() -> { - if (throwable1 != null) { - Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); - Log.e(TAG, "onFailure: ", throwable1); - return; - } - Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show(); - }), Dispatchers.getIO() - ) - )) - .setNegativeButton(R.string.cancel, null) - .show(); - ad.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); - input.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {} - - @Override - public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { - ad.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(!TextUtils.isEmpty(s)); - } - - @Override - public void afterTextChanged(final Editable s) {} - }); - return true; - } - if (itemId == R.id.action_profile) { - openProfile("@" + currentStory.getUser().getPk()); - } - return false; - } - - @Override - public void onPause() { - super.onPause(); - if (player != null) { - player.pause(); - } - } - - @Override - public void onResume() { - super.onResume(); - final ActionBar actionBar = fragmentActivity.getSupportActionBar(); - if (actionBar != null) { - actionBar.setTitle(actionBarTitle); - actionBar.setSubtitle(actionBarSubtitle); - } - setHasOptionsMenu(true); - } - - @Override - public void onDestroy() { - releasePlayer(); - // reset subtitle - final ActionBar actionBar = fragmentActivity.getSupportActionBar(); - if (actionBar != null) { - actionBar.setSubtitle(null); - } - super.onDestroy(); - } - - private void init() { - if (getArguments() == null) return; - final StoryViewerFragmentArgs fragmentArgs = StoryViewerFragmentArgs.fromBundle(getArguments()); - options = fragmentArgs.getOptions(); - currentFeedStoryIndex = options.getCurrentFeedStoryIndex(); - // highlight = fragmentArgs.getHighlight(); - // isHighlight = !TextUtils.isEmpty(highlight); - // isArchive = fragmentArgs.getIsArchive(); - // isNotification = fragmentArgs.getIsNotification(); - final Type type = options.getType(); - if (currentFeedStoryIndex >= 0) { - switch (type) { - case HIGHLIGHT: - viewModel = new ViewModelProvider(fragmentActivity).get(HighlightsViewModel.class); - break; - case STORY_ARCHIVE: - viewModel = new ViewModelProvider(fragmentActivity).get(ArchivesViewModel.class); - break; - default: - case FEED_STORY_POSITION: - viewModel = new ViewModelProvider(fragmentActivity).get(FeedStoriesViewModel.class); - break; - } - } - setupStories(); - } - - private void setupStories() { - storiesViewModel = new ViewModelProvider(this).get(StoriesViewModel.class); - setupListeners(); - final Context context = getContext(); - if (context == null) return; - binding.storiesList.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)); - storiesAdapter = new StoriesAdapter((model, position) -> { - currentStory = model; - slidePos = position; - refreshStory(); - }); - binding.storiesList.setAdapter(storiesAdapter); - storiesViewModel.getList().observe(fragmentActivity, storiesAdapter::submitList); - resetView(); - } - - @SuppressLint("ClickableViewAccessibility") - private void setupListeners() { - final boolean hasFeedStories; - List models = null; - if (currentFeedStoryIndex >= 0) { - final Type type = options.getType(); - switch (type) { - case HIGHLIGHT: - final HighlightsViewModel highlightsViewModel = (HighlightsViewModel) viewModel; - models = highlightsViewModel.getList().getValue(); - break; - case FEED_STORY_POSITION: - final FeedStoriesViewModel feedStoriesViewModel = (FeedStoriesViewModel) viewModel; - models = feedStoriesViewModel.getList().getValue(); - break; - case STORY_ARCHIVE: - final ArchivesViewModel archivesViewModel = (ArchivesViewModel) viewModel; - models = archivesViewModel.getList().getValue(); - break; - } - } - hasFeedStories = models != null && !models.isEmpty(); - final List finalModels = models; - final Context context = getContext(); - if (context == null) return; - swipeEvent = isRightSwipe -> { - final List storyModels = storiesViewModel.getList().getValue(); - final int storiesLen = storyModels == null ? 0 : storyModels.size(); - if (sticking) { - Toast.makeText(context, R.string.follower_wait_to_load, Toast.LENGTH_SHORT).show(); - return; - } - if (storiesLen <= 0) return; - final boolean isLeftSwipe = !isRightSwipe; - final boolean endOfCurrentStories = slidePos + 1 >= storiesLen; - final boolean swipingBeyondCurrentStories = (endOfCurrentStories && isLeftSwipe) || (slidePos == 0 && isRightSwipe); - if (swipingBeyondCurrentStories && hasFeedStories) { - final int index = currentFeedStoryIndex; - if ((isRightSwipe && index == 0) || (isLeftSwipe && index == finalModels.size() - 1)) { - Toast.makeText(context, R.string.no_more_stories, Toast.LENGTH_SHORT).show(); - return; - } - removeStickers(); - final Object feedStoryModel = isRightSwipe - ? finalModels.get(index - 1) - : finalModels.size() == index + 1 ? null : finalModels.get(index + 1); - paginateStories(feedStoryModel, finalModels.get(index), context, isRightSwipe, currentFeedStoryIndex == finalModels.size() - 2); - return; - } - removeStickers(); - if (isRightSwipe) { - if (--slidePos <= 0) { - slidePos = 0; - } - } else if (++slidePos >= storiesLen) { - slidePos = storiesLen - 1; - } - currentStory = storyModels.get(slidePos); - refreshStory(); - }; - gestureDetector = new GestureDetectorCompat(context, new SwipeGestureListener(swipeEvent)); - binding.playerView.setOnTouchListener((v, event) -> gestureDetector.onTouchEvent(event)); - final GestureDetector.SimpleOnGestureListener simpleOnGestureListener = new GestureDetector.SimpleOnGestureListener() { - @Override - public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX, final float velocityY) { - final float diffX = e2.getX() - e1.getX(); - try { - if (Math.abs(diffX) > Math.abs(e2.getY() - e1.getY()) && Math.abs(diffX) > SWIPE_THRESHOLD - && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) { - swipeEvent.onSwipe(diffX > 0); - return true; - } - } catch (final Exception e) { - // if (logCollector != null) - // logCollector.appendException(e, LogCollector.LogFile.ACTIVITY_STORY_VIEWER, "setupListeners", - // new Pair<>("swipeEvent", swipeEvent), - // new Pair<>("diffX", diffX)); - if (BuildConfig.DEBUG) Log.e(TAG, "Error", e); - } - return false; - } - }; - - if (hasFeedStories) { - binding.btnBackward.setVisibility(currentFeedStoryIndex == 0 ? View.INVISIBLE : View.VISIBLE); - binding.btnForward.setVisibility(currentFeedStoryIndex == finalModels.size() - 1 ? View.INVISIBLE : View.VISIBLE); - binding.btnBackward.setOnClickListener(v -> paginateStories(finalModels.get(currentFeedStoryIndex - 1), - finalModels.get(currentFeedStoryIndex), - context, true, false)); - binding.btnForward.setOnClickListener(v -> paginateStories(finalModels.get(currentFeedStoryIndex + 1), - finalModels.get(currentFeedStoryIndex), - context, false, - currentFeedStoryIndex == finalModels.size() - 2)); - } - - binding.imageViewer.setTapListener(simpleOnGestureListener); - binding.spotify.setOnClickListener(v -> { - final Object tag = v.getTag(); - if (tag instanceof CharSequence) { - Utils.openURL(context, tag.toString()); - } - }); - binding.swipeUp.setOnClickListener(v -> { - final Object tag = v.getTag(); - if (tag instanceof CharSequence) { - new AlertDialog.Builder(context) - .setTitle(R.string.swipe_up_confirmation) - .setMessage(tag.toString()).setPositiveButton(R.string.yes, (d, w) -> Utils.openURL(context, tag.toString())) - .setNegativeButton(R.string.no, (d, w) -> d.dismiss()).show(); - } - }); - binding.viewStoryPost.setOnClickListener(v -> { - final Object tag = v.getTag(); - if (!(tag instanceof CharSequence)) return; - final String mediaId = tag.toString(); - final AlertDialog alertDialog = new AlertDialog.Builder(context) - .setCancelable(false) - .setView(R.layout.dialog_opening_post) - .create(); - alertDialog.show(); - mediaRepository.fetch( - Long.parseLong(mediaId), - CoroutineUtilsKt.getContinuation((media, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> { - if (throwable != null) { - alertDialog.dismiss(); - Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); - return; - } - final NavController navController = NavHostFragment.findNavController(StoryViewerFragment.this); - final Bundle bundle = new Bundle(); - bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, media); - try { - navController.navigate(R.id.action_global_post_view, bundle); - alertDialog.dismiss(); - } catch (Exception e) { - Log.e(TAG, "openPostDialog: ", e); - } - }), Dispatchers.getIO()) - ); - }); - final View.OnClickListener storyActionListener = v -> { - final Object tag = v.getTag(); - if (tag instanceof PollSticker) { - poll = (PollSticker) tag; - final List tallies = poll.getTallies(); - final String[] choices = tallies.stream() - .map(t -> (poll.getViewerVote() != null && poll.getViewerVote() == tallies.indexOf(t) ? "√ " : "") - + t.getText() + " (" + t.getCount() + ")" ) - .toArray(String[]::new); - final ArrayAdapter adapter = new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, choices); - if (poll.getViewerVote() != null) { - new AlertDialog.Builder(context) - .setTitle(R.string.voted_story_poll) - .setAdapter(adapter, null) - .setPositiveButton(R.string.ok, null) - .show(); - } else { - new AlertDialog.Builder(context) - .setTitle(poll.getQuestion()) - .setAdapter(adapter, (d, w) -> { - sticking = true; - storiesRepository.respondToPoll( - csrfToken, - userId, - deviceId, - currentStory.getId().split("_")[0], - poll.getPollId(), - w, - CoroutineUtilsKt.getContinuation( - (storyStickerResponse, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> { - if (throwable != null) { - sticking = false; - Log.e(TAG, "Error responding", throwable); - try { - Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); - } catch (Exception ignored) {} - return; - } - sticking = false; - try { - poll.setViewerVote(w); - Toast.makeText(context, R.string.votef_story_poll, Toast.LENGTH_SHORT).show(); - } catch (Exception ignored) {} - }), - Dispatchers.getIO() - ) - ); - }) - .setPositiveButton(R.string.cancel, null) - .show(); - } - } else if (tag instanceof QuestionSticker) { - question = (QuestionSticker) tag; - final EditText input = new EditText(context); - input.setHint(R.string.answer_hint); - final AlertDialog ad = new AlertDialog.Builder(context) - .setTitle(question.getQuestion()) - .setView(input) - .setPositiveButton(R.string.confirm, (d, w) -> { - sticking = true; - storiesRepository.respondToQuestion( - csrfToken, - userId, - deviceId, - currentStory.getId().split("_")[0], - question.getQuestionId(), - input.getText().toString(), - CoroutineUtilsKt.getContinuation( - (storyStickerResponse, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> { - if (throwable != null) { - sticking = false; - Log.e(TAG, "Error responding", throwable); - try { - Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); - } catch (Exception ignored) {} - return; - } - sticking = false; - try { - Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show(); - } catch (Exception ignored) {} - }), - Dispatchers.getIO() - ) - ); - }) - .setNegativeButton(R.string.cancel, null) - .show(); - ad.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); - input.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {} - - @Override - public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { - ad.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(!TextUtils.isEmpty(s)); - } - - @Override - public void afterTextChanged(final Editable s) {} - }); - } else if (tag instanceof String[]) { - final String[] rawMentions = (String[]) tag; - mentions = new ArrayList(Arrays.asList(rawMentions)); - new AlertDialog.Builder(context) - .setTitle(R.string.story_mentions) - .setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, rawMentions), (d, w) -> openProfile(mentions.get(w))) - .setPositiveButton(R.string.cancel, null) - .show(); - } else if (tag instanceof QuizSticker) { - quiz = (QuizSticker) tag; - final List tallies = quiz.getTallies(); - final String[] choices = tallies.stream().map( - t -> (quiz.getViewerAnswer() != null && quiz.getViewerAnswer() == tallies.indexOf(t) ? "√ " : "") + - (quiz.getCorrectAnswer() == tallies.indexOf(t) ? "*** " : "") + - t.getText() + " (" + t.getCount() + ")" - ).toArray(String[]::new); - new AlertDialog.Builder(context) - .setTitle(quiz.getViewerAnswer() != null ? getString(R.string.story_quizzed) : quiz.getQuestion()) - .setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, choices), (d, w) -> { - if (quiz.getViewerAnswer() == null) { - sticking = true; - storiesRepository.respondToQuiz( - csrfToken, - userId, - deviceId, - currentStory.getId().split("_")[0], - quiz.getQuizId(), - w, - CoroutineUtilsKt.getContinuation( - (storyStickerResponse, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> { - if (throwable != null) { - sticking = false; - Log.e(TAG, "Error responding", throwable); - try { - Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); - } catch (Exception ignored) {} - return; - } - sticking = false; - try { - quiz.setViewerAnswer(w); - Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show(); - } catch (Exception ignored) {} - }), - Dispatchers.getIO() - ) - ); - } - }) - .setPositiveButton(R.string.cancel, null) - .show(); - } else if (tag instanceof SliderSticker) { - slider = (SliderSticker) tag; - NumberFormat percentage = NumberFormat.getPercentInstance(); - percentage.setMaximumFractionDigits(2); - LinearLayout sliderView = new LinearLayout(context); - sliderView.setLayoutParams(new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT)); - sliderView.setOrientation(LinearLayout.VERTICAL); - TextView tv = new TextView(context); - tv.setGravity(Gravity.CENTER_HORIZONTAL); - final SeekBar input = new SeekBar(context); - double avg = slider.getSliderVoteAverage() * 100; - input.setProgress((int) avg); - sliderView.addView(input); - sliderView.addView(tv); - if (slider.getViewerVote().isNaN() && slider.getViewerCanVote()) { - input.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - sliderValue = progress / 100.0; - tv.setText(percentage.format(sliderValue)); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - } - }); - new AlertDialog.Builder(context) - .setTitle(TextUtils.isEmpty(slider.getQuestion()) ? slider.getEmoji() : slider.getQuestion()) - .setMessage(getResources().getQuantityString(R.plurals.slider_info, - slider.getSliderVoteCount(), - slider.getSliderVoteCount(), - percentage.format(slider.getSliderVoteAverage()))) - .setView(sliderView) - .setPositiveButton(R.string.confirm, (d, w) -> { - sticking = true; - storiesRepository.respondToSlider( - csrfToken, - userId, - deviceId, - currentStory.getId().split("_")[0], - slider.getSliderId(), - sliderValue, - CoroutineUtilsKt.getContinuation( - (storyStickerResponse, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> { - if (throwable != null) { - sticking = false; - Log.e(TAG, "Error responding", throwable); - try { - Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); - } catch (Exception ignored) {} - return; - } - sticking = false; - try { - slider.setViewerVote(sliderValue); - Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show(); - } catch (Exception ignored) {} - }), Dispatchers.getIO() - ) - ); - }) - .setNegativeButton(R.string.cancel, null) - .show(); - } else { - input.setEnabled(false); - tv.setText(getString(R.string.slider_answer, percentage.format(slider.getViewerVote()))); - new AlertDialog.Builder(context) - .setTitle(TextUtils.isEmpty(slider.getQuestion()) ? slider.getEmoji() : slider.getQuestion()) - .setMessage(getResources().getQuantityString(R.plurals.slider_info, - slider.getSliderVoteCount(), - slider.getSliderVoteCount(), - percentage.format(slider.getSliderVoteAverage()))) - .setView(sliderView) - .setPositiveButton(R.string.ok, null) - .show(); - } - } - }; - binding.poll.setOnClickListener(storyActionListener); - binding.answer.setOnClickListener(storyActionListener); - binding.mention.setOnClickListener(storyActionListener); - binding.quiz.setOnClickListener(storyActionListener); - binding.slider.setOnClickListener(storyActionListener); - } - - private void resetView() { - final Context context = getContext(); - if (context == null) return; - live = null; - slidePos = 0; - lastSlidePos = 0; - if (menuDownload != null) menuDownload.setVisible(false); - if (menuDm != null) menuDm.setVisible(false); - if (menuProfile != null) menuProfile.setVisible(false); - downloadVisible = false; - dmVisible = false; - profileVisible = false; - binding.imageViewer.setController(null); - releasePlayer(); - String currentStoryMediaId = null; - final Type type = options.getType(); - StoryViewerOptions fetchOptions = null; - switch (type) { - case HIGHLIGHT: { - final HighlightsViewModel highlightsViewModel = (HighlightsViewModel) viewModel; - final List models = highlightsViewModel.getList().getValue(); - if (models == null || models.isEmpty() || currentFeedStoryIndex >= models.size() || currentFeedStoryIndex < 0) { - Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); - return; - } - final Story model = models.get(currentFeedStoryIndex); - currentStoryMediaId = model.getId(); - fetchOptions = StoryViewerOptions.forHighlight(model.getId()); - highlightTitle = model.getTitle(); - break; - } - case FEED_STORY_POSITION: { - final FeedStoriesViewModel feedStoriesViewModel = (FeedStoriesViewModel) viewModel; - final List models = feedStoriesViewModel.getList().getValue(); - if (models == null || currentFeedStoryIndex >= models.size() || currentFeedStoryIndex < 0) - return; - final Story model = models.get(currentFeedStoryIndex); - currentStoryMediaId = String.valueOf(model.getUser().getPk()); - currentStoryUsername = model.getUser().getUsername(); - fetchOptions = StoryViewerOptions.forUser(model.getUser().getPk(), currentStoryUsername); - live = model.getBroadcast(); - break; - } - case STORY_ARCHIVE: { - final ArchivesViewModel archivesViewModel = (ArchivesViewModel) viewModel; - final List models = archivesViewModel.getList().getValue(); - if (models == null || models.isEmpty() || currentFeedStoryIndex >= models.size() || currentFeedStoryIndex < 0) { - Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); - return; - } - final Story model = models.get(currentFeedStoryIndex); - currentStoryMediaId = parseStoryMediaId(model.getId()); - currentStoryUsername = model.getTitle(); - fetchOptions = StoryViewerOptions.forStoryArchive(model.getId()); - break; - } - case USER: { - currentStoryMediaId = String.valueOf(options.getId()); - currentStoryUsername = options.getName(); - fetchOptions = StoryViewerOptions.forUser(options.getId(), currentStoryUsername); - break; - } - } - setTitle(type); - storiesViewModel.getList().setValue(Collections.emptyList()); - if (type == Type.STORY) { - storiesRepository.fetch( - options.getId(), - CoroutineUtilsKt.getContinuation((storyModel, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> { - if (throwable != null) { - Toast.makeText(context, throwable.getMessage(), Toast.LENGTH_SHORT).show(); - Log.e(TAG, "Error", throwable); - return; - } - fetching = false; - binding.storiesList.setVisibility(View.GONE); - if (storyModel == null) { - storiesViewModel.getList().setValue(Collections.emptyList()); - currentStory = null; - return; - } - storiesViewModel.getList().setValue(Collections.singletonList(storyModel)); - currentStory = storyModel; - refreshStory(); - }), Dispatchers.getIO()) - ); - return; - } - if (currentStoryMediaId == null) return; - if (live != null) { - currentStory = null; - refreshLive(); - return; - } - final ServiceCallback> storyCallback = new ServiceCallback>() { - @Override - public void onSuccess(final List storyModels) { - fetching = false; - if (storyModels == null || storyModels.isEmpty()) { - storiesViewModel.getList().setValue(Collections.emptyList()); - currentStory = null; - binding.storiesList.setVisibility(View.GONE); - return; - } - binding.storiesList.setVisibility((storyModels.size() == 1 && currentFeedStoryIndex == -1) ? View.GONE : View.VISIBLE); - if (currentFeedStoryIndex == -1) { - binding.btnBackward.setVisibility(View.GONE); - binding.btnForward.setVisibility(View.GONE); - } - storiesViewModel.getList().setValue(storyModels); - currentStory = storyModels.get(0); - refreshStory(); - } - - @Override - public void onFailure(final Throwable t) { - Log.e(TAG, "Error", t); - } - }; - storiesRepository.getStories( - fetchOptions, - CoroutineUtilsKt.getContinuation((storyModels, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> { - if (throwable != null) { - storyCallback.onFailure(throwable); - return; - } - //noinspection unchecked - storyCallback.onSuccess((List) storyModels); - }), Dispatchers.getIO()) - ); - } - - private void setTitle(final Type type) { - final boolean hasUsername = !TextUtils.isEmpty(currentStoryUsername); - if (type == Type.HIGHLIGHT) { - final ActionBar actionBar = fragmentActivity.getSupportActionBar(); - if (actionBar != null) { - actionBarTitle = highlightTitle; - actionBar.setTitle(highlightTitle); - } - } else if (hasUsername) { - currentStoryUsername = currentStoryUsername.replace("@", ""); - final ActionBar actionBar = fragmentActivity.getSupportActionBar(); - if (actionBar != null) { - actionBarTitle = currentStoryUsername; - actionBar.setTitle(currentStoryUsername); - } - } - } - - private synchronized void refreshLive() { - binding.storiesList.setVisibility(View.INVISIBLE); - binding.viewStoryPost.setVisibility(View.GONE); - binding.spotify.setVisibility(View.GONE); - binding.poll.setVisibility(View.GONE); - binding.answer.setVisibility(View.GONE); - binding.mention.setVisibility(View.GONE); - binding.quiz.setVisibility(View.GONE); - binding.slider.setVisibility(View.GONE); - lastSlidePos = slidePos; - releasePlayer(); - url = live.getDashPlaybackUrl(); - setupLive(); - final ActionBar actionBar = fragmentActivity.getSupportActionBar(); - actionBarSubtitle = TextUtils.epochSecondToString(live.getPublishedTime()); - if (actionBar != null) { - try { - actionBar.setSubtitle(actionBarSubtitle); - } catch (Exception e) { - Log.e(TAG, "refreshLive: ", e); - } - } - } - - private synchronized void refreshStory() { - if (binding.storiesList.getVisibility() == View.VISIBLE) { - final List storyModels = storiesViewModel.getList().getValue(); - if (storyModels != null && storyModels.size() > 0) { - StoryMedia item = storyModels.get(lastSlidePos); - if (item != null) { - item.setCurrentSlide(false); - storiesAdapter.notifyItemChanged(lastSlidePos, item); - } - item = storyModels.get(slidePos); - if (item != null) { - item.setCurrentSlide(true); - storiesAdapter.notifyItemChanged(slidePos, item); - } - } - } - lastSlidePos = slidePos; - - final MediaItemType itemType = currentStory.getType(); - - url = itemType == MediaItemType.MEDIA_TYPE_IMAGE - ? ResponseBodyUtils.getImageUrl(currentStory) - : ResponseBodyUtils.getVideoUrl(currentStory); - - if (currentStory.getStoryFeedMedia() != null) { - final String shortCode = currentStory.getStoryFeedMedia().get(0).getMediaId(); - binding.viewStoryPost.setVisibility(View.VISIBLE); - binding.viewStoryPost.setTag(shortCode); - } - - final StoryAppAttribution spotify = currentStory.getStoryAppAttribution(); - if (spotify != null) { - binding.spotify.setVisibility(View.VISIBLE); - binding.spotify.setText(spotify.getName()); - binding.spotify.setTag(spotify.getContentUrl().split("?")[0]); - } - - if (currentStory.getStoryPolls() != null) { - poll = currentStory.getStoryPolls().get(0).getPollSticker(); - binding.poll.setVisibility(View.VISIBLE); - binding.poll.setTag(poll); - } - - if (currentStory.getStoryQuestions() != null) { - question = currentStory.getStoryQuestions().get(0).getQuestionSticker(); - binding.answer.setVisibility(View.VISIBLE); - binding.answer.setTag(question); - } - - mentions.clear(); - if (currentStory.getReelMentions() != null) { - mentions.addAll(currentStory.getReelMentions().stream().map( - s -> s.getUser().getUsername() - ).distinct().collect(Collectors.toList())); - } - if (currentStory.getStoryHashtags() != null) { - mentions.addAll(currentStory.getStoryHashtags().stream().map( - s -> s.getHashtag().getName() - ).distinct().collect(Collectors.toList())); - } - if (currentStory.getStoryLocations() != null) { - mentions.addAll(currentStory.getStoryLocations().stream().map( - s -> s.getLocation().getShortName() + " (" + s.getLocation().getPk() + ")" - ).distinct().collect(Collectors.toList())); - } - if (mentions.size() > 0) { - binding.mention.setVisibility(View.VISIBLE); - binding.mention.setTag(mentions.stream().toArray(String[]::new)); - } - - if (currentStory.getStoryQuizs() != null) { - quiz = currentStory.getStoryQuizs().get(0).getQuizSticker(); - binding.quiz.setVisibility(View.VISIBLE); - binding.quiz.setTag(quiz); - } - - if (currentStory.getStorySliders() != null) { - slider = currentStory.getStorySliders().get(0).getSliderSticker(); - binding.slider.setVisibility(View.VISIBLE); - binding.slider.setTag(slider); - } - - if (currentStory.getStoryCta() != null) { - final StoryCta swipeUp = currentStory.getStoryCta().get(0).getLinks().get(0); - binding.swipeUp.setVisibility(View.VISIBLE); - binding.swipeUp.setText(currentStory.getLinkText()); - final String swipeUpUrl = swipeUp.getWebUri(); - final String actualLink = swipeUpUrl.startsWith("https://l.instagram.com/") - ? Uri.parse(swipeUpUrl).getQueryParameter("u") - : null; - binding.swipeUp.setTag(actualLink == null && actualLink.startsWith("http") - ? swipeUpUrl : actualLink); - } - - releasePlayer(); - final Type type = options.getType(); - if (type == Type.HASHTAG || type == Type.LOCATION) { - final ActionBar actionBar = fragmentActivity.getSupportActionBar(); - if (actionBar != null) { - actionBarTitle = currentStory.getUser().getUsername(); - actionBar.setTitle(currentStory.getUser().getUsername()); - } - } - if (itemType == MediaItemType.MEDIA_TYPE_VIDEO) setupVideo(); - else setupImage(); - - final ActionBar actionBar = fragmentActivity.getSupportActionBar(); - actionBarSubtitle = TextUtils.epochSecondToString(currentStory.getTakenAt()); - if (actionBar != null) { - try { - actionBar.setSubtitle(actionBarSubtitle); - } catch (Exception e) { - Log.e(TAG, "refreshStory: ", e); - } - } - - if (settingsHelper.getBoolean(MARK_AS_SEEN)) - storiesRepository.seen( - csrfToken, - userId, - deviceId, - currentStory.getId(), - currentStory.getTakenAt(), - System.currentTimeMillis() / 1000, - CoroutineUtilsKt.getContinuation((s, throwable) -> {}, Dispatchers.getIO()) - ); - } - - private void removeStickers() { - binding.swipeUp.setVisibility(View.GONE); - binding.quiz.setVisibility(View.GONE); - binding.spotify.setVisibility(View.GONE); - binding.mention.setVisibility(View.GONE); - binding.viewStoryPost.setVisibility(View.GONE); - binding.answer.setVisibility(View.GONE); - binding.slider.setVisibility(View.GONE); - } - - private void downloadStory() { - final Context context = getContext(); - if (context == null) return; - if (currentStory == null) { - Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); - return; - } - DownloadUtils.download(context, currentStory); - } - - private void setupImage() { - binding.progressView.setVisibility(View.VISIBLE); - binding.playerView.setVisibility(View.GONE); - binding.imageViewer.setVisibility(View.VISIBLE); - final ImageRequest requestBuilder = ImageRequestBuilder.newBuilderWithSource(Uri.parse(url)) - .setLocalThumbnailPreviewsEnabled(true) - .setProgressiveRenderingEnabled(true) - .build(); - final DraweeController controller = Fresco.newDraweeControllerBuilder() - .setImageRequest(requestBuilder) - .setOldController(binding.imageViewer.getController()) - .setControllerListener(new BaseControllerListener() { - - @Override - public void onFailure(final String id, final Throwable throwable) { - binding.progressView.setVisibility(View.GONE); - } - - @Override - public void onFinalImageSet(final String id, - final ImageInfo imageInfo, - final Animatable animatable) { - if (menuDownload != null) { - downloadVisible = true; - menuDownload.setVisible(true); - } - if (currentStory.getCanReply() && menuDm != null) { - dmVisible = true; - menuDm.setVisible(true); - } - if (!TextUtils.isEmpty(currentStory.getUser().getUsername())) { - profileVisible = true; - menuProfile.setVisible(true); - } - binding.progressView.setVisibility(View.GONE); - } - }) - .build(); - binding.imageViewer.setController(controller); - } - - private void setupVideo() { - binding.playerView.setVisibility(View.VISIBLE); - binding.progressView.setVisibility(View.GONE); - binding.imageViewer.setVisibility(View.GONE); - binding.imageViewer.setController(null); - - final Context context = getContext(); - if (context == null) return; - player = new SimpleExoPlayer.Builder(context).build(); - binding.playerView.setPlayer(player); - player.setPlayWhenReady(settingsHelper.getBoolean(PreferenceKeys.AUTOPLAY_VIDEOS_STORIES)); - - final Uri uri = Uri.parse(url); - final MediaItem mediaItem = MediaItem.fromUri(uri); - final ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(new DefaultDataSourceFactory(context, "instagram")) - .createMediaSource(mediaItem); - mediaSource.addEventListener(new Handler(), new MediaSourceEventListener() { - @Override - public void onLoadCompleted(final int windowIndex, - @Nullable final MediaSource.MediaPeriodId mediaPeriodId, - @NonNull final LoadEventInfo loadEventInfo, - @NonNull final MediaLoadData mediaLoadData) { - if (menuDownload != null) { - downloadVisible = true; - menuDownload.setVisible(true); - } - if (currentStory.getCanReply() && menuDm != null) { - dmVisible = true; - menuDm.setVisible(true); - } - if (!TextUtils.isEmpty(currentStory.getUser().getUsername()) && menuProfile != null) { - profileVisible = true; - menuProfile.setVisible(true); - } - binding.progressView.setVisibility(View.GONE); - } - - @Override - public void onLoadStarted(final int windowIndex, - @Nullable final MediaSource.MediaPeriodId mediaPeriodId, - @NonNull final LoadEventInfo loadEventInfo, - @NonNull final MediaLoadData mediaLoadData) { - if (menuDownload != null) { - downloadVisible = true; - menuDownload.setVisible(true); - } - if (currentStory.getCanReply() && menuDm != null) { - dmVisible = true; - menuDm.setVisible(true); - } - if (!TextUtils.isEmpty(currentStory.getUser().getUsername()) && menuProfile != null) { - profileVisible = true; - menuProfile.setVisible(true); - } - binding.progressView.setVisibility(View.VISIBLE); - } - - @Override - public void onLoadCanceled(final int windowIndex, - @Nullable final MediaSource.MediaPeriodId mediaPeriodId, - @NonNull final LoadEventInfo loadEventInfo, - @NonNull final MediaLoadData mediaLoadData) { - binding.progressView.setVisibility(View.GONE); - } - - @Override - public void onLoadError(final int windowIndex, - @Nullable final MediaSource.MediaPeriodId mediaPeriodId, - @NonNull final LoadEventInfo loadEventInfo, - @NonNull final MediaLoadData mediaLoadData, - @NonNull final IOException error, - final boolean wasCanceled) { - if (menuDownload != null) { - downloadVisible = false; - menuDownload.setVisible(false); - } - if (menuDm != null) { - dmVisible = false; - menuDm.setVisible(false); - } - if (menuProfile != null) { - profileVisible = false; - menuProfile.setVisible(false); - } - binding.progressView.setVisibility(View.GONE); - } - }); - player.setMediaSource(mediaSource); - player.prepare(); - - binding.playerView.setOnClickListener(v -> { - if (player != null) { - if (player.getPlaybackState() == Player.STATE_ENDED) player.seekTo(0); - player.setPlayWhenReady(player.getPlaybackState() == Player.STATE_ENDED || !player.isPlaying()); - } - }); - } - - private void setupLive() { - binding.playerView.setVisibility(View.VISIBLE); - binding.progressView.setVisibility(View.GONE); - binding.imageViewer.setVisibility(View.GONE); - binding.imageViewer.setController(null); - - if (menuDownload != null) menuDownload.setVisible(false); - if (menuDm != null) menuDm.setVisible(false); - - final Context context = getContext(); - if (context == null) return; - player = new SimpleExoPlayer.Builder(context).build(); - binding.playerView.setPlayer(player); - player.setPlayWhenReady(settingsHelper.getBoolean(PreferenceKeys.AUTOPLAY_VIDEOS_STORIES)); - - final Uri uri = Uri.parse(url); - final MediaItem mediaItem = MediaItem.fromUri(uri); - final DashMediaSource mediaSource = new DashMediaSource.Factory(new DefaultDataSourceFactory(context, "instagram")) - .createMediaSource(mediaItem); - mediaSource.addEventListener(new Handler(), new MediaSourceEventListener() { - @Override - public void onLoadCompleted(final int windowIndex, - @Nullable final MediaSource.MediaPeriodId mediaPeriodId, - @NonNull final LoadEventInfo loadEventInfo, - @NonNull final MediaLoadData mediaLoadData) { - binding.progressView.setVisibility(View.GONE); - } - - @Override - public void onLoadStarted(final int windowIndex, - @Nullable final MediaSource.MediaPeriodId mediaPeriodId, - @NonNull final LoadEventInfo loadEventInfo, - @NonNull final MediaLoadData mediaLoadData) { - binding.progressView.setVisibility(View.VISIBLE); - } - - @Override - public void onLoadCanceled(final int windowIndex, - @Nullable final MediaSource.MediaPeriodId mediaPeriodId, - @NonNull final LoadEventInfo loadEventInfo, - @NonNull final MediaLoadData mediaLoadData) { - binding.progressView.setVisibility(View.GONE); - } - - @Override - public void onLoadError(final int windowIndex, - @Nullable final MediaSource.MediaPeriodId mediaPeriodId, - @NonNull final LoadEventInfo loadEventInfo, - @NonNull final MediaLoadData mediaLoadData, - @NonNull final IOException error, - final boolean wasCanceled) { - binding.progressView.setVisibility(View.GONE); - } - }); - player.setMediaSource(mediaSource); - player.prepare(); - - binding.playerView.setOnClickListener(v -> { - if (player != null) { - if (player.getPlaybackState() == Player.STATE_ENDED) player.seekTo(0); - player.setPlayWhenReady(player.getPlaybackState() == Player.STATE_ENDED || !player.isPlaying()); - } - }); - } - - private void openProfile(final String username) { - final ActionBar actionBar = fragmentActivity.getSupportActionBar(); - if (actionBar != null) { - actionBar.setSubtitle(null); - } - final char t = username.charAt(0); - if (t == '@') { - final NavDirections action = HashTagFragmentDirections.actionGlobalProfileFragment(username); - NavHostFragment.findNavController(this).navigate(action); - } else if (t == '#') { - final NavDirections action = HashTagFragmentDirections.actionGlobalHashTagFragment(username.substring(1)); - NavHostFragment.findNavController(this).navigate(action); - } else { - final NavDirections action = ProfileFragmentDirections - .actionGlobalLocationFragment(Long.parseLong(username.split(" \\(")[1].replace(")", ""))); - NavHostFragment.findNavController(this).navigate(action); - } - } - - private void releasePlayer() { - if (player == null) return; - try { player.stop(true); } catch (Exception ignored) { } - try { player.release(); } catch (Exception ignored) { } - player = null; - } - - private void paginateStories(Object newFeedStory, Object oldFeedStory, Context context, boolean backward, boolean last) { - if (newFeedStory != null) { - if (fetching) { - Toast.makeText(context, R.string.be_patient, Toast.LENGTH_SHORT).show(); - return; - } - if (settingsHelper.getBoolean(MARK_AS_SEEN) - && oldFeedStory instanceof Story - && viewModel instanceof FeedStoriesViewModel) { - final FeedStoriesViewModel feedStoriesViewModel = (FeedStoriesViewModel) viewModel; - final Story oldFeedStoryModel = (Story) oldFeedStory; - if (oldFeedStoryModel.getSeen() == null || !oldFeedStoryModel.getSeen().equals(oldFeedStoryModel.getLatestReelMedia())) { - oldFeedStoryModel.setSeen(oldFeedStoryModel.getLatestReelMedia()); - final List models = feedStoriesViewModel.getList().getValue(); - final List modelsCopy = models == null ? new ArrayList<>() : new ArrayList<>(models); - modelsCopy.set(currentFeedStoryIndex, oldFeedStoryModel); - feedStoriesViewModel.getList().postValue(modelsCopy); - } - } - fetching = true; - binding.btnBackward.setVisibility(currentFeedStoryIndex == 1 && backward ? View.INVISIBLE : View.VISIBLE); - binding.btnForward.setVisibility(last ? View.INVISIBLE : View.VISIBLE); - currentFeedStoryIndex = backward ? (currentFeedStoryIndex - 1) : (currentFeedStoryIndex + 1); - resetView(); - } - } - - /** - * Parses the Story's media ID. For user stories this is a number, but for archive stories - * this is "archiveDay:" plus a number. - */ - private static String parseStoryMediaId(String rawId) { - final String regex = "(?:archiveDay:)?(.+)"; - final Pattern pattern = Pattern.compile(regex); - final Matcher matcher = pattern.matcher(rawId); - - if (matcher.matches() && matcher.groupCount() >= 1) { - return matcher.group(1); - } - - return rawId; - } -} diff --git a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.kt b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.kt new file mode 100644 index 00000000..121c8150 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.kt @@ -0,0 +1,902 @@ +package awais.instagrabber.fragments + +import android.annotation.SuppressLint +import android.content.DialogInterface.OnClickListener +import android.graphics.drawable.Animatable +import android.net.Uri +import android.os.Bundle +import android.os.Handler +import android.util.Log +import android.view.* +import android.view.GestureDetector.SimpleOnGestureListener +import android.widget.* +import android.widget.SeekBar.OnSeekBarChangeListener +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.view.ContextThemeWrapper +import androidx.appcompat.widget.PopupMenu +import androidx.core.view.GestureDetectorCompat +import androidx.fragment.app.Fragment +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.navigation.NavController +import androidx.navigation.fragment.NavHostFragment +import androidx.recyclerview.widget.LinearLayoutManager +import awais.instagrabber.BuildConfig +import awais.instagrabber.R +import awais.instagrabber.adapters.StoriesAdapter +import awais.instagrabber.customviews.helpers.SwipeGestureListener +import awais.instagrabber.databinding.FragmentStoryViewerBinding +import awais.instagrabber.fragments.settings.PreferenceKeys +import awais.instagrabber.interfaces.SwipeEvent +import awais.instagrabber.models.Resource +import awais.instagrabber.models.enums.FavoriteType +import awais.instagrabber.models.enums.MediaItemType +import awais.instagrabber.models.enums.StoryPaginationType +import awais.instagrabber.repositories.requests.StoryViewerOptions +import awais.instagrabber.repositories.responses.directmessages.RankedRecipient +import awais.instagrabber.repositories.responses.stories.* +import awais.instagrabber.utils.* +import awais.instagrabber.utils.DownloadUtils.download +import awais.instagrabber.utils.TextUtils.epochSecondToString +import awais.instagrabber.utils.extensions.TAG +import awais.instagrabber.viewmodels.ArchivesViewModel +import awais.instagrabber.viewmodels.FeedStoriesViewModel +import awais.instagrabber.viewmodels.HighlightsViewModel +import awais.instagrabber.viewmodels.StoryFragmentViewModel +import awais.instagrabber.webservices.MediaRepository +import awais.instagrabber.webservices.StoriesRepository +import com.facebook.drawee.backends.pipeline.Fresco +import com.facebook.drawee.controller.BaseControllerListener +import com.facebook.drawee.interfaces.DraweeController +import com.facebook.imagepipeline.image.ImageInfo +import com.facebook.imagepipeline.request.ImageRequestBuilder +import com.google.android.exoplayer2.MediaItem +import com.google.android.exoplayer2.Player +import com.google.android.exoplayer2.SimpleExoPlayer +import com.google.android.exoplayer2.source.* +import com.google.android.exoplayer2.source.dash.DashMediaSource +import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory +import com.google.android.material.textfield.TextInputEditText +import java.io.IOException +import java.text.NumberFormat +import java.util.* + + +class StoryViewerFragment : Fragment() { + private val TAG = "StoryViewerFragment" + + private val cookie = Utils.settingsHelper.getString(Constants.COOKIE) + private var root: View? = null + private var currentStoryUsername: String? = null + private var highlightTitle: String? = null + private var storiesAdapter: StoriesAdapter? = null + private var swipeEvent: SwipeEvent? = null + private var gestureDetector: GestureDetectorCompat? = null + private val storiesRepository: StoriesRepository? = null + private val mediaRepository: MediaRepository? = null + private var live: Broadcast? = null + private var menuProfile: MenuItem? = null + private var profileVisible: Boolean = false + private var player: SimpleExoPlayer? = null + + private var actionBarTitle: String? = null + private var actionBarSubtitle: String? = null + private var fetching = false + private val sticking = false + private var shouldRefresh = true + private var dmVisible = false + private var currentFeedStoryIndex = 0 + private var sliderValue = 0.0 + private var options: StoryViewerOptions? = null + private var listViewModel: ViewModel? = null + private var backStackSavedStateResultLiveData: MutableLiveData? = null + private lateinit var fragmentActivity: AppCompatActivity + private lateinit var storiesViewModel: StoryFragmentViewModel + private lateinit var binding: FragmentStoryViewerBinding + + @Suppress("UNCHECKED_CAST") + private val backStackSavedStateObserver = Observer { result -> + if (result == null) return@Observer + if ((result is RankedRecipient)) { + if (context != null) { + Toast.makeText(context, R.string.sending, Toast.LENGTH_SHORT).show() + } + storiesViewModel.shareDm(result) + } else if ((result is Set<*>)) { + try { + if (context != null) { + Toast.makeText(context, R.string.sending, Toast.LENGTH_SHORT).show() + } + storiesViewModel.shareDm(result as Set) + } catch (e: Exception) { + Log.e(TAG, "share: ", e) + } + } + // clear result + backStackSavedStateResultLiveData?.postValue(null) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + fragmentActivity = requireActivity() as AppCompatActivity + storiesViewModel = ViewModelProvider(this).get(StoryFragmentViewModel::class.java) + setHasOptionsMenu(true) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + if (root != null) { + shouldRefresh = false + return root + } + binding = FragmentStoryViewerBinding.inflate(inflater, container, false) + root = binding.root + return root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + if (!shouldRefresh) return + init() + shouldRefresh = false + } + + override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) { + menuInflater.inflate(R.menu.story_menu, menu) + menuProfile = menu.findItem(R.id.action_profile) + menuProfile!!.isVisible = profileVisible + } + + override fun onPrepareOptionsMenu(menu: Menu) { + // hide menu items from activity + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + val context = context ?: return false + val itemId = item.itemId + if (itemId == R.id.action_profile) { + val username = storiesViewModel.getCurrentStory().value?.user?.username + openProfile(Pair(username, FavoriteType.USER)) + return true + } + return false + } + + override fun onPause() { + super.onPause() + player?.pause() ?: return + } + + override fun onResume() { + super.onResume() + setHasOptionsMenu(true) + try { + val backStackEntry = NavHostFragment.findNavController(this).currentBackStackEntry + if (backStackEntry != null) { + backStackSavedStateResultLiveData = backStackEntry.savedStateHandle.getLiveData("result") + backStackSavedStateResultLiveData?.observe(viewLifecycleOwner, backStackSavedStateObserver) + } + } catch (e: Exception) { + Log.e(TAG, "onResume: ", e) + } + val actionBar = fragmentActivity.supportActionBar ?: return + actionBar.title = storiesViewModel.getTitle().value + actionBar.subtitle = storiesViewModel.getDate().value + } + + override fun onDestroy() { + releasePlayer() + val actionBar = fragmentActivity.supportActionBar + actionBar?.subtitle = null + super.onDestroy() + } + + private fun init() { + val args = arguments + if (args == null) return + val fragmentArgs = StoryViewerFragmentArgs.fromBundle(args) + options = fragmentArgs.options + currentFeedStoryIndex = options!!.currentFeedStoryIndex + val type = options!!.type + if (currentFeedStoryIndex >= 0) { + listViewModel = when (type) { + StoryViewerOptions.Type.HIGHLIGHT -> ViewModelProvider(fragmentActivity).get( + HighlightsViewModel::class.java + ) + StoryViewerOptions.Type.STORY_ARCHIVE -> ViewModelProvider(fragmentActivity).get( + ArchivesViewModel::class.java + ) + StoryViewerOptions.Type.FEED_STORY_POSITION -> ViewModelProvider(fragmentActivity).get( + FeedStoriesViewModel::class.java + ) + else -> ViewModelProvider(fragmentActivity).get( + FeedStoriesViewModel::class.java + ) + } + } + setupButtons() + setupStories() + } + + private fun setupStories() { + setupListeners() + val context = context ?: return + binding.storiesList.layoutManager = + LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) + storiesAdapter = StoriesAdapter { model: StoryMedia?, position: Int -> + storiesViewModel.setMedia(position) + } + binding.storiesList.adapter = storiesAdapter + storiesViewModel.getCurrentStory().observe(fragmentActivity, { + if (it?.items != null) { + val storyMedias = it.items.toMutableList() + val newItem = storyMedias.get(0) + newItem.isCurrentSlide = true + storyMedias.set(0, newItem) + storiesAdapter!!.submitList(storyMedias) + storiesViewModel.setMedia(0) + } + }) + storiesViewModel.getDate().observe(fragmentActivity, { + val actionBar = fragmentActivity.supportActionBar + if (actionBar != null && it != null) actionBar.subtitle = it + }) + storiesViewModel.getTitle().observe(fragmentActivity, { + val actionBar = fragmentActivity.supportActionBar + if (actionBar != null && it != null) actionBar.title = it + }) + storiesViewModel.getCurrentMedia().observe(fragmentActivity, { refreshStory(it) }) + storiesViewModel.getCurrentIndex().observe(fragmentActivity, { + storiesAdapter!!.paginate(it) + }) + storiesViewModel.getOptions().observe(fragmentActivity, { + binding.stickers.isEnabled = it.first.size > 0 + }) + + resetView() + } + + private fun setupButtons() { + binding.btnDownload.setOnClickListener({ _ -> downloadStory() }) + binding.btnForward.setOnClickListener({ _ -> storiesViewModel.skip(false) }) + binding.btnBackward.setOnClickListener({ _ -> storiesViewModel.skip(true) }) + binding.btnShare.setOnClickListener({ _ -> shareStoryViaDm() }) + binding.btnReply.setOnClickListener({ _ -> createReplyDialog(null) }) + binding.stickers.setOnClickListener({ _ -> showStickerMenu() }) + } + + @SuppressLint("ClickableViewAccessibility") + private fun setupListeners() { + val hasFeedStories: Boolean + var models: List? = null + if (currentFeedStoryIndex >= 0) { + val type = options!!.type + when (type) { + StoryViewerOptions.Type.HIGHLIGHT -> { + val highlightsViewModel = listViewModel as HighlightsViewModel? + models = highlightsViewModel!!.list.value + } + StoryViewerOptions.Type.FEED_STORY_POSITION -> { + val feedStoriesViewModel = listViewModel as FeedStoriesViewModel? + models = feedStoriesViewModel!!.list.value + } + StoryViewerOptions.Type.STORY_ARCHIVE -> { + val archivesViewModel = listViewModel as ArchivesViewModel? + models = archivesViewModel!!.list.value + } + } + } + hasFeedStories = models != null && !models.isEmpty() + + storiesViewModel.getPagination().observe(fragmentActivity, { + if (models != null) { + when (it) { + StoryPaginationType.FORWARD -> { + paginateStories(false, currentFeedStoryIndex == models.size - 2) + } + StoryPaginationType.BACKWARD -> { + paginateStories(true, false) + } + StoryPaginationType.ERROR -> { + Toast.makeText( + context, + R.string.downloader_unknown_error, + Toast.LENGTH_SHORT + ).show() + } + StoryPaginationType.DO_NOTHING -> { + } // do nothing + } + } + }) + + val context = context ?: return + swipeEvent = label@ SwipeEvent { isRightSwipe: Boolean -> + storiesViewModel.paginate(isRightSwipe) + } + gestureDetector = GestureDetectorCompat(context, SwipeGestureListener(swipeEvent)) + binding.playerView.setOnTouchListener { _, event -> gestureDetector!!.onTouchEvent(event) } + val simpleOnGestureListener: SimpleOnGestureListener = object : SimpleOnGestureListener() { + override fun onFling( + e1: MotionEvent, + e2: MotionEvent, + velocityX: Float, + velocityY: Float + ): Boolean { + val diffX = e2.x - e1.x + try { + if (Math.abs(diffX) > Math.abs(e2.y - e1.y) && Math.abs(diffX) > SwipeGestureListener.SWIPE_THRESHOLD && Math.abs( + velocityX + ) > SwipeGestureListener.SWIPE_VELOCITY_THRESHOLD + ) { + storiesViewModel.paginate(diffX > 0) + return true + } + } catch (e: Exception) { + if (BuildConfig.DEBUG) Log.e(TAG, "Error", e) + } + return false + } + } + if (hasFeedStories) { + binding.btnBackward.isEnabled = currentFeedStoryIndex != 0 + binding.btnForward.isEnabled = currentFeedStoryIndex != models!!.size - 1 + } + binding.imageViewer.setTapListener(simpleOnGestureListener) + + // process stickers + } + + private fun resetView() { + val context = context ?: return + live = null + if (menuProfile != null) menuProfile!!.isVisible = false + profileVisible = false + binding.imageViewer.controller = null + releasePlayer() + val type = options!!.type + var fetchOptions: StoryViewerOptions? = null + when (type) { + StoryViewerOptions.Type.HIGHLIGHT -> { + val highlightsViewModel = listViewModel as HighlightsViewModel? + val models = highlightsViewModel!!.list.value + if (models == null || models.isEmpty() || currentFeedStoryIndex >= models.size || currentFeedStoryIndex < 0) { + Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT) + .show() + return + } + val (id, _, _, _, _, _, _, _, _, title) = models[currentFeedStoryIndex] + fetchOptions = StoryViewerOptions.forHighlight(id) + } + StoryViewerOptions.Type.FEED_STORY_POSITION -> { + val feedStoriesViewModel = listViewModel as FeedStoriesViewModel? + val models = feedStoriesViewModel!!.list.value + if (models == null || currentFeedStoryIndex >= models.size || currentFeedStoryIndex < 0) return + val (_, _, _, _, user, _, _, _, _, _, _, broadcast) = models[currentFeedStoryIndex] + currentStoryUsername = user!!.username + fetchOptions = StoryViewerOptions.forUser(user.pk, currentStoryUsername) + live = broadcast + } + StoryViewerOptions.Type.STORY_ARCHIVE -> { + val archivesViewModel = listViewModel as ArchivesViewModel? + val models = archivesViewModel!!.list.value + if (models == null || models.isEmpty() || currentFeedStoryIndex >= models.size || currentFeedStoryIndex < 0) { + Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT) + .show() + return + } + val (id, _, _, _, _, _, _, _, _, title) = models[currentFeedStoryIndex] + currentStoryUsername = title + fetchOptions = StoryViewerOptions.forStoryArchive(id) + } + StoryViewerOptions.Type.USER -> { + currentStoryUsername = options!!.name + fetchOptions = StoryViewerOptions.forUser(options!!.id, currentStoryUsername) + } + } + if (type == StoryViewerOptions.Type.STORY) { + storiesViewModel.fetchSingleMedia(options!!.id) + return + } + if (live != null) { + refreshLive() + return + } + storiesViewModel.fetchStory(fetchOptions).observe(fragmentActivity, { + // toast error if necessary? + }) + } + + @Synchronized + private fun refreshLive() { + releasePlayer() + setupLive(live!!.dashPlaybackUrl ?: live!!.dashAbrPlaybackUrl ?: return) + val actionBar = fragmentActivity.supportActionBar + actionBarSubtitle = epochSecondToString(live!!.publishedTime!!) + if (actionBar != null) { + try { + actionBar.setSubtitle(actionBarSubtitle) + } catch (e: Exception) { + Log.e(TAG, "refreshLive: ", e) + } + } + } + + @Synchronized + private fun refreshStory(currentStory: StoryMedia) { + val itemType = currentStory.type + val url = if (itemType === MediaItemType.MEDIA_TYPE_IMAGE) ResponseBodyUtils.getImageUrl(currentStory) + else ResponseBodyUtils.getVideoUrl(currentStory) + + releasePlayer() + + binding.btnDownload.isEnabled = false + binding.btnShare.isEnabled = currentStory.canReshare + binding.btnReply.isEnabled = currentStory.canReply + if (itemType === MediaItemType.MEDIA_TYPE_VIDEO) setupVideo(url) else setupImage(url) + +// if (Utils.settingsHelper.getBoolean(MARK_AS_SEEN)) storiesRepository!!.seen( +// csrfToken, +// userId, +// deviceId, +// currentStory!!.id!!, +// currentStory!!.takenAt, +// System.currentTimeMillis() / 1000 +// ) + } + + private fun downloadStory() { + val context = context ?: return + val currentStory = storiesViewModel.getMedia().value + if (currentStory == null) { + Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show() + return + } + download(context, currentStory) + } + + private fun setupImage(url: String) { + binding.progressView.visibility = View.VISIBLE + binding.playerView.visibility = View.GONE + binding.imageViewer.visibility = View.VISIBLE + val requestBuilder = ImageRequestBuilder.newBuilderWithSource(Uri.parse(url)) + .setLocalThumbnailPreviewsEnabled(true) + .setProgressiveRenderingEnabled(true) + .build() + val controller: DraweeController = Fresco.newDraweeControllerBuilder() + .setImageRequest(requestBuilder) + .setOldController(binding.imageViewer.controller) + .setControllerListener(object : BaseControllerListener() { + override fun onFailure(id: String, throwable: Throwable) { + binding.btnDownload.isEnabled = false + binding.progressView.visibility = View.GONE + } + + override fun onFinalImageSet( + id: String, + imageInfo: ImageInfo?, + animatable: Animatable? + ) { + binding.btnDownload.isEnabled = true + binding.progressView.visibility = View.GONE + } + }) + .build() + binding.imageViewer.controller = controller + } + + private fun setupVideo(url: String) { + binding.playerView.visibility = View.VISIBLE + binding.progressView.visibility = View.GONE + binding.imageViewer.visibility = View.GONE + binding.imageViewer.controller = null + val context = context ?: return + player = SimpleExoPlayer.Builder(context).build() + binding.playerView.player = player + player!!.playWhenReady = + Utils.settingsHelper.getBoolean(PreferenceKeys.AUTOPLAY_VIDEOS_STORIES) + val uri = Uri.parse(url) + val mediaItem = MediaItem.fromUri(uri) + val mediaSource = + ProgressiveMediaSource.Factory(DefaultDataSourceFactory(context, "instagram")) + .createMediaSource(mediaItem) + mediaSource.addEventListener(Handler(), object : MediaSourceEventListener { + override fun onLoadCompleted( + windowIndex: Int, + mediaPeriodId: MediaSource.MediaPeriodId?, + loadEventInfo: LoadEventInfo, + mediaLoadData: MediaLoadData + ) { + binding.btnDownload.isEnabled = true + binding.progressView.visibility = View.GONE + } + + override fun onLoadStarted( + windowIndex: Int, + mediaPeriodId: MediaSource.MediaPeriodId?, + loadEventInfo: LoadEventInfo, + mediaLoadData: MediaLoadData + ) { + binding.btnDownload.isEnabled = true + binding.progressView.visibility = View.VISIBLE + } + + override fun onLoadCanceled( + windowIndex: Int, + mediaPeriodId: MediaSource.MediaPeriodId?, + loadEventInfo: LoadEventInfo, + mediaLoadData: MediaLoadData + ) { + binding.progressView.visibility = View.GONE + } + + override fun onLoadError( + windowIndex: Int, + mediaPeriodId: MediaSource.MediaPeriodId?, + loadEventInfo: LoadEventInfo, + mediaLoadData: MediaLoadData, + error: IOException, + wasCanceled: Boolean + ) { + binding.btnDownload.isEnabled = false + if (menuProfile != null) { + profileVisible = false + menuProfile!!.isVisible = false + } + binding.progressView.visibility = View.GONE + } + }) + player!!.setMediaSource(mediaSource) + player!!.prepare() + binding.playerView.setOnClickListener { v: View? -> + if (player != null) { + if (player!!.playbackState == Player.STATE_ENDED) player!!.seekTo(0) + player!!.playWhenReady = + player!!.playbackState == Player.STATE_ENDED || !player!!.isPlaying + } + } + } + + private fun setupLive(url: String) { + binding.playerView.visibility = View.VISIBLE + binding.progressView.visibility = View.GONE + binding.imageViewer.visibility = View.GONE + binding.imageViewer.controller = null + val context = context ?: return + player = SimpleExoPlayer.Builder(context).build() + binding.playerView.player = player + player!!.playWhenReady = + Utils.settingsHelper.getBoolean(PreferenceKeys.AUTOPLAY_VIDEOS_STORIES) + val uri = Uri.parse(url) + val mediaItem = MediaItem.fromUri(uri) + val mediaSource = DashMediaSource.Factory(DefaultDataSourceFactory(context, "instagram")) + .createMediaSource(mediaItem) + mediaSource.addEventListener(Handler(), object : MediaSourceEventListener { + override fun onLoadCompleted( + windowIndex: Int, + mediaPeriodId: MediaSource.MediaPeriodId?, + loadEventInfo: LoadEventInfo, + mediaLoadData: MediaLoadData + ) { + binding.progressView.visibility = View.GONE + } + + override fun onLoadStarted( + windowIndex: Int, + mediaPeriodId: MediaSource.MediaPeriodId?, + loadEventInfo: LoadEventInfo, + mediaLoadData: MediaLoadData + ) { + binding.progressView.visibility = View.VISIBLE + } + + override fun onLoadCanceled( + windowIndex: Int, + mediaPeriodId: MediaSource.MediaPeriodId?, + loadEventInfo: LoadEventInfo, + mediaLoadData: MediaLoadData + ) { + binding.progressView.visibility = View.GONE + } + + override fun onLoadError( + windowIndex: Int, + mediaPeriodId: MediaSource.MediaPeriodId?, + loadEventInfo: LoadEventInfo, + mediaLoadData: MediaLoadData, + error: IOException, + wasCanceled: Boolean + ) { + binding.progressView.visibility = View.GONE + } + }) + player!!.setMediaSource(mediaSource) + player!!.prepare() + binding.playerView.setOnClickListener { _ -> + if (player != null) { + if (player!!.playbackState == Player.STATE_ENDED) player!!.seekTo(0) + player!!.playWhenReady = + player!!.playbackState == Player.STATE_ENDED || !player!!.isPlaying + } + } + } + + private fun openProfile(data: Pair) { + val navController: NavController = NavHostFragment.findNavController(this) + val bundle = Bundle() + if (data.first == null) { + // toast + return + } + val actionBar = fragmentActivity.supportActionBar + if (actionBar != null) { + actionBar.title = null + actionBar.subtitle = null + } + when (data.second) { + FavoriteType.USER -> { + bundle.putString("username", data.first) + navController.navigate(R.id.action_global_profileFragment, bundle) + } + FavoriteType.HASHTAG -> { + bundle.putString("hashtag", data.first) + navController.navigate(R.id.action_global_hashTagFragment, bundle) + } + FavoriteType.LOCATION -> { + bundle.putLong("locationId", data.first!!.toLong()) + navController.navigate(R.id.action_global_locationFragment, bundle) + } + } + } + + private fun releasePlayer() { + if (player == null) return + try { + player!!.stop(true) + } catch (ignored: Exception) { + } + try { + player!!.release() + } catch (ignored: Exception) { + } + player = null + } + + private fun paginateStories( + backward: Boolean, + last: Boolean + ) { + binding.btnBackward.isEnabled = currentFeedStoryIndex != 1 || !backward + binding.btnForward.isEnabled = !last + currentFeedStoryIndex = if (backward) currentFeedStoryIndex - 1 else currentFeedStoryIndex + 1 + resetView() + } + + private fun createChoiceDialog( + title: String?, + tallies: List, + onClickListener: OnClickListener, + viewerVote: Int?, + correctAnswer: Int? + ) { + val context = context ?: return + val choices = tallies.map { + (if (viewerVote == tallies.indexOf(it)) "√ " else "") + + (if (correctAnswer == tallies.indexOf(it)) "*** " else "") + + it.text + " (" + it.count + ")" } + val builder = AlertDialog.Builder(context) + if (title != null) builder.setTitle(title) + if (viewerVote != null) builder.setMessage(R.string.story_quizzed) + builder.setPositiveButton(if (viewerVote == null) R.string.cancel else R.string.ok, null) + val adapter = ArrayAdapter(context, android.R.layout.simple_list_item_1, choices.toTypedArray()) + builder.setAdapter(adapter, onClickListener) + builder.show() + } + + private fun createMentionDialog() { + val context = context ?: return + val adapter = ArrayAdapter(context, android.R.layout.simple_list_item_1, storiesViewModel.getMentionTexts()) + val builder = AlertDialog.Builder(context) + .setPositiveButton(R.string.ok, null) + .setAdapter(adapter, { _, w -> + val data = storiesViewModel.getMention(w) + if (data != null) openProfile(Pair(data.second, data.third)) + }) + builder.show() + } + + private fun createSliderDialog() { + val slider = storiesViewModel.getSlider().value ?: return + val context = context ?: return + val percentage: NumberFormat = NumberFormat.getPercentInstance() + percentage.maximumFractionDigits = 2 + val sliderView = LinearLayout(context) + sliderView.layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ) + sliderView.orientation = LinearLayout.VERTICAL + val tv = TextView(context) + tv.gravity = Gravity.CENTER_HORIZONTAL + val input = SeekBar(context) + val avg: Double = slider.sliderVoteAverage ?: 0.5 + input.progress = (avg * 100).toInt() + var onClickListener: OnClickListener? = null + + if (slider.viewerVote == null && slider.viewerCanVote == true) { + input.isEnabled = true + input.setOnSeekBarChangeListener(object : OnSeekBarChangeListener { + override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { + sliderValue = progress / 100.0 + tv.text = percentage.format(sliderValue) + } + + override fun onStartTrackingTouch(seekBar: SeekBar) {} + override fun onStopTrackingTouch(seekBar: SeekBar) {} + }) + onClickListener = OnClickListener { _, _ -> storiesViewModel.answerSlider(sliderValue) } + } + else { + input.isEnabled = false + tv.text = getString(R.string.slider_answer, percentage.format(slider.viewerVote)) + } + sliderView.addView(input) + sliderView.addView(tv) + val builder = AlertDialog.Builder(context) + .setTitle(if (slider.question.isNullOrEmpty()) slider.emoji else slider.question) + .setMessage( + resources.getQuantityString(R.plurals.slider_info, + slider.sliderVoteCount ?: 0, + slider.sliderVoteCount ?: 0, + percentage.format(avg))) + .setView(sliderView) + .setPositiveButton(R.string.ok, onClickListener) + + builder.show() + } + + private fun createReplyDialog(question: String?) { + val context = context ?: return + val input = TextInputEditText(context) + input.setHint(R.string.reply_hint) + val builder = AlertDialog.Builder(context) + .setTitle(question ?: context.getString(R.string.reply_story)) + .setView(input) + val onClickListener = OnClickListener{ _, _ -> + val result = + if (question != null) storiesViewModel.answerQuestion(input.text.toString()) + else storiesViewModel.reply(input.text.toString()) + if (result == null) { + Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT) + .show() + } + else result.observe(viewLifecycleOwner, { + when (it.status) { + Resource.Status.SUCCESS -> { + Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT) + .show() + } + Resource.Status.ERROR -> { + Toast.makeText(context, "Error: " + it.message, Toast.LENGTH_SHORT) + .show() + } + Resource.Status.LOADING -> { + Toast.makeText(context, R.string.sending, Toast.LENGTH_SHORT).show() + } + } + }) + } + builder.setPositiveButton(R.string.confirm, onClickListener) + builder.show() + } + + private fun shareStoryViaDm() { + val actionGlobalUserSearch = UserSearchFragmentDirections.actionGlobalUserSearch().apply { + title = getString(R.string.share) + setActionLabel(getString(R.string.send)) + showGroups = true + multiple = true + setSearchMode(UserSearchFragment.SearchMode.RAVEN) + } + try { + val navController = NavHostFragment.findNavController(this@StoryViewerFragment) + navController.navigate(actionGlobalUserSearch) + } catch (e: Exception) { + Log.e(TAG, "shareStoryViaDm: ", e) + } + } + + private fun showStickerMenu() { + val data = storiesViewModel.getOptions().value + if (data == null) return + val themeWrapper = ContextThemeWrapper(context, R.style.popupMenuStyle) + val popupMenu = PopupMenu(themeWrapper, binding.stickers) + val menu = popupMenu.menu + data.first.map { + if (it.second != 0) menu.add(0, it.first, 0, it.second) + if (it.first == R.id.swipeUp) menu.add(0, R.id.swipeUp, 0, data.second) + if (it.first == R.id.spotify) menu.add(0, R.id.spotify, 0, data.third) + } + popupMenu.setOnMenuItemClickListener { item: MenuItem -> + val itemId = item.itemId + if (itemId == R.id.spotify) openExternalLink(storiesViewModel.getAppAttribution()) + else if (itemId == R.id.swipeUp) openExternalLink(storiesViewModel.getSwipeUp()) + else if (itemId == R.id.mentions) createMentionDialog() + else if (itemId == R.id.slider) createSliderDialog() + else if (itemId == R.id.question) { + val question = storiesViewModel.getQuestion().value + if (question != null) createReplyDialog(question.question) + } + else if (itemId == R.id.quiz) { + val quiz = storiesViewModel.getQuiz().value + if (quiz != null) createChoiceDialog( + quiz.question, + quiz.tallies, + { _, w -> storiesViewModel.answerQuiz(w) }, + quiz.viewerAnswer, + quiz.correctAnswer + ) + } + else if (itemId == R.id.poll) { + val poll = storiesViewModel.getPoll().value + if (poll != null) createChoiceDialog( + poll.question, + poll.tallies, + { _, w -> storiesViewModel.answerPoll(w) }, + poll.viewerVote, + null + ) + } + else if (itemId == R.id.viewStoryPost) { + storiesViewModel.getLinkedPost().observe(viewLifecycleOwner, { + if (it == null) Toast.makeText(context, "Error: LiveData is null", Toast.LENGTH_SHORT).show() + else when (it.status) { + Resource.Status.SUCCESS -> { + if (it.data != null) { + val actionBar = fragmentActivity.supportActionBar + if (actionBar != null) { + actionBar.title = null + actionBar.subtitle = null + } + val navController = + NavHostFragment.findNavController(this@StoryViewerFragment) + val bundle = Bundle() + bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, it.data) + try { + navController.navigate(R.id.action_global_post_view, bundle) + } catch (e: Exception) { + Log.e(TAG, "openPostDialog: ", e) + } + } + } + Resource.Status.ERROR -> { + Toast.makeText(context, "Error: " + it.message, Toast.LENGTH_SHORT) + .show() + } + Resource.Status.LOADING -> { + Toast.makeText(context, R.string.opening_post, Toast.LENGTH_SHORT) + .show() + } + } + }) + } + false + } + popupMenu.show() + } + + private fun openExternalLink(url: String?) { + val context = context ?: return + if (url == null) return + AlertDialog.Builder(context) + .setTitle(R.string.swipe_up_confirmation) + .setMessage(url).setPositiveButton(R.string.yes, { _, _ -> Utils.openURL(context, url) }) + .setNegativeButton(R.string.no, null) + .show() + } +} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt index 9c2ef9ae..b043e0f0 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt +++ b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt @@ -199,7 +199,7 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall } } private val onProfilePicClickListener = View.OnClickListener { - val hasStories = viewModel.userStories.value?.data?.isNotEmpty() ?: false + val hasStories = viewModel.userStories.value?.data != null if (!hasStories) { showProfilePicDialog() return@OnClickListener @@ -514,7 +514,7 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall highlightsAdapter?.submitList(it.data) } viewModel.userStories.observe(viewLifecycleOwner) { - binding.header.mainProfileImage.setStoriesBorder(if (it.data.isNullOrEmpty()) 0 else 1) + binding.header.mainProfileImage.setStoriesBorder(if (it.data == null) 0 else 1) } viewModel.eventLiveData.observe(viewLifecycleOwner) { val event = it?.getContentIfNotHandled() ?: return@observe diff --git a/app/src/main/java/awais/instagrabber/models/enums/StoryPaginationType.kt b/app/src/main/java/awais/instagrabber/models/enums/StoryPaginationType.kt new file mode 100644 index 00000000..5bdb4f8f --- /dev/null +++ b/app/src/main/java/awais/instagrabber/models/enums/StoryPaginationType.kt @@ -0,0 +1,7 @@ +package awais.instagrabber.models.enums + +import java.io.Serializable + +enum class StoryPaginationType : Serializable { + FORWARD, BACKWARD, DO_NOTHING, ERROR +} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/repositories/StoriesService.kt b/app/src/main/java/awais/instagrabber/repositories/StoriesService.kt index e964f54b..f7793767 100644 --- a/app/src/main/java/awais/instagrabber/repositories/StoriesService.kt +++ b/app/src/main/java/awais/instagrabber/repositories/StoriesService.kt @@ -34,7 +34,7 @@ interface StoriesService { @FormUrlEncoded @POST("/api/v1/media/{storyId}/{stickerId}/{action}/") suspend fun respondToSticker( - @Path("storyId") storyId: String, + @Path("storyId") storyId: Long, @Path("stickerId") stickerId: Long, @Path("action") action: String, // story_poll_vote, story_question_response, story_slider_vote, story_quiz_answer @FieldMap form: Map, diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/stories/StoryMedia.kt b/app/src/main/java/awais/instagrabber/repositories/responses/stories/StoryMedia.kt index f9559fd0..399e11ca 100644 --- a/app/src/main/java/awais/instagrabber/repositories/responses/stories/StoryMedia.kt +++ b/app/src/main/java/awais/instagrabber/repositories/responses/stories/StoryMedia.kt @@ -10,8 +10,8 @@ import java.io.Serializable data class StoryMedia( // inherited from Media - val pk: String? = null, - val id: String? = null, + val pk: Long = -1, + val id: String = "", val takenAt: Long = -1, val user: User? = null, val canReshare: Boolean = false, diff --git a/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt b/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt index de704c59..5f4a0678 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt +++ b/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt @@ -16,7 +16,6 @@ import awais.instagrabber.repositories.responses.User import awais.instagrabber.repositories.responses.UserProfileContextLink import awais.instagrabber.repositories.responses.directmessages.RankedRecipient import awais.instagrabber.repositories.responses.stories.Story -import awais.instagrabber.repositories.responses.stories.StoryMedia import awais.instagrabber.utils.ControlledRunner import awais.instagrabber.utils.Event import awais.instagrabber.utils.SingleRunner @@ -153,9 +152,9 @@ class ProfileFragmentViewModel( } } - private val storyFetchControlledRunner = ControlledRunner?>() - val userStories: LiveData?>> = currentUserProfileActionLiveData.switchMap { currentUserAndProfilePair -> - liveData?>>(context = viewModelScope.coroutineContext + ioDispatcher) { + private val storyFetchControlledRunner = ControlledRunner() + val userStories: LiveData> = currentUserProfileActionLiveData.switchMap { currentUserAndProfilePair -> + liveData>(context = viewModelScope.coroutineContext + ioDispatcher) { val (currentUserResource, profileResource, action) = currentUserAndProfilePair if (action != INIT && action != REFRESH) { return@liveData @@ -231,7 +230,7 @@ class ProfileFragmentViewModel( return graphQLRepository.fetchUser(stateUsername) } - private suspend fun fetchUserStory(fetchedUser: User): List = storiesRepository.getStories( + private suspend fun fetchUserStory(fetchedUser: User): Story? = storiesRepository.getStories( StoryViewerOptions.forUser(fetchedUser.pk, fetchedUser.fullName) ) diff --git a/app/src/main/java/awais/instagrabber/viewmodels/StoryFragmentViewModel.kt b/app/src/main/java/awais/instagrabber/viewmodels/StoryFragmentViewModel.kt new file mode 100644 index 00000000..8fe4d0ab --- /dev/null +++ b/app/src/main/java/awais/instagrabber/viewmodels/StoryFragmentViewModel.kt @@ -0,0 +1,458 @@ +package awais.instagrabber.viewmodels + +import android.net.Uri +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import awais.instagrabber.R +import awais.instagrabber.managers.DirectMessagesManager +import awais.instagrabber.models.enums.FavoriteType +import awais.instagrabber.models.enums.MediaItemType +import awais.instagrabber.models.enums.StoryPaginationType +import awais.instagrabber.models.Resource +import awais.instagrabber.models.Resource.Companion.error +import awais.instagrabber.models.Resource.Companion.loading +import awais.instagrabber.models.Resource.Companion.success +import awais.instagrabber.models.enums.BroadcastItemType +import awais.instagrabber.repositories.requests.StoryViewerOptions +import awais.instagrabber.repositories.responses.directmessages.RankedRecipient +import awais.instagrabber.repositories.responses.stories.* +import awais.instagrabber.repositories.responses.Media +import awais.instagrabber.utils.* +import awais.instagrabber.webservices.MediaRepository +import awais.instagrabber.webservices.StoriesRepository +import com.google.common.collect.ImmutableList +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class StoryFragmentViewModel : ViewModel() { + // large data + private val currentStory = MutableLiveData() + private val currentMedia = MutableLiveData() + + // small data + private val storyTitle = MutableLiveData() + private val date = MutableLiveData() + private val type = MutableLiveData() + private val poll = MutableLiveData() + private val quiz = MutableLiveData() + private val question = MutableLiveData() + private val slider = MutableLiveData() + private val swipeUp = MutableLiveData() + private val linkedPost = MutableLiveData() + private val appAttribution = MutableLiveData() + private val reelMentions = MutableLiveData>>() + + // process + private val currentIndex = MutableLiveData() + private val pagination = MutableLiveData(StoryPaginationType.DO_NOTHING) + private val options = MutableLiveData>, String?, String?>>() + private val seen = MutableLiveData>() + + // utils + private var messageManager: DirectMessagesManager? = null + private val cookie = Utils.settingsHelper.getString(Constants.COOKIE) + private val deviceId = Utils.settingsHelper.getString(Constants.DEVICE_UUID) + private val csrfToken = getCsrfTokenFromCookie(cookie) + private val userId = getUserIdFromCookie(cookie) + private val storiesRepository: StoriesRepository by lazy { StoriesRepository.getInstance() } + private val mediaRepository: MediaRepository by lazy { MediaRepository.getInstance() } + + /* set functions */ + + fun setStory(story: Story) { + if (story.items == null || story.items.size == 0) { + pagination.postValue(StoryPaginationType.ERROR) + return + } + currentStory.postValue(story) + storyTitle.postValue(story.title ?: story.user?.username) + if (story.broadcast != null) { + date.postValue(story.dateTime) + type.postValue(MediaItemType.MEDIA_TYPE_LIVE) + pagination.postValue(StoryPaginationType.DO_NOTHING) + } + } + + fun setMedia(index: Int) { + if (currentStory.value?.items == null) return + if (index < 0 || index >= currentStory.value!!.items!!.size) { + pagination.postValue(if (index < 0) StoryPaginationType.BACKWARD else StoryPaginationType.FORWARD) + return + } + currentIndex.postValue(index) + val story: Story? = currentStory.value + val media = story!!.items!!.get(index) + currentMedia.postValue(media) + date.postValue(media.date) + type.postValue(media.type) + initStickers(media) + } + + fun setSingleMedia(media: StoryMedia) { + currentStory.postValue(null) + currentIndex.postValue(0) + currentMedia.postValue(media) + date.postValue(media.date) + type.postValue(media.type) + } + + private fun initStickers(media: StoryMedia) { + val builder = ImmutableList.builder>() + var linkedText: String? = null + var appText: String? = null + if (setMentions(media)) builder.add(Pair(R.id.mentions, R.string.story_mentions)) + if (setQuiz(media)) builder.add(Pair(R.id.quiz, R.string.story_quiz)) + if (setQuestion(media)) builder.add(Pair(R.id.question, R.string.story_question)) + if (setPoll(media)) builder.add(Pair(R.id.poll, R.string.story_poll)) + if (setSlider(media)) builder.add(Pair(R.id.slider, R.string.story_slider)) + if (setLinkedPost(media)) builder.add(Pair(R.id.viewStoryPost, R.string.view_post)) + if (setStoryCta(media)) { + linkedText = media.linkText + builder.add(Pair(R.id.swipeUp, 0)) + } + if (setStoryAppAttribution(media)) { + appText = media.storyAppAttribution!!.appActionText + builder.add(Pair(R.id.spotify, 0)) + } + options.postValue(Triple(builder.build(), linkedText, appText)) + } + + private fun setMentions(media: StoryMedia): Boolean { + val mentions: MutableList> = mutableListOf() + if (media.reelMentions != null) + mentions.addAll(media.reelMentions.map{ + Triple("@" + it.user?.username, it.user?.username, FavoriteType.USER) + }) + if (media.storyHashtags != null) + mentions.addAll(media.storyHashtags.map{ + Triple("#" + it.hashtag?.name, it.hashtag?.name, FavoriteType.HASHTAG) + }) + if (media.storyLocations != null) + mentions.addAll(media.storyLocations.map{ + Triple(it.location?.name ?: "", it.location?.pk?.toString(10), FavoriteType.LOCATION) + }) + reelMentions.postValue(mentions.filterNot { it.second.isNullOrEmpty() } .distinct()) + return !mentions.isEmpty() + } + + private fun setPoll(media: StoryMedia): Boolean { + poll.postValue(media.storyPolls?.get(0)?.pollSticker ?: return false) + return true + } + + private fun setQuiz(media: StoryMedia): Boolean { + quiz.postValue(media.storyQuizs?.get(0)?.quizSticker ?: return false) + return true + } + + private fun setQuestion(media: StoryMedia): Boolean { + val questionSticker = media.storyQuestions?.get(0)?.questionSticker ?: return false + if (questionSticker.questionType.equals("music")) return false + question.postValue(questionSticker) + return true + } + + private fun setSlider(media: StoryMedia): Boolean { + slider.postValue(media.storySliders?.get(0)?.sliderSticker ?: return false) + return true + } + + private fun setLinkedPost(media: StoryMedia): Boolean { + linkedPost.postValue(media.storyFeedMedia?.get(0)?.mediaId ?: return false) + return true + } + + private fun setStoryCta(media: StoryMedia): Boolean { + val webUri = media.storyCta?.get(0)?.links?.get(0)?.webUri ?: return false + val parsedUri = Uri.parse(webUri) + val cleanUri = if (parsedUri.host.equals("l.instagram.com")) parsedUri.getQueryParameter("u") + else null + swipeUp.postValue(if (cleanUri != null && Uri.parse(cleanUri).scheme?.startsWith("http") == true) cleanUri + else webUri) + return true + } + + private fun setStoryAppAttribution(media: StoryMedia): Boolean { + appAttribution.postValue(media.storyAppAttribution ?: return false) + return true + } + + /* get functions */ + + fun getCurrentStory(): LiveData { + return currentStory + } + + fun getCurrentIndex(): LiveData { + return currentIndex + } + + fun getCurrentMedia(): LiveData { + return currentMedia + } + + fun getPagination(): LiveData { + return pagination + } + + fun getDate(): LiveData { + return date + } + + fun getTitle(): LiveData { + return storyTitle + } + + fun getType(): LiveData { + return type + } + + fun getMedia(): LiveData { + return currentMedia + } + + fun getMention(index: Int): Triple? { + return reelMentions.value?.get(index) + } + + fun getMentionTexts(): Array { + return reelMentions.value!!.map { it.first } .toTypedArray() + } + + fun getPoll(): LiveData { + return poll + } + + fun getQuestion(): LiveData { + return question + } + + fun getQuiz(): LiveData { + return quiz + } + + fun getSlider(): LiveData { + return slider + } + + fun getLinkedPost(): LiveData> { + val data = MutableLiveData>() + data.postValue(loading(null)) + val postId = linkedPost.value + if (postId == null) data.postValue(error("No post ID supplied", null)) + else viewModelScope.launch(Dispatchers.IO) { + try { + val media = mediaRepository.fetch(postId.toLong()) + data.postValue(success(media)) + } + catch (e: Exception) { + data.postValue(error(e.message, null)) + } + } + return data + } + + fun getSwipeUp(): String? { + return swipeUp.value + } + + fun getAppAttribution(): String? { + return appAttribution.value?.url + } + + fun getOptions(): LiveData>, String?, String?>> { + return options + } + + /* action functions */ + + fun answerPoll(w: Int): LiveData> { + val data = MutableLiveData>() + data.postValue(loading(null)) + viewModelScope.launch(Dispatchers.IO) { + try { + val oldPoll: PollSticker = poll.value!! + val response = storiesRepository.respondToPoll( + csrfToken!!, + userId, + deviceId, + currentMedia.value!!.pk, + oldPoll.pollId, + w + ) + if (!"ok".equals(response.status)) + throw Exception("Instagram returned status \"" + response.status + "\"") + val tally = oldPoll.tallies.get(w) + val newTally = tally.copy(count = tally.count + 1) + val newTallies = oldPoll.tallies.toMutableList() + newTallies.set(w, newTally) + poll.postValue(oldPoll.copy(viewerVote = w, tallies = newTallies.toList())) + data.postValue(success(null)) + } + catch (e: Exception) { + data.postValue(error(e.message, null)) + } + } + return data + } + + fun answerQuiz(w: Int): LiveData> { + val data = MutableLiveData>() + data.postValue(loading(null)) + viewModelScope.launch(Dispatchers.IO) { + try { + val oldQuiz = quiz.value!! + val response = storiesRepository.respondToQuiz( + csrfToken!!, + userId, + deviceId, + currentMedia.value!!.pk, + oldQuiz.quizId, + w + ) + if (!"ok".equals(response.status)) + throw Exception("Instagram returned status \"" + response.status + "\"") + val tally = oldQuiz.tallies.get(w) + val newTally = tally.copy(count = tally.count + 1) + val newTallies = oldQuiz.tallies.toMutableList() + newTallies.set(w, newTally) + quiz.postValue(oldQuiz.copy(viewerAnswer = w, tallies = newTallies.toList())) + data.postValue(success(null)) + } + catch (e: Exception) { + data.postValue(error(e.message, null)) + } + } + return data + } + + fun answerQuestion(a: String): LiveData> { + val data = MutableLiveData>() + data.postValue(loading(null)) + viewModelScope.launch(Dispatchers.IO) { + try { + val response = storiesRepository.respondToQuestion( + csrfToken!!, + userId, + deviceId, + currentMedia.value!!.pk, + question.value!!.questionId, + a + ) + if (!"ok".equals(response.status)) + throw Exception("Instagram returned status \"" + response.status + "\"") + data.postValue(success(null)) + } + catch (e: Exception) { + data.postValue(error(e.message, null)) + } + } + return data + } + + fun answerSlider(a: Double): LiveData> { + val data = MutableLiveData>() + data.postValue(loading(null)) + viewModelScope.launch(Dispatchers.IO) { + try { + val oldSlider = slider.value!! + val response = storiesRepository.respondToSlider( + csrfToken!!, + userId, + deviceId, + currentMedia.value!!.pk, + oldSlider.sliderId, + a + ) + if (!"ok".equals(response.status)) + throw Exception("Instagram returned status \"" + response.status + "\"") + val newVoteCount = (oldSlider.sliderVoteCount ?: 0) + 1 + val newAverage = if (oldSlider.sliderVoteAverage == null) a + else (oldSlider.sliderVoteAverage * oldSlider.sliderVoteCount!! + a) / newVoteCount + slider.postValue(oldSlider.copy(viewerCanVote = false, + sliderVoteCount = newVoteCount, + viewerVote = a, + sliderVoteAverage = newAverage)) + data.postValue(success(null)) + } + catch (e: Exception) { + data.postValue(error(e.message, null)) + } + } + return data + } + + fun reply(a: String): LiveData>? { + if (messageManager == null) { + messageManager = DirectMessagesManager + } + return messageManager?.replyToStory( + currentStory.value?.user?.pk, + currentStory.value?.id, + currentMedia.value?.id, + a, + viewModelScope + ) + } + + fun shareDm(result: RankedRecipient) { + if (messageManager == null) { + messageManager = DirectMessagesManager + } + val mediaId = currentMedia.value?.id ?: return + val reelId = currentStory.value?.id ?: return + messageManager?.sendMedia(result, mediaId, reelId, BroadcastItemType.STORY, viewModelScope) + } + + fun shareDm(recipients: Set) { + if (messageManager == null) { + messageManager = DirectMessagesManager + } + val mediaId = currentMedia.value?.id ?: return + val reelId = currentStory.value?.id ?: return + messageManager?.sendMedia(recipients, mediaId, reelId, BroadcastItemType.STORY, viewModelScope) + } + + fun paginate(backward: Boolean) { + var index = currentIndex.value!! + index = if (backward) index - 1 else index + 1 + if (index < 0 || index >= currentStory.value!!.items!!.size) skip(backward) + setMedia(index) + } + + fun skip(backward: Boolean) { + pagination.postValue(if (backward) StoryPaginationType.BACKWARD else StoryPaginationType.FORWARD) + } + + fun fetchStory(fetchOptions: StoryViewerOptions?): LiveData> { + val data = MutableLiveData>() + data.postValue(loading(null)) + viewModelScope.launch(Dispatchers.IO) { + try { + val story = storiesRepository.getStories(fetchOptions!!) + setStory(story!!) + data.postValue(success(null)) + } catch (e: Exception) { + data.postValue(error(e.message, null)) + } + } + return data + } + + fun fetchSingleMedia(mediaId: Long): LiveData> { + val data = MutableLiveData>() + data.postValue(loading(null)) + viewModelScope.launch(Dispatchers.IO) { + try { + val storyMedia = storiesRepository.fetch(mediaId) + setSingleMedia(storyMedia!!) + data.postValue(success(null)) + } catch (e: Exception) { + data.postValue(error(e.message, null)) + } + } + return data + } +} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/webservices/StoriesRepository.kt b/app/src/main/java/awais/instagrabber/webservices/StoriesRepository.kt index 62c27847..e3713df1 100644 --- a/app/src/main/java/awais/instagrabber/webservices/StoriesRepository.kt +++ b/app/src/main/java/awais/instagrabber/webservices/StoriesRepository.kt @@ -7,7 +7,6 @@ import awais.instagrabber.repositories.responses.stories.ArchiveResponse import awais.instagrabber.repositories.responses.stories.Story import awais.instagrabber.repositories.responses.stories.StoryMedia import awais.instagrabber.repositories.responses.stories.StoryStickerResponse -import awais.instagrabber.utils.TextUtils.isEmpty import awais.instagrabber.utils.Utils import awais.instagrabber.webservices.RetrofitFactory.retrofit import java.util.UUID @@ -60,35 +59,34 @@ open class StoriesRepository(private val service: StoriesService) { "is_in_archive_home" to "true", "include_cover" to "1", ) - if (!isEmpty(maxId)) { + if (!maxId.isNullOrEmpty()) { form["max_id"] = maxId // NOT TESTED } return service.fetchArchive(form) } - open suspend fun getStories(options: StoryViewerOptions): List { + open suspend fun getStories(options: StoryViewerOptions): Story? { return when (options.type) { StoryViewerOptions.Type.HIGHLIGHT, StoryViewerOptions.Type.STORY_ARCHIVE -> { val response = service.getReelsMedia(options.name) - val story: Story? = response.reels?.get(options.name) - story?.items ?: emptyList() + response.reels?.get(options.name) } StoryViewerOptions.Type.USER -> { val response = service.getUserStories(options.id.toString()) - response.reel?.items ?: emptyList() + response.reel } // should not reach beyond this point StoryViewerOptions.Type.LOCATION -> { val response = service.getStories("locations", options.id.toString()) - response.story?.items ?: emptyList() + response.story } StoryViewerOptions.Type.HASHTAG -> { val response = service.getStories("tags", options.name) - response.story?.items ?: emptyList() + response.story } - else -> emptyList() + else -> null } } @@ -96,7 +94,7 @@ open class StoriesRepository(private val service: StoriesService) { csrfToken: String, userId: Long, deviceUuid: String, - storyId: String, + storyId: Long, stickerId: Long, action: String, arg1: String, @@ -119,7 +117,7 @@ open class StoriesRepository(private val service: StoriesService) { csrfToken: String, userId: Long, deviceUuid: String, - storyId: String, + storyId: Long, stickerId: Long, answer: String, ): StoryStickerResponse = respondToSticker(csrfToken, userId, deviceUuid, storyId, stickerId, "story_question_response", "response", answer) @@ -128,7 +126,7 @@ open class StoriesRepository(private val service: StoriesService) { csrfToken: String, userId: Long, deviceUuid: String, - storyId: String, + storyId: Long, stickerId: Long, answer: Int, ): StoryStickerResponse { @@ -139,7 +137,7 @@ open class StoriesRepository(private val service: StoriesService) { csrfToken: String, userId: Long, deviceUuid: String, - storyId: String, + storyId: Long, stickerId: Long, answer: Int, ): StoryStickerResponse = respondToSticker(csrfToken, userId, deviceUuid, storyId, stickerId, "story_poll_vote", "vote", answer.toString()) @@ -148,7 +146,7 @@ open class StoriesRepository(private val service: StoriesService) { csrfToken: String, userId: Long, deviceUuid: String, - storyId: String, + storyId: Long, stickerId: Long, answer: Double, ): StoryStickerResponse = respondToSticker(csrfToken, userId, deviceUuid, storyId, stickerId, "story_slider_vote", "vote", answer.toString()) diff --git a/app/src/main/res/drawable/ic_story_sticker.xml b/app/src/main/res/drawable/ic_story_sticker.xml new file mode 100644 index 00000000..a9079fe0 --- /dev/null +++ b/app/src/main/res/drawable/ic_story_sticker.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_story_viewer_list.xml b/app/src/main/res/drawable/ic_story_viewer_list.xml new file mode 100644 index 00000000..c0450237 --- /dev/null +++ b/app/src/main/res/drawable/ic_story_viewer_list.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/fragment_story_viewer.xml b/app/src/main/res/layout/fragment_story_viewer.xml index cb6b66ed..bbce26f2 100644 --- a/app/src/main/res/layout/fragment_story_viewer.xml +++ b/app/src/main/res/layout/fragment_story_viewer.xml @@ -9,7 +9,7 @@ android:id="@+id/story_container" android:layout_width="match_parent" android:layout_height="0dp" - app:layout_constraintBottom_toTopOf="@id/postActions" + app:layout_constraintBottom_toTopOf="@id/buttons_barrier" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> @@ -38,132 +38,150 @@ - + app:layout_constraintStart_toStartOf="parent" /> - - - - - - - - - - - - - - - + + app:layout_constraintTop_toBottomOf="@id/buttons_barrier" + app:rippleColor="@color/grey_300" /> - + + + + + + + + + app:layout_constraintStart_toEndOf="@id/btnDownload" + app:layout_constraintTop_toBottomOf="@id/buttons_barrier" + app:rippleColor="@color/grey_300" /> + app:layout_constraintStart_toEndOf="@id/btnReply" + app:layout_constraintTop_toBottomOf="@id/buttons_barrier" + app:rippleColor="@color/grey_300" /> \ No newline at end of file diff --git a/app/src/main/res/menu/story_menu.xml b/app/src/main/res/menu/story_menu.xml index 8169fd62..e7872ad5 100644 --- a/app/src/main/res/menu/story_menu.xml +++ b/app/src/main/res/menu/story_menu.xml @@ -2,21 +2,9 @@ - - \ No newline at end of file diff --git a/app/src/main/res/navigation/direct_messages_nav_graph.xml b/app/src/main/res/navigation/direct_messages_nav_graph.xml index dce30b6a..547ea1ad 100644 --- a/app/src/main/res/navigation/direct_messages_nav_graph.xml +++ b/app/src/main/res/navigation/direct_messages_nav_graph.xml @@ -188,7 +188,6 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c74b7e82..5b17f849 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -69,8 +69,7 @@ Be patient! View Post View Post - Spotify - Vote + Poll Vote successful! You have already voted! Respond @@ -87,6 +86,7 @@ Slider You have already answered! Mentions + Question This Account is Private You won\'t be able to access posts after unfollowing! Are you sure? Are you sure? @@ -104,7 +104,7 @@ Delete collection Are you sure you want to delete this collection? All contained media will remain in other collections. - Add to collection... + Add to collection… Remove from collection Liked Saved @@ -183,9 +183,9 @@ Screenshotted Cannot deliver Unseen count response is null! - Message... + Message… Press and hold to record audio - Updating... + Updating… Leave chat Leave this chat? Kick @@ -333,7 +333,7 @@ Comment Layout Feed stories - Opening post... + Opening post… Share Layout style Column count @@ -511,7 +511,7 @@ Click to show full like count No profile pic found! Are you sure you want to open this link? - Sending... + Sending… Share via DM Share link… Slide to Cancel From dd8fb8fadc164ed7e74e0b7896eb724f3522ee5a Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Tue, 6 Jul 2021 10:03:44 -0400 Subject: [PATCH 05/14] New Crowdin updates (#1464) * New translations strings.xml (Portuguese, Brazilian) * New translations strings.xml (Russian) * New translations strings.xml (Dutch) * New translations strings.xml (Macedonian) * New translations strings.xml (Macedonian) * New translations strings.xml (Macedonian) * New translations strings.xml (Czech) * New translations strings.xml (German) * New translations strings.xml (Polish) * New translations strings.xml (Japanese) * New translations strings.xml (Indonesian) * New translations strings.xml (Portuguese, Brazilian) * New translations strings.xml (Vietnamese) * New translations strings.xml (Chinese Traditional) * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Turkish) * New translations strings.xml (Swedish) * New translations strings.xml (Slovak) * New translations strings.xml (Dutch) * New translations strings.xml (Korean) * New translations strings.xml (Italian) * New translations strings.xml (German) * New translations strings.xml (Basque) * New translations strings.xml (Greek) * New translations strings.xml (Czech) * New translations strings.xml (Catalan) * New translations strings.xml (Spanish) * New translations strings.xml (French) * New translations strings.xml (Macedonian) * New translations strings.xml (Russian) * New translations strings.xml (Arabic) * New translations strings.xml (Hindi) * New translations strings.xml (Persian) * New translations strings.xml (Odia) * Update source file strings.xml * New translations strings.xml (Polish) * New translations strings.xml (Polish) * New translations strings.xml (Spanish) * New translations strings.xml (Portuguese, Brazilian) * New translations strings.xml (Czech) * New translations strings.xml (Czech) * New translations strings.xml (Greek) * New translations strings.xml (Macedonian) * New translations strings.xml (Korean) * New translations strings.xml (Italian) * New translations strings.xml (Italian) * New translations strings.xml (Polish) * New translations strings.xml (Japanese) * New translations strings.xml (Indonesian) * New translations strings.xml (Portuguese, Brazilian) * New translations strings.xml (Vietnamese) * New translations strings.xml (Chinese Traditional) * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Turkish) * New translations strings.xml (Swedish) * New translations strings.xml (Slovak) * New translations strings.xml (Dutch) * New translations strings.xml (Korean) * New translations strings.xml (Italian) * New translations strings.xml (German) * New translations strings.xml (Basque) * New translations strings.xml (Greek) * New translations strings.xml (Czech) * New translations strings.xml (Catalan) * New translations strings.xml (Spanish) * New translations strings.xml (French) * New translations strings.xml (Macedonian) * New translations strings.xml (Russian) * New translations strings.xml (Arabic) * New translations strings.xml (Hindi) * New translations strings.xml (Persian) * New translations strings.xml (Odia) * Update source file strings.xml * New translations strings.xml (Spanish) * New translations strings.xml (Czech) * New translations strings.xml (Portuguese, Brazilian) * New translations strings.xml (Polish) * New translations strings.xml (Arabic) * Update source file strings.xml * New translations strings.xml (Polish) * New translations strings.xml (Japanese) * New translations strings.xml (Indonesian) * New translations strings.xml (Portuguese, Brazilian) * New translations strings.xml (Vietnamese) * New translations strings.xml (Chinese Traditional) * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Turkish) * New translations strings.xml (Swedish) * New translations strings.xml (Slovak) * New translations strings.xml (Dutch) * New translations strings.xml (Korean) * New translations strings.xml (Italian) * New translations strings.xml (German) * New translations strings.xml (Basque) * New translations strings.xml (Greek) * New translations strings.xml (Czech) * New translations strings.xml (Catalan) * New translations strings.xml (Spanish) * New translations strings.xml (French) * New translations strings.xml (Macedonian) * New translations strings.xml (Russian) * New translations strings.xml (Arabic) * New translations strings.xml (Hindi) * New translations strings.xml (Persian) * New translations strings.xml (Odia) * New translations strings.xml (Spanish) * New translations strings.xml (Basque) * New translations strings.xml (Basque) * New translations strings.xml (Portuguese, Brazilian) * New translations strings.xml (French) * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Polish) * New translations strings.xml (Macedonian) * New translations strings.xml (Greek) * New translations strings.xml (Greek) * New translations strings.xml (Polish) * New translations strings.xml (Japanese) * New translations strings.xml (Indonesian) * New translations strings.xml (Portuguese, Brazilian) * New translations strings.xml (Vietnamese) * New translations strings.xml (Chinese Traditional) * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Turkish) * New translations strings.xml (Swedish) * New translations strings.xml (Slovak) * New translations strings.xml (Dutch) * New translations strings.xml (Korean) * New translations strings.xml (Italian) * New translations strings.xml (German) * New translations strings.xml (Basque) * New translations strings.xml (Greek) * New translations strings.xml (Czech) * New translations strings.xml (Catalan) * New translations strings.xml (Spanish) * New translations strings.xml (French) * New translations strings.xml (Macedonian) * New translations strings.xml (Russian) * New translations strings.xml (Arabic) * New translations strings.xml (Hindi) * New translations strings.xml (Persian) * New translations strings.xml (Odia) * Update source file strings.xml * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Portuguese, Brazilian) * New translations strings.xml (Greek) * New translations strings.xml (Spanish) * New translations strings.xml (Odia) * New translations arrays.xml (Odia) * New translations strings.xml (Odia) * New translations strings.xml (Odia) * New translations arrays.xml (Hindi) * New translations strings.xml (Czech) * New translations strings.xml (Polish) * New translations strings.xml (Italian) * New translations strings.xml (German) * New translations strings.xml (Basque) * New translations strings.xml (Basque) * New translations strings.xml (Odia) * New translations strings.xml (Odia) * New translations strings.xml (Odia) * New translations arrays.xml (Hindi) * New translations arrays.xml (Hindi) * New translations strings.xml (Hindi) * New translations arrays.xml (Hindi) * New translations strings.xml (Hindi) * New translations strings.xml (Hindi) * New translations strings.xml (Odia) * New translations strings.xml (Polish) * New translations strings.xml (Japanese) * New translations strings.xml (Indonesian) * New translations strings.xml (Portuguese, Brazilian) * New translations strings.xml (Vietnamese) * New translations strings.xml (Chinese Traditional) * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Turkish) * New translations strings.xml (Swedish) * New translations strings.xml (Slovak) * New translations strings.xml (Dutch) * New translations strings.xml (Korean) * New translations strings.xml (Italian) * New translations strings.xml (German) * New translations strings.xml (Basque) * New translations strings.xml (Greek) * New translations strings.xml (Czech) * New translations strings.xml (Catalan) * New translations strings.xml (Spanish) * New translations strings.xml (French) * New translations strings.xml (Macedonian) * New translations strings.xml (Russian) * New translations strings.xml (Arabic) * New translations strings.xml (Hindi) * New translations strings.xml (Persian) * New translations strings.xml (Odia) * Update source file strings.xml * New translations strings.xml (Spanish) * New translations strings.xml (Greek) * New translations strings.xml (Czech) * New translations strings.xml (Portuguese, Brazilian) * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Chinese Simplified) --- app/src/github/res/values-mk/strings.xml | 6 +- app/src/github/res/values-or/strings.xml | 6 +- app/src/main/res/values-ar/strings.xml | 95 ++++----- app/src/main/res/values-ca/strings.xml | 19 +- app/src/main/res/values-cs/strings.xml | 25 +-- app/src/main/res/values-de/strings.xml | 19 +- app/src/main/res/values-el/strings.xml | 45 +++-- app/src/main/res/values-es/strings.xml | 19 +- app/src/main/res/values-eu/strings.xml | 171 ++++++++-------- app/src/main/res/values-fa/strings.xml | 19 +- app/src/main/res/values-fr/strings.xml | 21 +- app/src/main/res/values-hi/arrays.xml | 14 +- app/src/main/res/values-hi/strings.xml | 220 +++++++++++---------- app/src/main/res/values-in/strings.xml | 19 +- app/src/main/res/values-it/strings.xml | 21 +- app/src/main/res/values-ja/strings.xml | 19 +- app/src/main/res/values-ko/strings.xml | 23 ++- app/src/main/res/values-mk/strings.xml | 107 +++++----- app/src/main/res/values-nl/strings.xml | 67 ++++--- app/src/main/res/values-or/arrays.xml | 10 +- app/src/main/res/values-or/strings.xml | 73 +++---- app/src/main/res/values-pl/strings.xml | 21 +- app/src/main/res/values-pt/strings.xml | 19 +- app/src/main/res/values-ru/strings.xml | 31 +-- app/src/main/res/values-sk/strings.xml | 19 +- app/src/main/res/values-sv/strings.xml | 19 +- app/src/main/res/values-tr/strings.xml | 19 +- app/src/main/res/values-vi/strings.xml | 19 +- app/src/main/res/values-zh-rCN/strings.xml | 19 +- app/src/main/res/values-zh-rTW/strings.xml | 19 +- 30 files changed, 641 insertions(+), 562 deletions(-) diff --git a/app/src/github/res/values-mk/strings.xml b/app/src/github/res/values-mk/strings.xml index 078fa6a8..1ef096e9 100644 --- a/app/src/github/res/values-mk/strings.xml +++ b/app/src/github/res/values-mk/strings.xml @@ -1,6 +1,6 @@ - Enable Sentry - Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io - Sentry will start on next launch + Овозможи Sentry + Sentry е слушач на грешки кој асинхроно ги испраќа на Sentry.io страната + Sentry ќе биде овозможен на следно отварање diff --git a/app/src/github/res/values-or/strings.xml b/app/src/github/res/values-or/strings.xml index 078fa6a8..173bf73c 100644 --- a/app/src/github/res/values-or/strings.xml +++ b/app/src/github/res/values-or/strings.xml @@ -1,6 +1,6 @@ - Enable Sentry - Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io - Sentry will start on next launch + Sentryକୁ ସକ୍ଷମ କରନ୍ତୁ + ତ୍ରୁଟି ପାଇଁ ସେଣ୍ଟ୍ରି ହେଉଛି ଏକ ଶ୍ରୋତା ଯାହା ତ୍ରୁଟି / ଘଟଣାକୁ Sentry.io କୁ ପଠାଏ | + ପରବର୍ତ୍ତୀ ଲଞ୍ଚଠାରୁ ସେଣ୍ଟ୍ରି ଆରମ୍ଭ ହେବ | diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index cccace7d..6561c224 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -19,6 +19,7 @@ المفضلة إكتشف التعليقات + الردود الأنشطة التحقق من التحديثات عند بدء التشغيل منع لقطات الشاشة & معاينة التطبيق @@ -29,7 +30,7 @@ إخفاء القصص المكتومة من الخلاصة تحديد DM كما هو بعد المشاهدة الأعضاء الآخرون سيعلمون أنك شاهدتها - Autoplay video stories + تشغيل تلقائي لقصص الفيديو تمكين تنبيهات النشاط تصنيف قصص التغذية خطأ في تحميل الملف الشخصي! هل اسم المستخدم صحيح؟ إذا كان الأمر كذلك، قد تكون مقيدا. @@ -65,7 +66,7 @@ %s\nFollowers %s\nFollowers - %s\nFollowing + %s\nمتابع تشغيل مقاطع الفيديو تلقائياً متابعة الفيديوهات في الخلفية لا تقم بإيقاف الفيديوهات مؤقتاً عندما يكون التطبيق خارج نطاق التركيز @@ -79,8 +80,7 @@ كن صبورا! عرض المنشور عرض المشاركة - Spotify - صوت + Poll تم التصويت بنجاح! لقد قمت بالتصويت بالفعل! رد @@ -101,8 +101,10 @@ شريط منزلق لقد قمت بالرد مسبقا! الإشارات + Question هذا الحساب خاص لن تتمكن من الوصول إلى المشاركات بعد إلغاء المتابعة! هل أنت متأكد؟ + Are you sure? يمكنك تسجيل الدخول عبر المزيد -> الحساب على الزاوية اليمنى السفلى أو يمكنك عرض الحسابات العامة بدون تسجيل الدخول! هذا الحساب لا يتضمن مشاركات لا توجد مثل هذه المشاركات! @@ -117,50 +119,50 @@ حذف المجموعة هل أنت متأكد من أنك تريد حذف هذه المجموعة؟ وستظل جميع الوسائط المحتوية في مجموعات أخرى. - إضافة إلى المجموعة... + Add to collection… إزالة من المجموعة أعجبني تم الحفظ مشار إليها الرسالة - Bookmark - Follow - Unfollow - Favorite - Block - Unblock - Restrict - Unrestrict - Mute stories - Mute posts - Unmute stories - Unmute posts - Remove follower - Copy bio + علامة مرجعية + تابع + إلغاء المتابعة + المفضلة + حظر + إلغاء الحظر + تقييد + إلغاء القيود + كتم القصص + كتم المشاركات + إلغاء كتم القصص + إلغاء كتم المنشورات + إزالة متابع + نسخ البيانات Translate bio Mutual - Following - Follower - Map - Accounts - Settings - Favorites - Successfully imported! - Failed to import! - Successfully exported! - Failed to export! - Refresh - Get cookies - Use custom format - Separator - Time Format - Date Format - Preview - Swap Time and Date positions - Cannot delete currently in use account - Are you sure you want to delete \'%s\'? - Open profile - View story + متابع + المتابعون + خريطه + الحسابات + الإعدادات + المفضلة + تمّ الإستيراد بنجاح! + فشل الاستيراد! + تمّ التصدير بنجاح! + فشل التصدير! + إعادة التحميل + الحصول على ملفات تعريف الارتباط + استخدام صيغة مخصصة + فاصل + تنسيق الوقت + تنسيق التاريخ + المعاينة + مبادلة اماكن الوقت والتاريخ + لا يمكن حذف الحساب المستخدم حاليا + هل أنت متأكد من أنك تريد حذف \"%s\"؟ + فتح ملف شخصي + عرض القصة View profile picture Unsupported message type Unsend message @@ -196,9 +198,9 @@ Screenshotted Cannot deliver Unseen count response is null! - Message... + Message… Press and hold to record audio - Updating... + Updating… Leave chat Leave this chat? Kick @@ -210,6 +212,7 @@ Downloads posts directly to the phone! Fetching post(s) Download completed + Preparing to download… Downloading post… Downloading media Downloading profile picture @@ -334,7 +337,7 @@ Comment Layout Feed stories - Opening post... + Opening post… Share Layout style Column count @@ -522,7 +525,7 @@ The previously selected folder does not exist now: Re-select the directory or select a new directory by clicking the button below. No folder selected! - Please choose a directory from your storage, not a category on the sidebar. + Please choose a directory from your storage, not a category on the sidebar.\n(%s) Success! Please wait. Starting app… Barinsta folder أعلى @@ -532,7 +535,7 @@ انقر لعرض العدد الكلي للأعجابات لم يتم العثور على صورة الملف الشخصي! هل أنت متأكد من أنك تريد فتح هذا الرابط؟ - جار الإرسال... + Sending… مشاركة عبر DM مشاركة الرابط… Slide to Cancel diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 9fb0f75c..8bf7a8fe 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -19,6 +19,7 @@ Preferits Descobrir Comentaris + Replies Activitat Cerca actualitzacions a l\'inici Bloca les captures de pantalla & previsualització de l\'aplicació @@ -67,8 +68,7 @@ Sigues pacient! Mostrar l\'entrada Mostrar l\'entrada - Spotify - Votar + Poll Has votat amb èxit! Ja has votat! Contestar @@ -85,8 +85,10 @@ Desplaçador Ja has contestat! Mencions + Question Aquest compte és privat Després de deixar de seguir, no podràs accedir a les publicacions. Estàs segur? + Are you sure? Pots iniciar la sessió a través de Més ->; El teu compte és a l\'extrem inferior dret o pots veure comptes públics sense iniciar una sessió! Aquest compte no té publicacions No existeixen publicacions d\'aquest tipus! @@ -101,7 +103,7 @@ Suprimeix la col·lecció Estàs segur de voler suprimir aquesta col·lecció? Tots els mitjans continguts romandran en altres col·leccions. - Afegir a la coŀlecció... + Add to collection… Eliminar de la coŀlecció M\'agrada Desat @@ -180,9 +182,9 @@ Captura de pantalla feta No es pot lliurar La resposta de recompte no vista és nul·la! - Missatge... + Message… Premeu i mantingueu premut per enregistrar l\'àudio - S\'està actualitzant... + Updating… Deixar el xat Vols sortir d\'aquest xat? Expulsar @@ -194,6 +196,7 @@ Descarregar publicacions directament al telèfon! S\'estan recuperant les noves publicacions S\'ha completat la descàrrega + Preparing to download… Descarregant publicació… Descarregant multimèdia Descarregant la imatge de perfil @@ -318,7 +321,7 @@ Comentar Disposició Fil d\'històries - Obrint publicació... + Opening post… Compartir Estil de disposició Nombre de columnes @@ -486,7 +489,7 @@ The previously selected folder does not exist now: Re-select the directory or select a new directory by clicking the button below. No folder selected! - Please choose a directory from your storage, not a category on the sidebar. + Please choose a directory from your storage, not a category on the sidebar.\n(%s) Success! Please wait. Starting app… Barinsta folder Part superior @@ -496,7 +499,7 @@ Feu clic per mostrar el recompte complet de m\'agrada No s\'ha trobat cap imatge de perfil! Esteu segur que voleu obrir aquest enllaç? - Enviant... + Sending… Comparteix via MD Comparteix l\'enllaç... Slide to Cancel diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index bc7b3db6..be7f21b0 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -19,6 +19,7 @@ Oblíbené Objevit Komentáře + Odpovědi Aktivita Zkontrolovat aktualizace při spuštění Blokovat snímky obrazovky a náhled aplikace @@ -73,8 +74,7 @@ Buďte trpěliví! Zobrazit příspěvek Zobrazit příspěvek - Spotify - Hlasovat + Hlasování Hlasování úspěšné! Již jste hlasovali! Odpovědět @@ -93,8 +93,10 @@ Posuvník Už jste odpověděli! Zmínky + Otázka Tento účet je soukromý Po zrušení sledování nebudete mít přístup k příspěvkům! Jste si jisti? + Určitě to chcete udělat? Můžete se přihlásit přes Více -> Účet v pravém dolním rohu nebo můžete zobrazit veřejné účty bez přihlášení! Tento účet nezveřejnil žádné příspěvky Žádné takové příspěvky! @@ -109,7 +111,7 @@ Odstranit sbírku Opravdu chcete tuto sbírku odstranit? Všechna uložená média zůstanou v jiných sbírkách. - Přidat do sbírky... + Přidat do sbírky… Odebrat ze sbírky Líbí se Uložené @@ -188,9 +190,9 @@ Udělán snímek obrazovky Nelze doručit Unseen count response is null! - Zpráva... + Zpráva… Stiskněte a podržte pro nahrávání - Aktualizace... + Aktualizace… Opustit chat Chcete opustit tento chat? Vyhodit @@ -202,6 +204,7 @@ Stahuje příspěvky přímo do telefonu! Načítání příspěvků Stahování bylo dokončeno + Připravuje se stahování… Stahování příspěvku… Stahování médií Stahování profilového obrázku @@ -294,8 +297,8 @@ Odebráno z Oblíbených! Záloha a obnovení Automatické zálohování - Starting from Android 6, Android\'s Auto Backup feature will upload all app settings, account login data, and favorites onto your Google Drive, which can be restored by reinstalling the app after uninstallation. - This preference has no effect if Google Play Services is not present, or if Auto Backup is disabled from your device settings. Disabling here does not erase existing backups. + Od Android 6 bude funkce zálohování Androidu automaticky nahrávat všechna nastavení aplikace, přihlašovací údaje a oblíbené na Disk Google. Tato záloha může být obnovena po opětovném nainstalování aplikace. + Toto nastavení nemá žádný účinek, pokud nejsou přítomny služby Google Play nebo pokud je automatické zálohování zakázáno v nastavení zařízení. Zakázání zde nevymaže existující zálohy. Povolit automatické zálohování Ruční zálohování Zálohujte nastavení aplikace Barinsta, přihlašovací údaje a/nebo oblíbené položky do prostého textu nebo šifrovaného souboru pro pozdější obnovu. @@ -326,7 +329,7 @@ Komentovat Rozložení Kanál příběhů - Otevírání příspěvku... + Otevírání příspěvku… Sdílet Styl rozložení Počet sloupců @@ -504,17 +507,17 @@ Dříve vybraná složka už neexistuje: Znovu vyberte adresář nebo vyberte nový adresář kliknutím na tlačítko níže. Není vybrána žádná složka! - Vyberte prosím adresář z vašeho úložiště, nikoli kategorii na postranním panelu. + Vyberte prosím adresář z vašeho úložiště, ne kategorii na postranním panelu.\n(%s) Úspěch! Počkejte prosím. Spouštění aplikace… Složka Barinsta - Populární + Nejlepší Nedávné Vymazat Nemáte žádnou aplikaci pro mapy! Klepnutím zobrazíte přesný počet \"to se mi líbí\" Nebyl nalezen žádný obrázek profilu! Opravdu chcete otevřít tento odkaz? - Odesílání... + Odesílání… Sdílet do zprávy Sdílet odkaz… Posunutím zrušíte diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 93f78af7..4b602693 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -19,6 +19,7 @@ Favoriten Entdecken Kommentare + Antworten Aktivität Beim Start auf Updates prüfen Bildschirmfotos & App-Vorschau verhindern @@ -67,8 +68,7 @@ Sei etwas geduldig! Beitrag anzeigen Beitrag anzeigen - Spotify - Abstimmung + Poll Abstimmung erfolgreich! Du hast bereits abgestimmt! Antworten @@ -85,8 +85,10 @@ Schieberegler Du hast bereits geantwortet! Erwähnungen + Question Dieser Account ist privat Du wirst nicht mehr auf Beiträge zugreifen können, wenn du entfolgst! Bist du sicher? + Bist du sicher? Du kannst dich unten rechts über Mehr -> anmelden, oder öffentliche Konten ohne Anmeldung ansehen! Dieses Konto hat keine Beiträge Keine derartigen Posts! @@ -101,7 +103,7 @@ Sammlung löschen Bist du sicher, dass du diese Sammlung löschen möchtest? Alle enthaltenen Medien werden in anderen Sammlungen verbleiben. - Zu Sammlung hinzufügen... + Add to collection… Aus Sammlung entfernen Gefällt mir Gespeichert @@ -180,9 +182,9 @@ Screenshot erstellt Kann nicht zugestellt werden Ungelesen-Zähler Antwort ist null! - Nachricht... + Message… Für Audioaufnahme drücken und halten - Aktualisieren... + Updating… Chat verlassen Chat verlassen? Entfernen @@ -194,6 +196,7 @@ Lade Posts direkt auf das Handy herunter! Beiträgen abrufen Download abgeschlossen + Download wird vorbereitet… Beitrag wird heruntergeladen… Medien herunterladen Profilbild wird heruntergeladen @@ -318,7 +321,7 @@ Kommentar Layout Storyfeed - Öffne Beitrag... + Opening post… Teilen Layoutstil Spaltenanzahl @@ -486,7 +489,7 @@ Der zuvor ausgewählte Ordner existiert nicht mehr: Wähle das Verzeichnis erneut aus oder wähle ein neues Verzeichnis, indem du unten auf die Schaltfläche klickst. Kein Ordner ausgewählt! - Please choose a directory from your storage, not a category on the sidebar. + Bitte wähle ein Verzeichnis aus deinem Speicher, nicht eine Kategorie in der Seitenleiste.\n(%s) Erfolg! Bitte warten. App wird gestartet… Barinsta-Ordner Anfang @@ -496,7 +499,7 @@ Klicken, um die gesamte Anzahl der Likes anzuzeigen Kein Profilbild gefunden! Bist du sicher, dass du diesen Link öffnen möchtest? - Wird gesendet... + Sending… Über PN teilen Link teilen… Zum Abbrechen wischen diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 55810e73..e05c7782 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -1,7 +1,7 @@ Σχετικά με - Απευθείας Μηνύματα + Μηνύματα Ρυθμίσεις Λήψη Αναζήτηση ονόματος χρήστη… @@ -19,6 +19,7 @@ Αγαπημένα Ανακαλύψτε Σχόλια + Απαντήσεις Δραστηριότητα Έλεγχος για ενημερώσεις κατά την εκκίνηση Παρεμπόδιση στιγμιοτύπων οθόνης & προεπισκόπησης εφαρμογής @@ -29,7 +30,7 @@ Απόκρυψη ιστοριών που βρίσκονται σε σίγαση από τη ροή Επισήμανση μηνυμάτων ως αναγνωσμένων μετά την προβολή Τα υπόλοιπα μέλη θα γνωρίζουν ότι προβλήθηκε - Autoplay video stories + Αυτόματη αναπαραγωγή ιστοριών με βίντεο Ενεργοποίηση ειδοποιήσεων δραστηριότητας Ταξινόμηση ροής ιστορίων Σφάλμα κατά τη φόρτωση προφίλ! Είναι το όνομα χρήστη έγκυρο; Αν ναι, μπορεί να είστε περιορισμένος. @@ -67,8 +68,7 @@ Κάντε υπομονή! Προβολή Δημοσίευσης Προβολή Δημοσίευσης - Spotify - Ψηφίστε + Δημοσκόπηση Η ψήφος ήταν επιτυχής! Έχετε ήδη ψηφίσει! Απάντηση @@ -85,8 +85,10 @@ Slider Έχετε ήδη απαντήσει! Αναφορές + Ερώτηση Ιδιωτικός λογαριασμός Δε θα έχετε πρόσβαση στις δημοσιεύσεις μετά την άρση ακολούθησης! Είστε βέβαιος; + Είστε σίγουρος; Μπορείτε να συνδεθείτε μέσω του Περισσότερα-> Λογαριασμός στην κάτω δεξιά γωνία ή μπορείτε να δείτε δημόσιους λογαριασμούς χωρίς σύνδεση! Αυτός ο λογαριασμός δεν έχει δημοσιεύσεις Δεν Υπάρχουν Τέτοιες Δημοσιεύσεις! @@ -101,7 +103,7 @@ Διαγραφή συλλογής Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτή τη συλλογή; Όλα τα πολυμέσα που περιέχονται θα παραμείνουν σε άλλες συλλογές. - Προσθήκη στη συλλογή... + Προσθήκη στη συλλογή… Αφαίρεση από τη συλλογή Μ\'αρέσουν Αποθηκευμένα @@ -180,9 +182,9 @@ Λήφθηκε στιγμιότυπο οθόνης Αδυναμία παράδοσης Η απόκριση καταμέτρησης μη προβληθέντων είναι άκυρη! - Μήνυμα... + Μήνυμα… Πατήστε παρατεταμένα για εγγραφή ήχου - Ενημέρωση... + Ενημέρωση… Αποχώρηση από τη συνομιλία Αποχώρηση από αυτήν τη συνομιλία; Διώξιμο @@ -194,6 +196,7 @@ Γίνεται λήψη των δημοσιεύσεων απευθείας στο τηλέφωνο! Ανάκτηση δημοσίευσης/-εων Η λήψη ολοκληρώθηκε + Προετοιμασία για λήψη… Γίνεται λήψη της δημοσίευσης… Γίνεται λήψη πολυμέσων Γίνεται λήψη εικόνας προφίλ @@ -285,11 +288,11 @@ Άγνωστο Αφαίρεση από τα αγαπημένα! Αντίγραφα ασφαλείας & Επαναφορά - Auto Backup - Starting from Android 6, Android\'s Auto Backup feature will upload all app settings, account login data, and favorites onto your Google Drive, which can be restored by reinstalling the app after uninstallation. - This preference has no effect if Google Play Services is not present, or if Auto Backup is disabled from your device settings. Disabling here does not erase existing backups. - Enable Auto Backup - Manual Backup + Αυτόματη δημιουργία αντιγράφων ασφαλείας + Από το Android 6 κι έπειτα, το χαρακτηριστικό αυτόματης δημιουργίας αντιγράφων ασφαλείας του Android, θα αναφορτώνει όλες τις ρυθμίσεις της εφαρμογής, τα δεδομένα σύνδεσης του λογαριασμού και τα αγαπημένα στο Google Drive σας. Έτσι, θα μπορούν να επαναφερθούν με την επανεγκατάσταση της εφαρμογής μετά την απεγκατάσταση. + Αυτή η προτίμηση δεν έχει καμία επίδραση εάν οι υπηρεσίες Google Play δεν είναι παρούσες, ή εάν το αυτόματο αντίγραφο ασφαλείας είναι απενεργοποιημένο από τις ρυθμίσεις της συσκευής σας. Η απενεργοποίηση εδώ δεν διαγράφει τα υπάρχοντα αντίγραφα. + Ενεργοποίηση Αυτόματης Δημιουργίας Αντιγράφων Ασφαλείας + Μη αυτόματη δημιουργία αντιγράφων ασφαλείας Δημιουργία αντιγράφου ασφαλείας των ρυθμίσεων της εφαρμογής, των δεδομένων σύνδεσης του λογαριασμού και/ή των αγαπημένων, σε ακρυπτογράφητο ή κρυπτογραφημένο αρχείο για μεταγενέστερη επαναφορά. Αν δημιουργείτε αντίγραφα ασφαλείας των δεδομένων σύνδεσης λογαριασμού, αντιμετωπίστε το αρχείο ως απόρρητο και κρατήστε το σε ασφαλές μέρος! Δημιουργία νέου αρχείου αντιγράφου ασφαλείας @@ -318,7 +321,7 @@ Σχόλιο Διάταξη Ροή ιστοριών - Άνοιγμα δημοσίευσης... + Άνοιγμα δημοσίευσης… Κοινοποίηση Τρόπος διάταξης Πλήθος στηλών @@ -480,13 +483,13 @@ Αντιγραφή απάντησης Ανάκτηση Αντίγραφο ασφαλείας - Select a folder where Barinsta can store downloads and temporary files.\n\nYou can change this later in More > Settings > Downloads. + Επιλέξτε έναν φάκελο όπου εκεί θα αποθηκεύονται οι λήψεις και τα προσωρινά αρχεία του Barinsta.\n\nΗ επιλογή σας μπορεί να αλλαχθεί στο Λοιπά> Ρυθμίσεις> Λήψεις. Το Android έχει αλλάξει τον τρόπο με τον οποίο οι εφαρμογές μπορούν να έχουν πρόσβαση σε αρχεία και καταλόγους στον αποθηκευτικό χώρο. Αυτή τη στιγμή η Barinsta δεν έχει άδεια πρόσβασης στον ακόλουθο φάκελο: - Permissions for the previously selected folder were revoked by the system: - The previously selected folder does not exist now: - Re-select the directory or select a new directory by clicking the button below. + Τα δικαιώματα για τον προηγουμένως επιλεγμένο φάκελο ανακλήθηκαν από το σύστημα: + Ο προηγουμένως επιλεγμένος φάκελος δεν υπάρχει τώρα: + Επιλέξτε ξανά τον φάκελο ή επιλέξτε ένα νέο φάκελο κάνοντας κλικ στο παρακάτω κουμπί. Δεν επιλέχθηκε φάκελος! - Please choose a directory from your storage, not a category on the sidebar. + Παρακαλώ επιλέξτε έναν φάκελο από τον αποθηκευτικό σας χώρο, όχι μια κατηγορία στην πλαϊνή μπάρα.\n(%s) Επιτυχία! Παρακαλώ περιμένετε. Εκκίνηση εφαρμογής… Φάκελος Barinsta Κορυφή @@ -496,8 +499,8 @@ Σε πόσους ακριβώς αρέσει Δε βρέθηκε εικόνα προφίλ! Είστε βέβαιος για το άνοιγμα αυτού του συνδέσμου; - Γίνεται αποστολή... - Κοινοποίηση μέσω + Αποστολή… + Κοινοποίηση μέσω μηνύματος Κοινοποίηση συνδέσμου… - Slide to Cancel + Σύρετε για ακύρωση diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 04e9345a..89e41e58 100755 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -19,6 +19,7 @@ Favoritos Explorar Comentarios + Respuestas Actividad Buscar actualizaciones al inicio Bloquea capturas de pantalla & vista previa de aplicaciones @@ -67,8 +68,7 @@ ¡Sea paciente! Ver publicación Ver publicación - Spotify - Votar + Encuesta ¡Votación exitosa! ¡Ya ha votado! Comentar @@ -85,8 +85,10 @@ Deslizador ¡Ya has respondido! Menciones + Pregunta Esta cuenta es privada ¡No podrás acceder a sus publicaciones después de dejar de seguirle! ¿Estás seguro? + ¿Estás seguro/a? ¡Puedes iniciar sesión a través de Más -> Cuenta en la esquina inferior derecha o puedes ver cuentas públicas sin iniciar sesión! Esta cuenta no tiene publicaciones ¡No existen tales publicaciones! @@ -101,7 +103,7 @@ Eliminar colección ¿Estás seguro de querer borrar esta colección? Todos los medios incluidos permanecerán en otras colecciones. - Añadir a la colección... + Añadir a la colección… Eliminar de la colección Gustado Guardado @@ -180,9 +182,9 @@ Captura de pantalla realizada No se puede entregar ¡Respuesta del contador de no vistos es nulo! - Mensaje... + Mensaje… Pulsa y mantén presionado para grabar audio - Actualizando... + Actualizando… Abandonar chat ¿Salir de este chat? Expulsar @@ -194,6 +196,7 @@ ¡Descarga los mensajes directamente al teléfono! Obteniendo publicación(es) Descarga completada + Preparando para descargar… Descargando publicación… Descargando multimedia Descargando foto de perfil @@ -318,7 +321,7 @@ Comentario Disposición Muro de historias - Abriendo publicación... + Abriendo publicación… Compartir Estilo de disposición Número de columnas @@ -486,7 +489,7 @@ La carpeta seleccionada anteriormente no existe ahora: Vuelva a seleccionar el directorio o seleccione un nuevo directorio haciendo clic en el botón de abajo. ¡Ninguna carpeta seleccionada! - Por favor, elija un directorio de su almacenamiento, no una categoría en la barra lateral. + Por favor, elija un directorio de su almacenamiento, no una categoría en la barra lateral.\n(%s) ¡Éxito! Por favor espere. Iniciando aplicación… Carpeta Barinsta Arriba @@ -496,7 +499,7 @@ Clic para ver el recuento completo de me gustas ¡No se encontró foto de perfil! ¿Está seguro de querer abrir este enlace? - Enviando... + Enviando… Compartir por MD Compartir enlace… Desliza para cancelar diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 4888ab74..d26f7b3b 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -19,6 +19,7 @@ Gogokoak Aurkitu Iruzkinak + Erantzunak Jarduera Bilatu eguneratzeak abioan Blokeatu pantaila-argazkiak eta aplikazioaren aurrebista @@ -29,7 +30,7 @@ Hide muted stories from feed Markatu MZ ikusita gisa ikusi ondoren Beste kideek ikusi duzula jakingo dute - Autoplay video stories + Erreproduzitu automatikoki bideoa diren istorioak Gaitu jarduera-jakinarazpenak Istorioen jarioaren sailkapena Error loading profile! Is the username valid? If so, you may be ratelimited. @@ -42,11 +43,11 @@ Gaia Hizkuntza - %s\nPost + Bidalketa\n%s %s\nBidalketa - %s Post + Bidalketa %s %s Bidalketa @@ -55,7 +56,7 @@ %s\nJarraituak Erreproduzitu bideoak automatikoki - Continue videos in background + Mantendu bideoak bigarren planoan Do not pause videos when the app is out of focus Mututu bideoak beti Erakutsi argazki-oina beti @@ -67,8 +68,7 @@ Itxaron Ikusi bidalketa Ikusi bidalketa - Spotify - Bozkatu + Poll Bozka ongi bidali da! Jada bozkatu duzu! Erantzun @@ -82,11 +82,13 @@ Erantzun istorioa Erantzun… Lehiaketa - Slider + Irristatze-barra Dagoeneko erantzun duzu! Aipamenak + Question Kontu hau pribatua da Ezingo dituzu bidalketak ikusi jarraitzeari utzi ondoren. Ziur zaude? + Ziur zaude? You can log in via More -> Account on the bottom-right corner or you can view public accounts without login! Kontu honek bidalketarik ez du Bidalketarik ez! @@ -96,13 +98,13 @@ Kendu kontu guztiak This will remove all added accounts from the app!\nTo remove just one account, long tap the account from the account switcher dialog.\nDo you want to continue? Data-formatua - Create new collection + Bilduma berria sortu Editatu bildumaren izena Ezabatu bilduma Ziur zaude bilduma hau ezabatu nahi duzula? All contained media will remain in other collections. - Add to collection... - Remove from collection + Add to collection… + Kendu bildumatik Atsegiteak Gordeta Etiketatuta @@ -146,28 +148,28 @@ Ireki profila Ikusi istorioa Ikusi profil-irudia - Unsupported message type + Mezu mota ez da onartzen Ezabatu mezua Ikusi GIPHYn %s shared a post by @%s - %s shared an image - %s shared a video - %s sent a message - %s shared a gif - %s shared a sticker - %s shared a profile: @%s - %s shared a location: %s + %s(e)k irudia partekatu du + %s(e)k bideoa partekatu du + %s(e)k mezua bidali du + %s(e)k gifa partekatu du + %s(e)k eranskina partekatu du + %s(e)k profila partekatu du: @%s + %s(e)k kokalekua partekatu du: %s %s shared a story highlight by @%s - %s shared a story by @%s - %s sent a voice message + %s(e)k @%s(r)en istorioa partekatu du + %s(e)k ahots-mezua bidali du %s shared a clip by @%s - %s shared an IGTV video by @%s - You replied to their story: %s - %s replied to your story: %s - You reacted to their story: %s - %s reacted to your story: %s - You mentioned @%s in your story - %s mentioned you in their story + %s(e)k @%s(r)en IGTV bideoa partekatu du + Bere istorioari erantzun diozu: %s + %s(e)k zure istorioari erantzun dio: %s + Bere istorioari erreakzionatu diozu: %s + %s(e)k zure istorioari erreakzionatu dio: %s + \@%s zure istorioan aipatu duzu + %s(e)k bere istorioan aipatu zaitu Multimedia mota ezezaguna Multimedia iraungi da! Iritsi da @@ -180,9 +182,9 @@ Pantaila-argazkia aterata Ez da iritsi Unseen count response is null! - Mezua... - Press and hold to record audio - Eguneratzen... + Message… + Sakatu eta mantendu audioa grabatzeko + Updating… Utzi txata Utzi txat hau? Bota @@ -194,6 +196,7 @@ Bidalketak telefonora zuzenean deskargatzen ditu Fetching post(s) Deskarga burutu da + Deskargatzeko prestatzen… Bidalketa deskargatzen… Multimedia deskargatzen Profil-irudia deskargatzen @@ -202,7 +205,7 @@ Errorea fitxategia deskargatzean 100 bidalketa deskargatu ditzakezu une berean. Ez izan gutiziatsu! Itzuli iruzkina - Delete comment + Ezabatu iruzkina Erabiltzaile-izena bilatu nahi duzu? Traol-hitza bilatu nahi duzu? Jarraitzaileak @@ -222,7 +225,7 @@ Onartu eskaera Baztertu eskaera Partekatu bidalketa publiko hau… - This is a private post! Share to those who can view it. + Bidalketa pribatua da. Partekatu ikus dezaketen horiekin. Kategoria hau hutsik dago… Eguneratze bat eskuragarri dago (%s) F-Droidetik deskargatu baduzu, bertatik eguneratu behar duzu. GitHubekin ere hori egin behar da. @@ -240,8 +243,8 @@ %d iruzkin-atsegite %d erabiltzaile-etiketak %d atsegite - %d photos of you - %d follow requests + Ateratzen zaren %d argazki + %d jarraipen-eskari You logged out before clicking this notification?! Jarioa Profila @@ -254,7 +257,7 @@ Ez erakutsi hurrengo eguneratzera arte Bertsioa Hasierako pantaila - Show keyboard on search + Erakutsi teklatua bilaketan Orokorra Gaia Deskargak @@ -301,7 +304,7 @@ Gorde Argazki-oina Editatu argazki-oina - Translate caption + Itzuli argazki-oina Video player timeline Atsegiten… Like unsuccessful @@ -318,7 +321,7 @@ Iruzkina Antolamendua Istorioen jarioa - Bidalketa irekitzen... + Opening post… Partekatu Diseinua Zutabe kopurua @@ -372,8 +375,8 @@ %d iruzkin - %d view - %d views + Ikusaldi %d + %d ikusaldi Istorio %s @@ -383,7 +386,7 @@ Izenburua Kideak Administratzailea - Inviter + Gonbidatzailea Mututu mezuak Mututu aipamenak Gehitu kideak @@ -400,35 +403,35 @@ Gehitu Bidali Zure buruari erantzuten - %s(e)ri erantzuten + %s(r)i erantzuten Zure buruari erantzun diozu Erantzun duzu - You replied to %s + %s(r)i erantzun diozu Replied to %s Erantzun dizu Bere buruari erantzun dio - You reacted to their story - Reacted to your story + Bere istorioari erreakzionatu diozu + Zure istorioari erreakzionatu dio You mentioned them in your story - Mentioned you in their story - You replied to their story - Replied to your story - Image has expired - Image will expire when seen - Video has expired - Video will expire when seen - Message has expired - Message will expire when seen - \@%s\'s story + Bere istorioan aipatu zaitu + Bere istorioari erantzun diozu + Zure istorioari erantzun dio + Irudia iraungitu da + Irudia ikusi bezain pronto iraungituko da + Bideoa iraungitu da + Bideoa ikusi bezain pronto iraungituko da + Mezua iraungitu da + Mezua ikusi bezain pronto iraungituko da + \@%s(r)en istorioa \@%s\'s story highlight - Photo - Video - Voice message - Post - Approval required to join - Requests - Admins only - Added by %s + Argazkia + Bideoa + Ahots-mezua + Bidalketa + Batzeko onarpena behar da + Eskaerak + Administratzaileak soilik + %s(e)k gehituta Admin approval required An admin approval will be required to add new members to the group Amaitu txata @@ -437,14 +440,14 @@ Zain dauden eskariak Onartu %1s(e)n (%2s) eskaria? Baztertu - Accept + Onartu Zu No pending requests Checking for new messages Istorioak MZ Jakinarazpenak - Post + Bidalketa Gaitu MZen jakinarazpenak Freskatu jarioa automatikoki Auto refresh every @@ -460,44 +463,44 @@ Added keyword: %s to filter list Removed keyword: %s from filter list Ikusita gisa markatuta - Delete unsuccessful + Ez da ongi deskargatu Throttled by Instagram because of too many API requests. Wait for some time before retrying. Errorea This account has been logged out. - Login required! - User is inactive! + Saioa hastea beharrezkoa da! + Erabiltzailea ez dago aktibo! Barinsta Crash Report Select an email app to send crash logs Not found! Your IP has been rate limited by Instagram. Wait for an hour and try again. <a href=\"https://redd.it/msxlko\">Learn more.</a> - Skip this update - You\'re already on the latest version + Saltatu eguneratze hau + Jadanik azken bertsioa duzu Screen order Other tabs The tab order will be reflected on next launch If saved, all DM related features will be disabled on next launch - Copy caption - Copy reply + Kopiatu argazki-oina + Kopiatu erantzuna Leheneratu - Backup + Babeskopia Select a folder where Barinsta can store downloads and temporary files.\n\nYou can change this later in More > Settings > Downloads. Android has changed the way apps can access files and directories on storage. Currently Barinsta does not have permission to access the following folder: Permissions for the previously selected folder were revoked by the system: The previously selected folder does not exist now: Re-select the directory or select a new directory by clicking the button below. - No folder selected! - Please choose a directory from your storage, not a category on the sidebar. - Success! Please wait. Starting app… - Barinsta folder + Ez da karpetarik hautatu + Please choose a directory from your storage, not a category on the sidebar.\n(%s) + Arrakasta! Itxaron, aplikazioa hasten ari da… + Barinsta karpeta Top - Recent - Clear - No Map app found! + Azkenak + Garbitu + Ez da mapa-aplikaziorik aurkitu! Click to show full like count - No profile pic found! - Are you sure you want to open this link? - Sending... - Share via DM + Irudirik ez da aurkitu! + Ziur zaude esteka hau ireki nahi duzula? + Sending… + Partekatu MZ bidez Partekatu esteka… - Slide to Cancel + Irristatu uzteko diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 152614d0..40c4a314 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -19,6 +19,7 @@ علاقه مندی ها کاوش دیدگاه‌ها + Replies فعالیت بررسی بروزرسانی هنگام آغاز برنامه مسدود کردن تصویر از صفحه& پیش‌نمایش برنامه @@ -67,8 +68,7 @@ صبور باشید! دیدن فرسته دیدن فرسته - Spotify - رای دادن + Poll رای با موفقیت بود! در حال حاظر شما رای داده اید! پاسخ @@ -85,9 +85,11 @@ Slider در حال حاظر شما رای داده اید! یادآوری ها + Question این اکانت شخصی است شما امکان دسترسی به پست ها را بعد از آنفالو کردن ندارید! مطمن هستید؟ + Are you sure? می توانید توسط (بیشتر) وارد شوید -> حساب در گوشه پایین سمت راست یا می توانید حساب های عمومی را بدون ورود به سیستم ببینید! این اکانت پستی ندارد چنین پست هایی نیست! @@ -102,7 +104,7 @@ Delete collection Are you sure you want to delete this collection? All contained media will remain in other collections. - Add to collection... + Add to collection… Remove from collection پسندیده شد ذخیره شد @@ -181,9 +183,9 @@ اسکرین شات گرفته شده نمیتواند ارسال شود Unseen count response is null! - پیام... + Message… Press and hold to record audio - Updating... + Updating… Leave chat این گفتگو را ترک میکنید؟ اخراج @@ -195,6 +197,7 @@ دانلود مستقیم پست ها در موبایل! آوردن پست (ها) دانلود کامل شده + Preparing to download… دریافت پیک… دانلود رسانه ها دانلود عکس پروفایل @@ -319,7 +322,7 @@ دیدگاه Layout Feed stories - باز کردن پیک... + Opening post… همرسانی Layout style Column count @@ -487,7 +490,7 @@ The previously selected folder does not exist now: Re-select the directory or select a new directory by clicking the button below. No folder selected! - Please choose a directory from your storage, not a category on the sidebar. + Please choose a directory from your storage, not a category on the sidebar.\n(%s) Success! Please wait. Starting app… Barinsta folder Top @@ -497,7 +500,7 @@ Click to show full like count No profile pic found! Are you sure you want to open this link? - Sending... + Sending… Share via DM Share link… Slide to Cancel diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index d1f0fe83..0402c466 100755 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -19,6 +19,7 @@ Favoris Découvrir Commentaires + Replies Activité Rechercher les mises à jours au démarrage Bloquer les captures d\'écran & l\'aperçu de l\'application @@ -67,8 +68,7 @@ Soyez patient(e)! Aperçu Aperçu - Spotify - Vote + Poll Vote réussi ! Vous avez déjà voté ! Répondre @@ -85,8 +85,10 @@ Curseur Vous avez déjà répondu ! Mentions + Question Compte privé Vous ne pourrez plus accéder aux messages après avoir été désabonné! Êtes-vous sûr(e) ? + Are you sure? Vous pouvez vous connecter via Plus -> de Compte en bas à droite ou vous pouvez consulter les comptes publics sans vous connecter ! Ce compte ne contient pas de posts Aucun post de ce genre ! @@ -101,7 +103,7 @@ Supprimer la collection Êtes vous sûr·e de vouloir supprimer cette collection ? Tous les médias contenus resteront dans d\'autres collections. - Ajouter à la collection... + Add to collection… Retirer de la collection Aimé Sauvegardé @@ -180,9 +182,9 @@ Capture d\'écran effectuée Impossible de livrer La réponse du comptage invisible est nulle ! - Message... + Message… Appuyer de manière prolongée pour enregistrer un audio - Mise à jour en cours... + Updating… Quitter la conversation Quitter cette conversation ? Éjecter @@ -194,6 +196,7 @@ Téléchargez les posts directement sur le téléphone ! Téléchargement de(s) post(s) Téléchargement terminé + Preparing to download… Entrain de télécharger la publications… Téléchargement du média en cours Téléchargement de la photo de profil @@ -230,7 +233,7 @@ L\'application a planté Oups.. l\'application a planté, mais ne vous inquiétez pas, vous pouvez envoyer un rapport d\'erreur au développeur pour l\'aider à résoudre le problème :) Activité - Archive du story + Archive des story Utilisateurs suggérés Sélectionnez une image Envoi en cours… @@ -318,7 +321,7 @@ Commentaire Disposition Flux de stories - Entrain d\'ouvrir la publication... + Opening post… Partager Style du modèle Nombre de colonnes @@ -486,7 +489,7 @@ Le dossier sélectionné précédemment n\'existe pas : Ré-sélectionnez le répertoire ou sélectionnez un nouveau répertoire en cliquant sur le bouton ci-dessous. Aucun dossier sélectionné! - Veuillez choisir un répertoire dans votre espace de stockage, et non une catégorie dans la barre latérale. + Please choose a directory from your storage, not a category on the sidebar.\n(%s) Succès ! Veuillez patienter. Démarrage de l\'application… Dossier Barinsta Retour vers le haut @@ -496,7 +499,7 @@ Cliquez pour voir le compte complet de j\'aimes Aucune photo de profil trouvée ! Êtes-vous sûr de vouloir ouvrir ce lien ? - En cours d\'envoi... + Sending… Partager via MP Partager le lien… Glisser pour annuler diff --git a/app/src/main/res/values-hi/arrays.xml b/app/src/main/res/values-hi/arrays.xml index 9655191d..61fa6c6d 100644 --- a/app/src/main/res/values-hi/arrays.xml +++ b/app/src/main/res/values-hi/arrays.xml @@ -31,19 +31,19 @@ सिस्टम के अनुसार बैटरी स्तर के अनुसार - गहरा + डार्क हल्का - Instagram default (Unread then read) - From newest to oldest - From oldest to newest + सिस्टम निर्धारित (नए से पुराने) + नए से पुराना + पुराने से नया कुछ नहीं \@ पर - पर + चालू \| - @@ -57,7 +57,7 @@ - secs - mins + सेकंड + मिनट diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index b74f6145..e3ae4576 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -19,6 +19,7 @@ पसंदीदा खोजिए टिप्पणियाँ + जवाब कार्यकलाप खुलने पर अपडेट के लिए जाँच करें स्क्रीनशॉट को ब्लॉक करें & ऐप प्रीव्यू @@ -29,7 +30,7 @@ फ़ीड से म्यूट की गई स्टोरीस छिपाएं तुम देखने के बाद सीधा संदेश को \"दिखागया\" लिखा जाएगा अलग सदस्य जानेंगे कि तुम देखे हो - Autoplay video stories + व्हिडिओ स्टोरी ऑटो प्ले करे क्रियाकलाप सूचनाएँ दिखायें फ़ीड स्टोरीज क्रमबद्ध करें प्रोफ़ाइल लोड करने में एरर! क्या यूजरनाम मान्य है? यदि हां, तो आप सीमित हो सकते हैं. @@ -67,8 +68,7 @@ धैर्य रखें! पोस्ट देखें पोस्ट देखें - Spotify - मत दें + Poll मतदान सफल! आप पहले से ही मत दिये हो! जवाब @@ -85,9 +85,11 @@ स्लाइडर आप पहले से ही उत्तर दिये हो! उल्लेख + Question यह एकाउॅट निजी है अनफलो चे बाद आप पोस्ट तक पहुंच नहिं पाओगे! आप निश्चित हैं? + क्या आप सुनिश्चित हैं? आप लग इन और तरिकें के साथ कर सकते हो -> दक्षिण के निचे कोने में एकाउॅट पर या आप सार्बजनीन एकाउॅट को बिना लग इन के देख सकते हो! इसी एकाउॅट में कोई पोस्ट नहीं है। एसे कोई पोस्ट नहीं है! @@ -102,7 +104,7 @@ संग्रह हटाएं क्या आप इस संग्रह को मिटाना चाहते हैं? सभी निहित मीडिया अन्य संग्रहों में बने रहेंगे। - संग्रह में जोड़ें... + Add to collection… संग्रह से निकालें पसंद किये सेव किया @@ -159,16 +161,16 @@ %s ने @%s. द्वारा एक प्रोफ़ाइल शेयर की %s ने एक स्थान शेयर किया: %s %s ने @%s. द्वारा एक कहानी हाइलाइट शेयर की - %s shared a story by @%s - %s sent a voice message - %s shared a clip by @%s - %s shared an IGTV video by @%s - You replied to their story: %s - %s replied to your story: %s - You reacted to their story: %s - %s reacted to your story: %s - You mentioned @%s in your story - %s mentioned you in their story + %s ने @%s. द्वारा एक स्टोरी शेयर की + %s ने व्हॉईस मेसेज भेजा + %s ने @%s द्वारा एक क्लिप भेजी + %s ने @%s द्वारा एक IGTV व्हिडिओ भेजा + आपने %s की स्टोरी को रिप्लाय किया + %s ने आपकी स्टोरी को रिप्लाय किया %s + आपने %s की स्टोरी को रिएक्शन दी + %s ने आपकी स्टोरी को रिएक्शन दी: %s + आपने @%s को अपनी स्टोरी मे मेनशन किया + %s ने आपको उनकी स्टोरी मे मेनशन किया अज्ञात प्रकार के मिडीआ मिडीया एक्सपायारड! वितरित @@ -181,20 +183,21 @@ स्क्रीनशॉट किया पहुॅचा नहीं जा सकता Unseen count response is null! - Message... - Press and hold to record audio - Updating... - Leave chat + Message… + ऑडिओ रिकॉर्ड करने के लिये बटन दबाये रखे + Updating… + चॅट से बाहर निकले इस बार्तालाप को छोड दें? बाहर निकालें अवशेष ब्यबहारकारी - Invalid user + अवैध उपयोगकर्ता Instagram DM के लिए 60 सेकंड से अधिक के वीडियो अपलोड करने की अनुमति नहीं देता है। Instagram 60 सेकंड से अधिक समय तक ऑडियो अपलोड करने की अनुमति नहीं देता है। सीधे डाउनलोड करें पोस्ट को सिधे अपने फोन पर डाउनलोड करें पोस्ट(स्) ला रहे हैं डाउनलोड समाप्त हुआ + डाउनलोड करने की तैयारी... डाउनलोड जारी है… मिडीया डाउनलोड हो रहा है प्रोफाइल चित्र डाउनलोड करें @@ -231,63 +234,64 @@ एप में त्रुटि हुयी। Oops.. the app crashed, but don\'t worry you can send error report to the developer to help him fix the issue. (: गतिविधि - Story archive - Suggested users + स्टोरी आरकाईव + सुझायें ऊपयोगकर्ता चित्र का चयन करें अपलोड हो रहा है... आपके पास है: %d अनुगामी %d टिप्पणियाँ %d टिप्पणीयाँ पसन्दीत - %d usertags - %d likes - %d photos of you - %d follow requests + %d युजरटॅग + %d लाईक्स + %d आपके फोटोज् + %d फॉलो रिक्वेस्ट इस अधिसूचना पर क्लिक करने से पहले आपने लॉग आउट किया?! फ़ीड - Profile - More - DM - %d selected - Successfully logged out! - Info - Mark as seen - Do not show again until next update + प्रोफाईल + अधिक + डी एम + %d सिलेक्ट किए हुए + सफलतापूर्वक लॉग आउट कर दिया + जानकारी + पढ़ा गया के रूप मे मार्क करें + अगले अपडेट ताक ना दिखाएं संस्करण प्रारंभ पृष्ठ - Show keyboard on search + सर्च मे कीबोर्ड दिखाएं सामान्य थीम डाउनलोड - Locale + लोकल खाता वर्तमान लॉगिन काम नहीं कर रहा है? बस फिर से खाता जोड़ें। खाता जोड़ें - License (English only) + लायसेन्स (सिर्फ इंग्लिश) हमारी वेबसाइट पर जाएं - Get support, discuss, meet others, and have fun! + सपोर्ट करे, बात चीत करे, दुसरोसे मिले, आहे मजे किजिये! Github मे सोर्स कोड देखें - Audit, star, report bugs, contribute, and have fun (again)! - Send feedback by email - Third-Party Attributions + ऑडिट, स्टार, बुग रिपोर्ट करे, योगदान करे, ऑर मजे करे (फिर से)! + फीडबॅक ई मेल द्वारा भेजे + तीसरे पक्ष के अनुप्रयोग रिमाइन्डर - Please use this app responsibly. Downloaded images should only be used for purposes allowed by applicable laws. + कृपया यह एप जिम्मेदारी से इस्तेमाल करे. +डाउनलोड किए हुए इमेजेस सिर्फ नियम पूरक उद्देश से इस्तेमाल करे. सफेद काला हल्के रंग का थीम गहरे रंग की थीम कॉफ़ी - Material Dark - Added to Favorites! - Add to Favorites + मैटेरियल डार्क + फेवरेट में जोड़ा गया + फेवरेट के एड करे खाते हैशटैग्स स्थान अज्ञात - Removed from Favourites! - Backup & Restore - Auto Backup - Starting from Android 6, Android\'s Auto Backup feature will upload all app settings, account login data, and favorites onto your Google Drive, which can be restored by reinstalling the app after uninstallation. + फेवरेट से हटाया + बैकअप & रिस्टोर + ऑटो बैकअप + एंड्रॉयड 6 के आगे, एंड्रॉयड का ऑटो बैकअप फीचर सब एप का बैकअप, अकाउंट लॉग इन डेटा, और फेवरेट गुगल ड्राइव पे होगा, जो एप वापिस इंस्टॉल करते वक्त रिस्टोर होगा अनइंस्टॉल करने के बाद. This preference has no effect if Google Play Services is not present, or if Auto Backup is disabled from your device settings. Disabling here does not erase existing backups. Enable Auto Backup Manual Backup @@ -319,7 +323,7 @@ Comment Layout Feed stories - Opening post... + Opening post… Share Layout style Column count @@ -346,71 +350,71 @@ Saturation Sharpen Exposure - Center - Color - Start - End - Bilateral Blur - Vignette - Box blur - Sepia - Clarendon - 1977 - Aden - Reset - Crop - Normal + मध्य + रंग + शुरू करें + समाप्त + बायलॅटरल ब्लर + विन्‍येट + बॉक्स ब्लर + सिपिया + क्लेरेंडन + १९७७ + एडेन + रीसेट + क्रॉप + सामान्य - %d like - %d likes + %d लाईक्स + %d लाइक्स - %d reply - %d replies + %d रिप्लाई + %d रिप्लाई %d comment - %d comments + %d कॉमेंट्स - %d view + %d व्यू %d views %s story - %s stories + %s स्टोरीज - Details - Title - Members - Admin - Inviter - Mute messages - Mute mentions - Add members - Search - Done - Make Admin - Remove as Admin - Edit was unsuccessful - Message - Tap to remove - Forward - You forwarded a message - Forwarded a message - Add - Send - Replying to yourself - Replying to %s - You replied to yourself - You replied - You replied to %s - Replied to %s - Replied to you - Replied to themself - You reacted to their story - Reacted to your story - You mentioned them in your story + विवरण + शीर्षक: + सदस्य + एडमिन + आमंत्रणकर्ता + मेसेज शांत रखे + मेनशन शांत रखे + सदस्यों को जोड़ें + खोजें + ठीक + एडमिन बनाएं + एडमिन से हटाएँ + एडिट असफल था + मेसेज + हटाने के लिए टैप करें + फॉरवर्ड करें + आपने मेसेज फॉरवर्ड किया + मेसेज फॉरवर्ड किया + जोड़ें + भेजें + खुद को रिप्लाई कर रहे + %s को रिप्लाई कर रहे + खुद को रिप्लाई किया + आपने रिप्लाई किया + आपने %s को रिप्लाई किया + %s को रिप्लाई किया + आपको रिप्लाई किया + खुद को रिप्लाई किया + आपने उनके स्टोरी को रिएक्ट किया + आपके स्टोरी को रिएक्ट किया + आपने @%s को अपनी स्टोरी मे मेनशन किया Mentioned you in their story You replied to their story Replied to your story @@ -463,11 +467,11 @@ Marked as seen Delete unsuccessful Throttled by Instagram because of too many API requests. Wait for some time before retrying. - Error - This account has been logged out. - Login required! - User is inactive! - Barinsta Crash Report + एरर + यह अकाउंट लॉग आउट हुआ + लॉगिन अनिवार्य + उपयोगकर्ता निष्क्रिय है + Barinsta क्रैश रिपोर्ट Select an email app to send crash logs Not found! Your IP has been rate limited by Instagram. Wait for an hour and try again. @@ -487,7 +491,7 @@ The previously selected folder does not exist now: Re-select the directory or select a new directory by clicking the button below. No folder selected! - Please choose a directory from your storage, not a category on the sidebar. + Please choose a directory from your storage, not a category on the sidebar.\n(%s) Success! Please wait. Starting app… Barinsta folder Top @@ -497,7 +501,7 @@ Click to show full like count No profile pic found! Are you sure you want to open this link? - Sending... + Sending… Share via DM Share link… Slide to Cancel diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 84ab0264..5fc2a9fb 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -19,6 +19,7 @@ Favorit Temukan Komentar + Replies Aktivitas Cek pembaruan saat memulai Block screenshots & app preview @@ -64,8 +65,7 @@ Mohon bersabar! Lihat Kiriman Lihat Kiriman - Spotify - Pilih + Poll Berhasil memilih! Anda sudah memilih! Tanggapi @@ -81,8 +81,10 @@ Slider Anda sudah menjawab! Sebutan + Question Akun Ini Bersifat Pribadi Anda tidak akan dapat mengakses kiriman setelah berhenti mengikuti! Anda yakin? + Are you sure? Anda bisa masuk melalui Lebih-> Akun di pojok kanan bawah. Atau anda dapat melihat akun publik tanpa masuk! Akun ini tidak memiliki kiriman Kiriman tidak ditemukan! @@ -97,7 +99,7 @@ Hapus koleksi Apakah Anda yakin Anda ingin menghapus koleksi ini? Seluruh media akan tetap berada dalam koleksi-koleksi lain. - Tambah ke koleksi... + Add to collection… Hapus dari koleksi Disuka Tersimpan @@ -176,9 +178,9 @@ Layar tertangkap Tidak dapat mengirim Unseen count response is null! - Pesan... + Message… Tekan dan tahan untuk merekam - Memperbarui... + Updating… Tinggalkan obrolan Tinggalkan obrolan ini? Keluarkan @@ -190,6 +192,7 @@ Langsung unduh kiriman ke ponsel! Mendapatkan kiriman… Unduhan selesai + Preparing to download… Mengunduh kiriman… Mengunduh media Mengunduh foto profil @@ -314,7 +317,7 @@ Komentar Tata letak Umpan cerita - Membuka kiriman... + Opening post… Bagikan Gaya tata letak Jumlah kolom @@ -477,7 +480,7 @@ The previously selected folder does not exist now: Re-select the directory or select a new directory by clicking the button below. No folder selected! - Please choose a directory from your storage, not a category on the sidebar. + Please choose a directory from your storage, not a category on the sidebar.\n(%s) Success! Please wait. Starting app… Barinsta folder Top @@ -487,7 +490,7 @@ Click to show full like count No profile pic found! Are you sure you want to open this link? - Sending... + Sending… Share via DM Share link… Slide to Cancel diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 43d0d418..e6600907 100755 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -19,6 +19,7 @@ Preferiti Scopri Commenti + Risposte Attività Verifica aggiornamenti all\'avvio Blocca screenshot & anteprima app @@ -29,7 +30,7 @@ Nascondi storie silenziate dal feed Segna il DM come visto dopo la visualizzazione Altri membri sapranno che lo hai visualizzato - Autoplay video stories + Riproduci automaticamente le storie video Abilita notifiche attività Ordinamento storie feed Errore nel caricamento del profilo! Il nome utente è valido? Se sì, potresti essere considerato limitato. @@ -67,8 +68,7 @@ Sii paziente! Visualizza Post Visualizza Post - Spotify - Vota + Poll Votazione riuscita! Hai già votato! Rispondi @@ -85,8 +85,10 @@ Scorrimento Hai già risposto! Menzioni + Question Questo Profilo è Privato Non potrai accederei ai post dopo aver smesso di seguire! Sei sicuro? + Sei sicuro? Puoi accedere tramite Altro -> Profilo nell\'angolo in basso a destra o puoi visualizzare i profili pubblici senza accedere! Questo Profilo Non ha Post Nessun Post Simile! @@ -101,7 +103,7 @@ Elimina raccolta Sei sicuro di voler eliminare questa raccolta? Tutti i media contenuti rimarranno in altre raccolte. - Aggiungi alla raccolta... + Add to collection… Rimuovi dalla raccolta Piaciuti Salvati @@ -180,9 +182,9 @@ Catturato Impossibile consegnare Il responso del conteggio non visto è nullo! - Messaggio... + Message… Tieni premuto per registrare l\'audio - In aggiornamento... + Updating… Lascia chat Lasciare questa chat? Espelli @@ -194,6 +196,7 @@ Scarica i post direttamente sul telefono! Recuperando i post Download completato + Preparazione al download… Scaricando il post… Download del media Scaricando l\'immagine del profilo @@ -318,7 +321,7 @@ Commento Disposizione Feed storie - Aprendo il post... + Opening post… Condividi Stile disposizione Conteggio colonne @@ -486,7 +489,7 @@ La cartella precedentemente selezionata non esiste ora: Riselezionare la cartella o selezionare una nuova cartella facendo clic sul pulsante qui sotto. Nessuna cartella selezionata! - Please choose a directory from your storage, not a category on the sidebar. + Scegli una cartella dal tuo archivio, non una categoria sulla barra laterale.\n(%s) Successo! Attendere prego. Avvio app… Cartella di Barinsta Torna su @@ -496,7 +499,7 @@ Clicca per mostrare il conteggio completo Nessuna foto profilo trovata! Sei sicuro di voler aprire questo link? - Invio in corso... + Sending… Condividi tramite DM Condivisione link… Trascinare per cancellare diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 3b3575b9..68c201db 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -19,6 +19,7 @@ お気に入り 発見 コメント + Replies アクティビティ 起動時にアップデートを確認 スクリーンショットとアプリプレビューをブロック @@ -64,8 +65,7 @@ お待ちください 投稿を表示 投稿を表示 - Spotify - 投票 + Poll 投票しました! すでに投票済みです! 返信する @@ -81,8 +81,10 @@ スライダー すでに回答済みです! メンション + Question このアカウントは非公開です フォローを解除すると、投稿にアクセスできなくなります。よろしいですか? + Are you sure? 右下にある 詳細 -> アカウント でログインすることができます。または、ログインせずに公開アカウントを表示することができます! このアカウントには投稿がありません 投稿はありません! @@ -97,7 +99,7 @@ コレクションを削除する このコレクションを削除してもよろしいですか? 含まれるすべてのメディアは他のコレクションに残ります。 - コレクションに追加する + Add to collection… コレクションから削除する いいね! 保存 @@ -176,9 +178,9 @@ スクリーンショット撮影済み 配信できません 未読の返信はありません! - メッセージ... + Message… 録音するには長押ししてください - 更新中... + Updating… チャットから退出する チャットを終了しますか? キックする @@ -190,6 +192,7 @@ 投稿を直接ダウンロード! 投稿を取得中 ダウンロードが完了しました + Preparing to download… 投稿をダウンロード中… メディアをダウンロード中 プロフィール画像をダウンロード中 @@ -314,7 +317,7 @@ コメント レイアウト フィードのストーリーズ - 投稿を開いています... + Opening post… 共有 レイアウトスタイル 列数 @@ -477,7 +480,7 @@ 以前に選択したフォルダは現在存在しません: ディレクトリを再度選択するか、下のボタンをクリックして新しいディレクトリを選択してください。 フォルダーが選択されていません! - Please choose a directory from your storage, not a category on the sidebar. + Please choose a directory from your storage, not a category on the sidebar.\n(%s) 成功!お待ちください。アプリを起動しています… Barinstaフォルダ 関連 @@ -487,7 +490,7 @@ Click to show full like count プロフィール写真が見つかりません! Are you sure you want to open this link? - Sending... + Sending… Share via DM Share link… Slide to Cancel diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 1f8ccbf7..4de78257 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -19,6 +19,7 @@ 즐겨찾기 발견 댓글 + Replies 활동 시작시 업데이트 확인 Block screenshots & app preview @@ -64,8 +65,7 @@ 기다려주세요. 게시물 보기 게시물 보기 - Spotify - 투표 + Poll 투표 성공! 이미 투표하셨습니다 메시지 보내기 @@ -81,8 +81,10 @@ 슬라이더 이미 답변하셨습나다. 언급 + Question 비공개 계정입니다 언팔로우하면 게시물을 볼 수 없겠습니다. 확실합니까? + Are you sure? You can log in via More -> Account on the bottom-right corner or you can view public accounts without login! 계정에 게시물이 없음 게시물 없음 @@ -97,7 +99,7 @@ 컬렉션 삭제 Are you sure you want to delete this collection? All contained media will remain in other collections. - 컬렉션에 저장... + Add to collection… 컬렉션에서 제거 좋아하는 게시물 저장됨 @@ -138,11 +140,11 @@ 미리 보기 Swap Time and Date positions Cannot delete currently in use account - Are you sure you want to delete \'%s\'? + \"%s\"을(를) 삭제하시겠습니까? 프로필 보기 스토리 보기 프로필 사진 보기 - Unsupported message type + 지원되지 않는 메시지 유형 보내기 취소 GIPHY에서 보기 %s shared a post by @%s @@ -176,9 +178,9 @@ 스크린샷 Cannot deliver Unseen count response is null! - 메시지... + Message… Press and hold to record audio - Updating... + Updating… 채팅 나가기 이 채팅에서 나가시겠습니까? Kick @@ -190,6 +192,7 @@ Downloads posts directly to the phone! Fetching post(s) Download completed + Preparing to download… Downloading post… Downloading media Downloading profile picture @@ -314,7 +317,7 @@ 댓글 달기 레이아웃 피드 스토리 - 게시물 열기 중... + Opening post… 공유 레이아웃 스타일 Column count @@ -477,7 +480,7 @@ The previously selected folder does not exist now: Re-select the directory or select a new directory by clicking the button below. No folder selected! - Please choose a directory from your storage, not a category on the sidebar. + Please choose a directory from your storage, not a category on the sidebar.\n(%s) Success! Please wait. Starting app… Barinsta folder 상단 @@ -487,7 +490,7 @@ Click to show full like count No profile pic found! Are you sure you want to open this link? - Sending... + Sending… Share via DM Share link… Slide to Cancel diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index 8999792c..5da6575f 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -19,17 +19,18 @@ Омилени Откриј Коментари + Одговори Активности Провери за ажурирање Блокирај слики од екранот & преглед на апликацијата Превземи објави во папката со кориснички имиња - Prepend Username to Filename + Додади корисничко име при зачувување Означи ги приказните како видени Авторот на приказната ќе знае дека сте ја погледнале приказната - Hide muted stories from feed + Сокриј замолчени приказни Означи порака како видена Другите членови ќе знаат дека сте ја виделе содржината на пораката - Autoplay video stories + Автоматски гледај видео приказни Овозможи нотификации Сортирање на објави Грешка при вчитување на профилот! Проверете дали корисничкото име е валидно? @@ -55,8 +56,8 @@ %s\nСледбеници Autoplay на видеа - Continue videos in background - Do not pause videos when the app is out of focus + Овозможи играње на видео во позадина + Не паузирај видеа кога апликацијата е надвор од фокус Секогаш гледај видеа без звук Секогаш прикажувај наслов Одбери што сакаш да превземеш @@ -67,8 +68,7 @@ Биди трпелив! Прегледај Објава Погледни Објава - Spotify - Гласај + Poll Гласањето беше успешно! Вие веќе гласавте! Одговори @@ -85,8 +85,10 @@ Лизгач Вие веќе одговоривте! Спомнувања + Question Корисникот има приватен профил Наме да можете да гледате објави и приказни од овој корисник ако го Одследите! Дали сте сигурни? + Дали сте сигурни? Можете да се најавите преку Повеќе -> Корисничата сметка која се наоѓа долу десно или само можете да гледате Отворени профили без да се логирате! Овој корисник нема објави Не постојат такви објави! @@ -101,7 +103,7 @@ Избриши колекција Дали сте сигурни дека сакате да ја избришите оваа колекција? Сите медиуми ќе останат во други колекции. - Додади во колекција... + Add to collection… Одстрани од колекција Лајкнато Зачувано @@ -180,9 +182,9 @@ Скриншотнато Неможе да се прати Одговорот беше null! - Одговори... + Message… Притисни и задржи за да снимиш аудио порака - Се ажурира... + Updating… Напушти чат Дали сакате да го напуштите чатот? Кикни @@ -194,6 +196,7 @@ Превземи објави во твојот телефон! Се превземаат објава(ви) Преземањето е завршено + Preparing to download… Превземање на Објава… Се превзема медиум Се превзема профилната слика @@ -222,7 +225,7 @@ Одобри барање Не одобрувај барање Сподели ја оваа јавна објава до… - This is a private post! Share to those who can view it. + Ова е приватна објава! Споделете ја со оние кои можат да ја видат. Оваа категорија е празна… Нова надоградба е присутна! (%s) Потсетник: Ако ја имате превземено оваа апликација со F-Droid, морате од таму да надоградите! Истото важи и за верзиите од GitHub. @@ -254,7 +257,7 @@ Прескокни ажурирање Верзија Почетна страна - Show keyboard on search + Прикажи тастатура при пребарување Општо Изглед Превземања @@ -285,11 +288,11 @@ Непознато Одстрането од Омилени! Направете Резервна Копија & Враќање - Auto Backup - Starting from Android 6, Android\'s Auto Backup feature will upload all app settings, account login data, and favorites onto your Google Drive, which can be restored by reinstalling the app after uninstallation. - This preference has no effect if Google Play Services is not present, or if Auto Backup is disabled from your device settings. Disabling here does not erase existing backups. - Enable Auto Backup - Manual Backup + Автоматска резерва + Со Андроид верзија 6, Андроид автоматското зачувување на копија ќе зачувува, опции, кориснички информации, омилиени, во Google Drive, и кои ќе бидат вратени со реинсталација на апликацијата. + Оваа опција нема ефект, ако немате Google Play Services, или ако Автоматското зачувување на копии е исклучено од подесување. Исклучувањето од тука нема да избрише постоечки резервни копии. + Уклучи Автоматски Резерви + Рачни Резервни Копии Зачувај опции од апликацијата, кориснички профил, и/или информации од твоите омилени профили во обичен текст или со енкрипција за подоцна да можеш да ги вратиш. Ако зачувуваш информации за најавување, третирај го генералниот фајл како многу важен, чувај го на безбедно место! Креирај новa резервна копија @@ -318,7 +321,7 @@ Коментирај Изглед Приказни - Отварање на Објава... + Opening post… Сподели Стил на изглед Број на колони @@ -455,49 +458,49 @@ Одговорот на статусот не е ок! Барањето неуспешно! Клучен збор - Enable keyword filter - Edit keyword filters - Added keyword: %s to filter list - Removed keyword: %s from filter list - Marked as seen - Delete unsuccessful - Throttled by Instagram because of too many API requests. Wait for some time before retrying. + Вклучи филтер на зборови + Уреди филтер на зборови + Додаден клучниот збор: %s во листата на филтер + Отстранет клучниот збор: %s од листата на филтер + Означено како видено + Отстранувањето неуспешно + Успорување поради Instagram, премногу API пребарувања. Ве молиме почекајте некое време, пред да пробате повторно. Грешка Оваа сметка е одјавена. Потребна е најава! Корисникот е неактивен! - Barinsta Crash Report - Select an email app to send crash logs - Not found! - Your IP has been rate limited by Instagram. Wait for an hour and try again. <a href=\"https://redd.it/msxlko\">Learn more.</a> + Пријави Грешка + Одберете еmail апликација за праќање на грешки + Не беше пронајдено! + Вашата IP адреса е ограничена од Instagram. Пробајте после еден час повторно. <a href=\"https://redd.it/msxlko\"> Повеќе тука:</a> Прескокни го ова ажурирање - You\'re already on the latest version - Screen order - Other tabs - The tab order will be reflected on next launch - If saved, all DM related features will be disabled on next launch + Веќе ја користите најновата верзија + Подредување + Други менија + Редоследот на менија ќе биде аплициран на следно лансирање + Ако го зачувате, сите карактеристики поврзани со Директни Пораки ќе бидат исклучени на следно лансирање Копирај наслов Копирај одговор - Restore - Backup - Select a folder where Barinsta can store downloads and temporary files.\n\nYou can change this later in More > Settings > Downloads. - Android has changed the way apps can access files and directories on storage. Currently Barinsta does not have permission to access the following folder: - Permissions for the previously selected folder were revoked by the system: - The previously selected folder does not exist now: - Re-select the directory or select a new directory by clicking the button below. - No folder selected! - Please choose a directory from your storage, not a category on the sidebar. - Success! Please wait. Starting app… - Barinsta folder - Top + Врати + Резерви + Одберете фолдер каде што Barinsta ќе ги зачувува сите привремени податоци.\n\n Можете да го промените ова подоцна во Повеќе > Опции > Превземања. + Андроид 12 го промени начинот на кои апликациите можат да пристапуваат кон податоци од меморија. Засега Barinsta не го подржува ова: + Дозволите за претходниот одбран фолдер ќе бидат вратени од страна на системот: + Претходно одбраниот фолдер не постои: + Одберете пак директориум или направете нов со притскање на копчето подолу. + Немате одберено фолдер! + Одберете фолдер од вашата меморија, а Не категорија од страна.\n(%s) + Успешно! Почекајте... + Barinsta фолдер + Врв Најнови Исчисти - No Map app found! - Click to show full like count - No profile pic found! - Are you sure you want to open this link? - Се испраќа... + Нема апликација со Мапи! + Прилажи прецизен број на допаѓања + Слика на профил не беше пронајдена! + Дали сте сигурни дека сакате да го отворите овој линк? + Sending… Сподели преку ДП Сподели линк… - Slide to Cancel + Повлечете за да Откажите diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 7bbfc94f..689de451 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -19,6 +19,7 @@ Favorieten Ontdekken Opmerkingen + Replies Activiteit Controleer op updates bij het opstarten Screenshots & app voorbeeld blokkeren @@ -29,7 +30,7 @@ Verberg gedempte verhalen uit feed Markeer privéberichten als gelezen na bekijken Andere gebruikers zullen het weten als je het hebt bekeken - Autoplay video stories + Videoverhalen automatisch afspelen Activiteitmeldingen inschakelen Feedverhalen sorteren Fout bij het laden van profiel! Is de gebruikersnaam geldig? Zo ja, dan kan je geratelimiteerd zijn. @@ -67,8 +68,7 @@ Wees geduldig! Bekijk Bericht Bekijk Bericht - Spotify - Stem + Poll Stem succesvol! Je hebt al gestemd! Reageer @@ -85,8 +85,10 @@ Schuifregelaar Je hebt al geantwoord! Vermeldingen + Question Dit Account is Privé Je zal geen toegang meer hebben tot berichten na het ontvolgen! Weet je het zeker? + Are you sure? Je kunt inloggen via Meer -> Rechtsonder in je account, of je kunt openbare accounts bekijken zonder in te loggen! Dit Account heeft Geen Berichten Geen dergelijke berichten! @@ -101,7 +103,7 @@ Verwijder collectie Weet u zeker dat u deze collectie wilt verwijderen? Alle opgenomen media blijven in andere collecties. - Aan collectie toevoegen... + Add to collection… Uit collectie verwijderen Leuk gevonden Opgeslagen @@ -119,7 +121,7 @@ Berichten dempen Verhalen uitzetten Berichten uitschakelen - Remove follower + Volger verwijderen Bio kopiëren Bio vertalen Wederzijds @@ -180,9 +182,9 @@ Screenshot genomen Kan niet afleveren Ongeziene graaf reactie is nul! - Bericht... + Message… Ingedrukt houden om audio op te nemen - Bijwerken... + Updating… Verlaat chat Deze chat verlaten? Kick @@ -194,6 +196,7 @@ Download rechtstreeks naar het apparaat! Post(s) ophalen Download voltooid + Preparing to download… Bericht downloaden… Media downloaden Profielfoto downloaden @@ -222,7 +225,7 @@ Verzoek accepteren Verzoek afwijzen Deel dit openbare bericht met… - This is a private post! Share to those who can view it. + Dit is een privé bericht! Deel dit met degenen die het kunnen bekijken. Deze categorie is op de een of andere manier leeg… Een update is beschikbaar(%s) Herinnering: Als je via F-Droid hebt gedownload, moet je ook updaten via F-Droid! Hetzelfde geldt voor GitHub. @@ -254,7 +257,7 @@ Niet meer weergeven tot de volgende update Versie Startscherm - Show keyboard on search + Toon toetsenbord bij zoeken Algemeen Thema Downloads @@ -285,11 +288,11 @@ Onbekend Verwijderd uit Favorieten! Backup & Herstel - Auto Backup - Starting from Android 6, Android\'s Auto Backup feature will upload all app settings, account login data, and favorites onto your Google Drive, which can be restored by reinstalling the app after uninstallation. - This preference has no effect if Google Play Services is not present, or if Auto Backup is disabled from your device settings. Disabling here does not erase existing backups. - Enable Auto Backup - Manual Backup + Automatische back-up + Vanaf Android 6, zal Android\'s Auto Backup functie alle app instellingen, account login gegevens, en favorieten uploaden naar uw Google Drive, die kan worden hersteld door het opnieuw installeren van de app na de-installatie. + Deze voorkeur heeft geen effect als Google Play Services niet aanwezig is, of als Auto Backup is uitgeschakeld in uw toestelinstellingen. Als u dit uitschakelt, worden bestaande back-ups niet gewist. + Automatische back-up inschakelen + Handmatige back-up Back-up maken van Barinsta app-instellingen, accountloggegevens en/of favorieten naar een platte tekst of versleuteld back-up bestand voor later herstellen. Als u een back-up van accountaanmeldgegevens maakt, het bestand vertrouwelijk behandelt en ergens veilig houdt! Nieuw back-upbestand maken @@ -318,7 +321,7 @@ Reageeer Lay-out Feed verhalen - Bericht openen... + Opening post… Delen Lay-out stijl Aantal kolommen @@ -469,7 +472,7 @@ Barinsta Crash Rapport Selecteer een email app om crashlogs te verzenden Niet gevonden! - Your IP has been rate limited by Instagram. Wait for an hour and try again. <a href=\"https://redd.it/msxlko\">Learn more.</a> + Je IP is beperkt tot Instagram. Wacht een uur en probeer het opnieuw. <a href=\"https://redd.it/msxlko\">Meer informatie.</a> Deze update overslaan Je bent al op de nieuwste versie Scherm volgorde @@ -478,26 +481,26 @@ Indien opgeslagen, zullen alle DM gerelateerde functies worden uitgeschakeld bij volgende start Kopieer opschrift Antwoord kopiëren - Restore + Herstel Backup - Select a folder where Barinsta can store downloads and temporary files.\n\nYou can change this later in More > Settings > Downloads. - Android has changed the way apps can access files and directories on storage. Currently Barinsta does not have permission to access the following folder: - Permissions for the previously selected folder were revoked by the system: - The previously selected folder does not exist now: - Re-select the directory or select a new directory by clicking the button below. - No folder selected! - Please choose a directory from your storage, not a category on the sidebar. - Success! Please wait. Starting app… - Barinsta folder + Selecteer een map waar Barinsta downloads en tijdelijke bestanden kan opslaan.\n\nJe kunt dit later wijzigen in meer > Instellingen > Downloads. + Android heeft de manier veranderd waarop apps toegang hebben tot bestanden en mappen op de opslag. Op dit moment heeft Barinsta geen toestemming om toegang te krijgen tot de volgende map: + De machtigingen voor de eerder geselecteerde map zijn door het systeem ingetrokken: + De eerder geselecteerde map bestaat nu niet meer: + Kies de directory opnieuw of kies een nieuwe directory door op de knop hieronder te klikken. + Geen map geselecteerd! + Please choose a directory from your storage, not a category on the sidebar.\n(%s) + Succes! Even geduld aub. Start app… + Barinsta map Top Recent Wissen Geen Kaartapp gevonden! - Click to show full like count + Klik om volledige like count te tonen Geen profielfoto gevonden! - Are you sure you want to open this link? - Sending... - Share via DM - Share link… - Slide to Cancel + Ben je zeker dat je deze link wilt openen? + Sending… + Delen via DM + Link delen… + Schuif om te annuleren diff --git a/app/src/main/res/values-or/arrays.xml b/app/src/main/res/values-or/arrays.xml index 65ae1c23..e7cf94ad 100644 --- a/app/src/main/res/values-or/arrays.xml +++ b/app/src/main/res/values-or/arrays.xml @@ -35,9 +35,9 @@ ହାଲୁକା - Instagram default (Unread then read) - From newest to oldest - From oldest to newest + ଇନଷ୍ଟାଗ୍ରାମ ଅନୁଯାୟୀ ‌(ପ୍ରଥମେ ଦେଖି ନ ଥିବା ଓ ପରେ ଦେଖିଥିବା) + ନୂତନ ରୁ ପୁରାତନ + ପୁରାତନ ରୁ ନୂତନ କିଛି ନୁହେଁ @@ -57,7 +57,7 @@ - secs - mins + ସେକେଣ୍ଡ + ମିନିଟ୍ diff --git a/app/src/main/res/values-or/strings.xml b/app/src/main/res/values-or/strings.xml index ea04c162..5119d3ec 100644 --- a/app/src/main/res/values-or/strings.xml +++ b/app/src/main/res/values-or/strings.xml @@ -9,7 +9,7 @@ ଲେଖା କପି କରିବାରେ ତ୍ରୁଟି କ୍ଲିପବୋର୍ଡରେ କପି କରାଗଲା! ରିପୋର୍ଟ କରନ୍ତୁ - Protect file with password + ପାସୱାର୍ଡ ଦ୍ବାରା ଫାଇଲକୁ ସୁରକ୍ଷିତ କରନ୍ତୁ ପାସୱାର୍ଡ ଠିକ୍ ଅଛି ହଁ @@ -19,35 +19,36 @@ ପସନ୍ଦିତ ଖୋଜିବା ଟିପ୍ପଣୀ + ଟିପ୍ପଣୀଗୁଡିକ କାର୍ଯ୍ୟକଳାପ ଖୋଲିବା ସମୟରେ ଅପଡେଟ ପାଇଁ ଯାଞ୍ଚ କରନ୍ତୁ - Block screenshots & app preview + ସ୍କ୍ରିନସଟ୍ ଏବଂ app preview ଅବରୋଧ କରନ୍ତୁ ଡାଉନଲୋଡ ପୋଷ୍ଟକୁ ବ୍ୟବହାରକାରୀଙ୍କ ନାମରେ ହୋଇଥିବା ସ୍ଥାନ ରେ ରଖ - Prepend Username to Filename + ଏକାଉଣ୍ଟ ନାମକୁ ଫାଇଲନାମ ସହିତ ଯୋଡନ୍ତୁ କାହାଣୀଗୁଡିକ ଦେଖିବା ପରେ \'ଦେଖାଗଲା\' ଚିହ୍ନିତ କରନ୍ତୁ | କାହାଣୀ ପ୍ରେରକ ଜାଣିବେ ତୁମେ ଏହାକୁ ଦେଖିଛ - Hide muted stories from feed + ଫିଡ୍ ରୁ mute ହୋଇଥିବା କାହାଣୀ କୁ ବାଦ ଦିଅନ୍ତୁ ବାର୍ତା ଦେଖିବା ପରେ \'ଦେଖାଗଲା\' ଚିହ୍ନିତ କରନ୍ତୁ | ଅନ୍ୟ ସଦସ୍ୟମାନେ ଜାଣିବେ ତୁମେ ଏହାକୁ ଦେଖିଛ। Autoplay video stories କାର୍ଯ୍ୟକଳାପ ସୂଚନା ଦେଖାନ୍ତୁ କାହାଣୀଗୁଡିକ ଶ୍ରେଣୀବଦ୍ଧ କରନ୍ତୁ - Error loading profile! Is the username valid? If so, you may be ratelimited. - Error loading profile! Is the username valid? Or did they block you? - Error loading hashtag! Is the name valid? - Error loading location! Is the URL valid? + ପ୍ରୋଫାଇଲ୍ ଲୋଡ୍ କରିବାରେ ତ୍ରୁଟି! ଉପଯୋଗକର୍ତ୍ତା ନାମ ସଠିକ କି? ଯଦି ଠିକ ଅଛି, ତେବେ ଆପଣ instagram ଦ୍ବାରା ସୀମିତ ହୋଇପାରନ୍ତି | + ପ୍ରୋଫାଇଲ୍ ଲୋଡ୍ କରିବାରେ ତ୍ରୁଟି! ଉପଯୋଗକର୍ତ୍ତା ନାମ ସଠିକ କି? କିମ୍ବା ସେମାନେ ତୁମକୁ ଅବରୋଧ କରିଛନ୍ତି କି? + ହ୍ୟାସଟ୍ୟାଗ୍ ଲୋଡ୍ କରିବାରେ ତ୍ରୁଟି! ନାମ ଠିକ୍ ତ? + ସ୍ଥାନ ଲୋଡ୍ କରିବାରେ ତ୍ରୁଟି! ଲିଙ୍କ୍ ଠିକ୍ ତ? ଡ଼ାଉନଲୋଡ଼ ଫୋଲଡ଼ର ସୃଷ୍ଟି କରିବାରେ ତ୍ରୁଟି ପରିଲକ୍ଷିତ ହେଉଛି। ନିଜେ ସ୍ଥିର କରିଥିବା ଫୋଲଡର ରେ ରଖ। ଫୋଲ୍‌ଡର୍‌ ଚୟନ କରନ୍ତୁ ଥିମ ଭାଷା - %s\nPost - %s\nPosts + %s\n ପୋଷ୍ଟ + %s\nପୋଷ୍ଟସ - %s Post - %s Posts + %s ପୋଷ୍ଟ + %s ପୋଷ୍ଟସ %s\nFollower @@ -55,10 +56,10 @@ %s\nଅନୁସରଣ କରୁଛନ୍ତି ଭିଡ଼ିଓ ସ୍ୱତଃ ଚାଲୁ କର - Continue videos in background - Do not pause videos when the app is out of focus + ପ୍ରଚ୍ଛଦପଟରେ ଭିଡିଓ ଜାରି ରଖନ୍ତୁ | + ଆପ୍ ଫୋକସ୍ ବାହାରେ ଥିବାବେଳେ ଭିଡିଓଗୁଡିକୁ pause କରନ୍ତୁ ନାହିଁ | ସର୍ବଦା ଭିଡ଼ିଓକୁ ଶବ୍ଦହୀନ ରଖ - Always show post captions + ସର୍ବଦା ପୋଷ୍ଟ କ୍ୟାପସନ୍ ଦେଖାନ୍ତୁ | ଡାଉନଲୋଡ଼ କରିବା ପାଇଁ ଚୟନ କରନ୍ତୁ ସମ୍ପ୍ରତି ସମସ୍ତ ଆଲବମ @@ -67,8 +68,7 @@ ଧୈର୍ଯ୍ୟ ରଖନ୍ତୁ! ପୋଷ୍ଟ ଦେଖନ୍ତୁ ପୋଷ୍ଟ ଦେଖନ୍ତୁ - Spotify - ମତଦାନ କରନ୍ତୁ | + Poll ମତଦାନ ସଫଳ! ଆପଣ ଭୋଟ୍‌ ଦେଇସାରିଛନ୍ତି! ଜବାବ ଦେବା @@ -85,8 +85,10 @@ ସ୍ଲାଇଡର୍ ଆପଣ ଉତ୍ତର ଦେଇସାରିଛନ୍ତି! ଉଲ୍ଲେଖଗୁ‌‍‌‌‌‌‌‌‌‌‌‌‌ଡିକ + Question ଏହି ଏକାଉଣ୍ଟ ଗୁପ୍ତ ଅଟେ ଆପଣ ଅନୁସରଣ ନ କଲେ ପୋଷ୍ଟଗୁଡିକୁ ପ୍ରବେଶ କରିବାକୁ ସମର୍ଥ ହେବେ ନାହିଁ! ଆପଣ ନିଶ୍ଚିତ କି? + ଆପଣ ନିଶ୍ଚିତ ତ? ଆପଣ ନିମ୍ନ - ଡାହାଣ କୋଣରେ ଅଧିକ -> ଆକାଉଣ୍ଟ୍ ମାଧ୍ୟମରେ ଲଗ୍ ଇନ୍ କରିପାରିବେ କିମ୍ବା ଆପଣ ଲଗ୍ଇନ୍ ବିନା ସର୍ବସାଧାରଣ ଆକାଉଣ୍ଟ୍ ଦେଖିପାରିବେ |! ଏହି ଆକାଉଣ୍ଟରେ କୌଣସି ପୋଷ୍ଟ ନାହିଁ | ଏପରି କୌଣସି ପୋଷ୍ଟ ନାହିଁ! @@ -96,23 +98,23 @@ ସମସ୍ତ ଏକାଉଣ୍ଟ ହଟାନ୍ତୁ ଏହା ଆପରୁ ସମସ୍ତ ଯୋଡା ଯାଇଥିବା ଖାତାଗୁଡ଼ିକୁ ଅପସାରଣ କରିବ! \n କେବଳ ଗୋଟିଏ ଖାତା ଅପସାରଣ କରିବା ପାଇଁ ଆକାଉଣ୍ଟ୍ ସୁଇଚର୍ ରୁ ଆକାଉଣ୍ଟକୁ ଲମ୍ବା ଟ୍ୟାପ୍ କରନ୍ତୁ |\nଆପଣ ସମସ୍ତ ଯୋଡା ଯାଇଥିବା ଖାତାଗୁଡ଼ିକୁ ଅପସାରଣ କରିବାକୁ ନିଶ୍ଚିତ ତ? ଦିନାଙ୍କ ସ୍ୱରୂପ - Create new collection - Edit collection name - Delete collection - Are you sure you want to delete this collection? - All contained media will remain in other collections. - Add to collection... - Remove from collection + ନୂତନ ସଂଗ୍ରହ ସୃଷ୍ଟି କରନ୍ତୁ + ସଂଗ୍ରହଟିର ନାମ ବଦଳାନ୍ତୁ + ସଂଗ୍ରହ ହଟାନ୍ତୁ + କଣ ଆପଣ ଏହି ସଂଗ୍ରହକୁ ବିଲୋପ କରିବାକୁ ନିଶ୍ଚିତ? + ଏହା ଧାରଣ କରିଥିବା ସମସ୍ତ ମିଡିଆ ଅନ୍ୟ ସଂଗ୍ରହରେ ରହିବ | + Add to collection… + ସଂଗ୍ରହରୁ କାଢନ୍ତୁ ପସନ୍ଦ କରିଛନ୍ତି ସଞ୍ଚୟ ହେଲା ଟ୍ୟାଗ୍ କରିଛନ୍ତି ସନ୍ଦେଶ - Bookmark - Follow - Unfollow - Favorite - Block - Unblock + ବୁକମାର୍କ କରନ୍ତୁ + ଫୋଲୋ କରନ୍ତୁ + ଅନୁସରଣ କରନ୍ତୁ ନାହିଁ + ପସନ୍ଦିତ + ଅବରୋଧ କରନ୍ତୁ | + ଅବରୋଧ ହଟାନ୍ତୁ Restrict Unrestrict Mute stories @@ -180,9 +182,9 @@ Screenshotted Cannot deliver Unseen count response is null! - Message... + Message… Press and hold to record audio - Updating... + Updating… Leave chat Leave this chat? Kick @@ -194,6 +196,7 @@ Downloads posts directly to the phone! Fetching post(s) Download completed + Preparing to download… Downloading post… Downloading media Downloading profile picture @@ -318,7 +321,7 @@ Comment Layout Feed stories - Opening post... + Opening post… Share Layout style Column count @@ -486,7 +489,7 @@ The previously selected folder does not exist now: Re-select the directory or select a new directory by clicking the button below. No folder selected! - Please choose a directory from your storage, not a category on the sidebar. + Please choose a directory from your storage, not a category on the sidebar.\n(%s) Success! Please wait. Starting app… Barinsta folder Top @@ -496,7 +499,7 @@ Click to show full like count No profile pic found! Are you sure you want to open this link? - Sending... + Sending… Share via DM Share link… Slide to Cancel diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 217dce32..9a157ce7 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -19,6 +19,7 @@ Ulubione Odkrywaj Komentarze + Odpowiedzi Aktywność Sprawdź aktualizacje przy starcie Blokuj zrzuty ekranu & podgląd aplikacji @@ -29,7 +30,7 @@ Ukryj wyciszone relacje z kanału Oznacz wiadomość jako przeczytaną Inni użytkownicy będą wiedzieli, że to wyświetliłeś - Autoplay video stories + Automatyczne odtwarzanie plików wideo Włącz powiadomienia o aktywności Sortowanie relacji Błąd ładowania profilu! Czy nazwa użytkownika jest prawidłowa? Jeśli tak, możesz być ograniczony. @@ -73,8 +74,7 @@ Bądź cierpliwy! Zobacz post Zobacz post - Spotify - Głosuj + Poll Zagłosowano pomyślnie! Już głosowałeś! Odpowiedz @@ -93,8 +93,10 @@ Suwak Już odpowiedziałeś! Wzmianki + Question To konto jest prywatne Nie będziesz mieć dostępu do postów po anulowaniu obserwowania! Jesteś pewny? + Jesteś pewien? Możesz zalogować się za pomocą \"Więcej\" - > \"Konto\" w prawym dolnym rogu lub możesz zobaczyć konta publiczne bez logowania! To konto nie zawiera postów Nie ma więcej postów! @@ -109,7 +111,7 @@ Usunąć kolekcję Czy na pewno chcesz usunąć tę kolekcję? Wszystkie media pozostaną w innych kolekcjach. - Dodaj do kolekcji... + Add to collection… Usuń z kolekcji Polubione Zapisane @@ -188,9 +190,9 @@ Dokonano zrzutu ekranu Nie dostarczono Unseen count response is null! - Wiadomość... + Message… Naciśnij i przytrzymaj, aby nagrać dźwięk - Aktualizowanie... + Updating… Opuść czat Opuścić ten czat? Wyrzuć @@ -202,6 +204,7 @@ Pobiera posty bezpośrednio na telefon! Pobieranie post(ów) Pobieranie zakończone + Przygotowywanie do pobrania… Pobieranie wpisu… Pobieranie multimediów Pobieranie zdjęcia profilowego @@ -326,7 +329,7 @@ Komentarz Układ Relacje - Otwieranie wpisu... + Opening post… Udostępnij Styl układu Liczba kolumn @@ -504,7 +507,7 @@ Poprzednio wybrany folder nie istnieje: Wybierz ponownie folder lub wybierz now klikając przycisk poniżej. Nie wybrano folderu! - Please choose a directory from your storage, not a category on the sidebar. + Wybierz katalog z pamięci, a nie kategorię na pasku bocznym.\n(%s) Sukces! Proszę czekać. Uruchamianie aplikacji… Folder Barinsta Najlepsze @@ -514,7 +517,7 @@ Kliknij, aby wyświetlić pełną liczbę polubień Nie znaleziono zdjęcia profilowego! Czy na pewno chcesz otworzyć ten link? - Wysyłanie... + Sending… Udostępnij przez PW Udostępnij link… Przesuń, by anulować diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 092fdffc..1f9a39eb 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -19,6 +19,7 @@ Favoritos Descobrir Comentários + Respostas Atividade Verificar se há atualizações ao iniciar Bloquear capturas de tela e a pré-visualização do aplicativo @@ -67,8 +68,7 @@ Tenha paciência! Ver Publicação Ver Publicação - Spotify - Votar + Enquete Voto enviado! Você já votou! Responder @@ -85,8 +85,10 @@ Deslizante Você já respondeu! Menções + Pergunta Esta conta é privada Você não será capaz de acessar as publicações após deixar de seguir! Tem certeza? + Tem certeza? Você pode fazer login em Mais -> Conta no canto inferior direito ou você pode ver as contas públicas sem fazer login! Esta conta não tem publicações Não há publicações! @@ -101,7 +103,7 @@ Excluir coleção Tem certeza que quer excluir essa coleção? Toda a mídia contida permanecerá em outras coleções. - Adicionar à coleção... + Adicionar à coleção… Remover da coleção Curtiu Salvo @@ -180,9 +182,9 @@ Capturado Não pode ser entregue Resposta do contador de não lidas é nula! - Mensagem... + Mensagem… Aperte e segure para gravar um áudio - Atualizando... + Atualizando… Sair da conversa Sair deste chat? Expulsar @@ -194,6 +196,7 @@ Baixar as publicações diretamente no telefone! Buscando publicação(ões) Download concluído + Preparando o download… Baixando publicação… Baixando mídia Baixando foto do perfil @@ -318,7 +321,7 @@ Comentar Visualização Feed de stories - Abrindo publicação... + Abrindo publicação… Compartilhar Estilo do layout Número de colunas @@ -486,7 +489,7 @@ A pasta selecionada anteriormente não existe agora: Selecione novamente o diretório ou selecione um novo diretório clicando no botão abaixo. Nenhuma pasta selecionada! - Please choose a directory from your storage, not a category on the sidebar. + Por favor, escolha um diretório do seu armazenamento, não uma categoria na barra lateral.\n(%s) Sucesso! Por favor, aguarde. Iniciando o aplicativo… Pasta Barinsta Início @@ -496,7 +499,7 @@ Clique para mostrar a contagem completa de curtidas Nenhuma foto de perfil encontrada! Tem certeza que quer abrir este link? - Enviando... + Enviando… Compartilhar via DM Compartilhar link… Deslize para Cancelar diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 4c71160a..07b10200 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -19,6 +19,7 @@ Избранное Подборка Комментарии + Replies Активность Проверять наличие обновлений при запуске Блокировать скриншоты & превью приложения @@ -29,7 +30,7 @@ Скрыть заглушённые истории из ленты новостей Отметить ЛС как увиденные после просмотра Другие участники узнают, что вы просмотрели его - Autoplay video stories + Автозапуск видео историй Включить уведомления об активности Сортировка историй ленты Ошибка при загрузке профиля! Верно ли имя пользователя? Если это так, то ваш запрос мог быть ограничен сервером. @@ -73,8 +74,7 @@ Будьте терпеливы! Посмотреть публикацию Посмотреть публикацию - Spotify - Голосовать + Poll Голосование успешно! Вы уже проголосовали! Отреагировать @@ -93,8 +93,10 @@ Ползунок Вы уже ответили! Упоминания + Question Это частная учётная запись Вы не сможете получить доступ к публикациям после отписки! Вы уверены? + Are you sure? Вы можете войти через Ещё -> Учётная запись в правом нижнем углу или можете просматривать публичные учётные записи без авторизации! У этой учётной записи нет публикаций Нет таких публикаций! @@ -109,7 +111,7 @@ Удалить коллекцию Вы действительно хотите удалить эту коллекцию? Всё медиа содержимое сохранится в других коллекциях. - Добавить в коллекцию... + Add to collection… Удалить из коллекции Понравилось Сохранено @@ -188,9 +190,9 @@ Сделан снимок экрана Не удаётся доставить Количество непрочитанных сообщений неизвестно! - Сообщение... + Message… Нажмите и удерживайте, чтобы записать звук - Обновление... + Updating… Покинуть чат Покинуть эту беседу? Выгнать @@ -202,6 +204,7 @@ Скачивает публикации прямо на телефон! Получение публикации(ий) Скачивание завершено + Preparing to download… Скачивание публикации… Загрузка медиафайлов Загрузка изображения профиля @@ -293,11 +296,11 @@ Неизвестный Удалено из избранного! Резервное копирование & Восстановление - Auto Backup - Starting from Android 6, Android\'s Auto Backup feature will upload all app settings, account login data, and favorites onto your Google Drive, which can be restored by reinstalling the app after uninstallation. - This preference has no effect if Google Play Services is not present, or if Auto Backup is disabled from your device settings. Disabling here does not erase existing backups. - Enable Auto Backup - Manual Backup + Автобэкап + Начиная с Android 6, функция автоматического резервного копирования Android будет загружать все настройки приложения, данные для входа в аккаунт, и избранное на диск Google, которые можно восстановить, переустановив приложение после удаления. + Эта настройка не имеет эффекта, если службы Google Play отсутствуют, или если Автобэкап отключен в настройках вашего устройства. Отключение не стирает существующие резервные копии. + Включить Автобэкап + Ручной бэкап Резервное копирование настроек приложения Barinsta, учётных данных, и/или избранного в виде обычного текста или зашифрованного файла резервной копии для последующего восстановления. Если вы делаете резервное копирование данных для входа в учётную запись, считайте файл конфиденциальным и храните его где-то в безопасности! Создать новый файл резервной копии @@ -326,7 +329,7 @@ Комментировать Формат Истории ленты новостей - Открытие публикации... + Opening post… Поделиться Внешний вид Количество столбцов @@ -504,7 +507,7 @@ Выбранная ранее папка не существует: Выберите папку заново, нажав на кнопку ниже. Папка не выбрана! - Please choose a directory from your storage, not a category on the sidebar. + Please choose a directory from your storage, not a category on the sidebar.\n(%s) Успех! Пожалуйста, подождите. Приложение запускается… Папка Barinsta В лидерах @@ -514,7 +517,7 @@ Нажмите, чтобы показать полное число лайков Картинка профиля не найдена! Вы действительно хотите открыть эту ссылку? - Отправляю... + Sending… Поделиться в ЛС Поделиться ссылкой… Сдвиньте для отмены diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 36722b21..5164bf75 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -19,6 +19,7 @@ Obľúbené Prezerať Komentáre + Replies Aktivita Kontrolovať aktualizácie pri štarte Block screenshots & app preview @@ -73,8 +74,7 @@ Buďte trpezlivý! Zobraziť príspevok Zobraziť príspevok - Spotify - Hlasovať + Poll Hlasovanie bolo úspešné! Už si hlasoval! Odpovedať @@ -93,8 +93,10 @@ Posúvač Už si hlasoval! Zmienky + Question Tento účet je súkromný Nebudeš môcť pristúpiť k príspevkom po zrušení sledovania. Si si istý? + Are you sure? Môžete sa prihlásiť pomocou Viac -> Účet na dolnom pravom rohu alebo pokračovať v sledovaní verejných profilov bez prihlásenia! Tento účet nemá žiadne príspevky Žiadne príspevky! @@ -109,7 +111,7 @@ Vymazať kolekciu Si si istý že chceš zmazať túto kolekciu? All contained media will remain in other collections. - Add to collection... + Add to collection… Remove from collection Obľúbené Uložené @@ -188,9 +190,9 @@ Urobená snímka obrazovky Nedá sa odoslať Unseen count response is null! - Message... + Message… Press and hold to record audio - Updating... + Updating… Opustiť chat Opustiť tento chat? Vykopnúť @@ -202,6 +204,7 @@ Stiahnuť príspevky priamo do telefónu! Získavam príspevky Sťahovanie bolo dokončené + Preparing to download… Sťahujem príspevok… Sťahujem médiá Sťahujem profilovú fotku @@ -326,7 +329,7 @@ Komentovať Rozloženie Príbehy - Otváram príspevok... + Opening post… Zdieľať Štýl rozloženia Počet stĺpcov @@ -504,7 +507,7 @@ The previously selected folder does not exist now: Re-select the directory or select a new directory by clicking the button below. No folder selected! - Please choose a directory from your storage, not a category on the sidebar. + Please choose a directory from your storage, not a category on the sidebar.\n(%s) Success! Please wait. Starting app… Barinsta folder Top @@ -514,7 +517,7 @@ Click to show full like count No profile pic found! Are you sure you want to open this link? - Sending... + Sending… Share via DM Share link… Slide to Cancel diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 5b3302b8..0114f7d3 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -19,6 +19,7 @@ Favoriter Upptäck Kommentarer + Replies Aktivitet Sök efter uppdatering vid uppstart Blockera skärmdumpar & förhandsgranskning av appen @@ -67,8 +68,7 @@ Håll dig lugn! Visa inlägg Visa inlägg - Spotify - Rösta + Poll Röstningen lyckades! Du har redan röstat! Svara @@ -85,8 +85,10 @@ Slider Du har redan svarat! Omnämnanden + Question Det här kontot är Privat You won\'t be able to access posts after unfollowing! Are you sure? + Are you sure? You can log in via More -> Account on the bottom-right corner or you can view public accounts without login! Det här kontot har inga inlägg Inga sådana inlägg! @@ -101,7 +103,7 @@ Ta bort samling Är du säker på att du vill ta bort den här samlingen? All contained media will remain in other collections. - Lägg till i samlingen... + Add to collection… Ta bort från samling Gillade Sparade @@ -180,9 +182,9 @@ Screenshotted Kan inte leverera Unseen count response is null! - Meddelande... + Message… Tryck och håll in för att spela in ljud - Uppdaterar... + Updating… Lämna chatt Lämna den här chatten? Sparka @@ -194,6 +196,7 @@ Laddar ner inläggen direkt till telefonen! Hämtar inlägg(en) Nerladdningen är färdig + Preparing to download… Laddar ner inlägg… Laddar ner media Laddar ner profilbild @@ -318,7 +321,7 @@ Kommentera Layout Feed stories - Öppnar inlägget... + Opening post… Dela Layout style Column count @@ -486,7 +489,7 @@ The previously selected folder does not exist now: Re-select the directory or select a new directory by clicking the button below. No folder selected! - Please choose a directory from your storage, not a category on the sidebar. + Please choose a directory from your storage, not a category on the sidebar.\n(%s) Success! Please wait. Starting app… Barinsta folder Topp @@ -496,7 +499,7 @@ Click to show full like count No profile pic found! Are you sure you want to open this link? - Sending... + Sending… Share via DM Share link… Slide to Cancel diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index ca6cf993..c146b1d2 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -19,6 +19,7 @@ Favoriler Keşfet Yorumlar + Replies Hareketler Güncellemeleri başlangıçta kontrol et Block screenshots & app preview @@ -67,8 +68,7 @@ Sabırlı ol! Gönderiyi Gör Gönderiyi Gör - Spotify - Oyla + Poll Oylama başarılı! Zaten oy verdiniz! Yanıt ver @@ -85,8 +85,10 @@ Kaydırıcı Zaten cevapladınız! Etiketlenenler + Question Bu Hesap Gizlidir Takipten çıkarsan gönderilere erişemeyeceksin! Emin misin? + Are you sure? Hesabınıza giriş yapmak için sağ alttan Daha Fazla -> Hesap yolunu takip edebilir ya da giriş yapmadan herkese açık profilleri dolaşabilirsin! Bu Hesabın Gönderisi Yok Öyle Bir Gönderi Yok! @@ -101,7 +103,7 @@ Koleksiyonu sil Bu koleksiyonu silmek istediğinden emin misin? İçerilen tüm medya diğer koleksiyonlarda kalacak. - Koleksiyon ekle... + Add to collection… Koleksiyondan kaldır Beğenilenler Kaydedilenler @@ -180,9 +182,9 @@ Ekran görüntüsü alındı İletilemiyor Unseen count response is null! - Mesaj... + Message… Ses kaydetmek için dokun ve basılı tut - Güncelleniyor... + Updating… Sohbetten ayrıl Bu sohbetten ayrıl? Dışarıya At @@ -194,6 +196,7 @@ Gönderileri direkt telefona indir! Gönderi(ler) getiriliyor İndirme tamamlandı + Preparing to download… Gönderi indiriliyor… Medya indiriliyor Profil fotoğrafı indiriliyor @@ -318,7 +321,7 @@ Yorum yap Yerleşim Hikaye akışı - Gönderi açılıyor... + Opening post… Paylaş Yerleşim biçimi Sütun sayısı @@ -486,7 +489,7 @@ The previously selected folder does not exist now: Re-select the directory or select a new directory by clicking the button below. No folder selected! - Please choose a directory from your storage, not a category on the sidebar. + Please choose a directory from your storage, not a category on the sidebar.\n(%s) Success! Please wait. Starting app… Barinsta folder Top @@ -496,7 +499,7 @@ Click to show full like count No profile pic found! Are you sure you want to open this link? - Sending... + Sending… Share via DM Share link… Slide to Cancel diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 747dcabe..e23ab8f0 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -19,6 +19,7 @@ Yêu thích Khám phá Bình luận + Replies Hoạt động Kiểm tra cập nhật khi khởi động Chặn chụp ảnh màn hình & xem trước ứng dụng @@ -64,8 +65,7 @@ Hãy kiên nhẫn! Xem Bài đăng Xem Bài đăng - Spotify - Bình chọn + Poll Bình chọn thành công! Bạn đã bình chọn rồi! Trả lời @@ -81,8 +81,10 @@ Thanh trượt Bạn đã trả lời rồi! Đề cập + Question Tài khoản này là riêng tư Bạn sẽ không thể truy cập vào bài đăng sau khi bỏ theo dõi? Bạn chắc chứ? + Are you sure? Bạn có thể đăng nhập bằng Thêm -> Tài khoản ở góc phải dưới hoặc bạn có thể xem tài khoản công khai mà không cần đăng nhập! Tài khoản này không có bài viết nào Không có bài đăng đó! @@ -97,7 +99,7 @@ Xoá bộ sưu tập Bạn có chắc muốn xoá bộ sưu tập này chứ? Tất cả những phương tiện đã lưu sẽ được giữ lại trong bộ sưu tập khác. - Thêm vào bộ sưu tập... + Add to collection… Xoá khỏi bộ sưu tập Đã thích Đã lưu @@ -176,9 +178,9 @@ Đã chụp màn hình Không thể gửi Đánh đấu chưa xem không có giá trị! - Tin nhắn... + Message… Chạm và giữ để ghi âm - Đang cập nhật... + Updating… Rời khỏi trò chuyện Rời khỏi nhóm này? Đá @@ -190,6 +192,7 @@ Tải bài viết xuống trực tiếp về điện thoại! Đang nạp bài viết Tải xuống hoàn tất + Preparing to download… Đang tải xuống bài viết… Đang tải xuống phương tiện Đang tải xuống ảnh đại diện @@ -314,7 +317,7 @@ Bình luận Giao diện Story trên bảng tin - Đang mở bài đăng... + Opening post… Chia sẻ Phong cach giao diện Số cột @@ -477,7 +480,7 @@ The previously selected folder does not exist now: Re-select the directory or select a new directory by clicking the button below. No folder selected! - Please choose a directory from your storage, not a category on the sidebar. + Please choose a directory from your storage, not a category on the sidebar.\n(%s) Success! Please wait. Starting app… Barinsta folder Hàng đầu @@ -487,7 +490,7 @@ Click to show full like count No profile pic found! Are you sure you want to open this link? - Sending... + Sending… Share via DM Share link… Slide to Cancel diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 076f9ebf..0a3d00b6 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -19,6 +19,7 @@ 最爱 发现 留言 + 回复 动态 启动时检查更新 屏蔽截图及应用预览 @@ -64,8 +65,7 @@ 请耐心等待! 查看帖子 查看帖子 - Spotify - 投票 + 投票 投票成功! 您已投票过了! 回复 @@ -81,8 +81,10 @@ 滑块 您已回答过了! 提及 + 问题 私密账户 取消关注后,您将无法访问帖子!您确定吗? + 您确定吗? 你可以通过右下角的 更多-> 账户 来登录,或者您无须登录即可查看公开账户! 暂未发帖 无此类帖! @@ -97,7 +99,7 @@ 删除收藏夹 确定想删除这个收藏夹吗? 所有包含的媒体将留在其他收藏夹中。 - 加入收藏夹... + 添加到收藏集… 从收藏夹中移除 已赞 已保存 @@ -176,9 +178,9 @@ 已截屏 无法发送 未读计数错误! - 撰写消息... + 撰写消息… 长按即可录制语音消息 - 刷新中... + 刷新中… 离开聊天 离开此聊天吗? 移除成员 @@ -190,6 +192,7 @@ 直接下载至手机! 读取帖子 下载完成 + 正在准备下载... 帖子下载中… 媒体下载中 大头贴下载中 @@ -314,7 +317,7 @@ 评论 布局 快拍动态 - 正在打开帖子... + 正在打开帖子… 分享 布局样式 列数 @@ -477,7 +480,7 @@ 之前选择的文件夹已不存在: 请点击下面的按钮,来重新选择该目录或选择一个新目录。 未选择文件夹! - 请选择设备存储中的一个目录,而不是侧边栏上的类别。 + 请选择设备存储中的一个目录,而不是侧边栏上的类别。\n(%s) 设置成功!请稍候。应用程序启动中… Barinsta 文件夹 热门 @@ -487,7 +490,7 @@ 点击显示完整点赞计数 未找到用户头像! 您确定要打开此链接吗? - 发送中... + 正在发送… 分享至私信 分享链接… 滑动以取消 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index a23a0f16..e4c6bbb4 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -19,6 +19,7 @@ 收藏 探索 評論 + Replies 動態 啟動時檢查更新 阻擋截圖及應用程式預覽 @@ -64,8 +65,7 @@ 耐心一點! 查看文章 查看文章 - Spotify - 投票 + Poll 投票成功! 您已經投過票了! 回覆 @@ -81,8 +81,10 @@ 滑桿 您已經回答了! 提及 + Question 此帳戶為私人帳戶 取消關注後,您將無法閱讀人家的貼文!你確定嗎? + Are you sure? 您可以透過右下角的 「更多」 -> 「新增帳號」 來登入您的帳號,或者以匿名的身分查看公共帳戶! 此帳戶尚未張貼任何文章 找不到此貼文! @@ -97,7 +99,7 @@ 刪除收藏 確定刪除此收藏? 所有包含的多媒體將會保留於其他收藏夾中。 - 新增至收藏... + Add to collection… 從收藏中移除 已按讚 已儲存 @@ -176,9 +178,9 @@ 已截圖 無法傳送 未讀計數錯誤! - 撰寫訊息... + Message… 長按即可錄製語音訊息 - 更新中... + Updating… 離開聊天 離開此聊天室? 踢除 @@ -190,6 +192,7 @@ 將貼文直接下載到手機中! 獲取文章中 下載完成 + Preparing to download… 下載貼文中… 下載媒體中 下載個人圖片中 @@ -314,7 +317,7 @@ 評論 佈局 限時動態 - 開啟貼文中… + Opening post… 分享 佈局樣式 欄位數量 @@ -477,7 +480,7 @@ The previously selected folder does not exist now: Re-select the directory or select a new directory by clicking the button below. No folder selected! - Please choose a directory from your storage, not a category on the sidebar. + Please choose a directory from your storage, not a category on the sidebar.\n(%s) Success! Please wait. Starting app… Barinsta folder 熱門 @@ -487,7 +490,7 @@ 點選以顯示完整的按讚數量 找不到大頭貼照 您確定要打開此連結嗎? - 傳送中… + Sending… 透過私訊分享 分享連結 Slide to Cancel From df8c7295958008a86d25b6f71bf21327000106f8 Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Tue, 6 Jul 2021 10:16:13 -0400 Subject: [PATCH 06/14] let github remove label action logs --- .github/workflows/label-bugs.yml | 5 +++++ .github/workflows/label-duplicates.yml | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/.github/workflows/label-bugs.yml b/.github/workflows/label-bugs.yml index 6bbb86e8..0400f16a 100644 --- a/.github/workflows/label-bugs.yml +++ b/.github/workflows/label-bugs.yml @@ -16,3 +16,8 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} issue-number: ${{ github.event.issue.number }} labels: 'bug' + - name: Remove runs + uses: GongT/cancel-previous-workflows@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DELETE: true diff --git a/.github/workflows/label-duplicates.yml b/.github/workflows/label-duplicates.yml index bffe2a54..26ed4de3 100644 --- a/.github/workflows/label-duplicates.yml +++ b/.github/workflows/label-duplicates.yml @@ -16,3 +16,8 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} issue-number: ${{ github.event.issue.number }} labels: 'duplicate' + - name: Remove runs + uses: GongT/cancel-previous-workflows@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DELETE: true From cdeedc378402627a79a89ec7b43d937d7e0eab7d Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Tue, 6 Jul 2021 10:19:59 -0400 Subject: [PATCH 07/14] vs code didn't remove the tabs?! --- .github/workflows/label-bugs.yml | 2 +- .github/workflows/label-duplicates.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/label-bugs.yml b/.github/workflows/label-bugs.yml index 0400f16a..ad4d191a 100644 --- a/.github/workflows/label-bugs.yml +++ b/.github/workflows/label-bugs.yml @@ -20,4 +20,4 @@ jobs: uses: GongT/cancel-previous-workflows@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - DELETE: true + DELETE: true diff --git a/.github/workflows/label-duplicates.yml b/.github/workflows/label-duplicates.yml index 26ed4de3..4cc35774 100644 --- a/.github/workflows/label-duplicates.yml +++ b/.github/workflows/label-duplicates.yml @@ -20,4 +20,4 @@ jobs: uses: GongT/cancel-previous-workflows@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - DELETE: true + DELETE: true From cade09aceb329ed2803b79484f9351c6b89aafd8 Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Tue, 6 Jul 2021 11:23:53 -0400 Subject: [PATCH 08/14] remove SHOW_CAPTION setting --- .../fragments/settings/PostPreferencesFragment.java | 10 ---------- .../instagrabber/fragments/settings/PreferenceKeys.kt | 2 +- .../java/awais/instagrabber/utils/SettingsHelper.kt | 2 +- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/PostPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/PostPreferencesFragment.java index b1db2e40..4c91407f 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/PostPreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/PostPreferencesFragment.java @@ -18,7 +18,6 @@ public class PostPreferencesFragment extends BasePreferencesFragment { // generalCategory.addPreference(getAutoPlayVideosPreference(context)); screen.addPreference(getBackgroundPlayPreference(context)); screen.addPreference(getAlwaysMuteVideosPreference(context)); - screen.addPreference(getShowCaptionPreference(context)); screen.addPreference(getToggleKeywordFilterPreference(context)); screen.addPreference(getEditKeywordFilterPreference(context)); } @@ -48,15 +47,6 @@ public class PostPreferencesFragment extends BasePreferencesFragment { return preference; } - private Preference getShowCaptionPreference(@NonNull final Context context) { - final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context); - preference.setKey(PreferenceKeys.SHOW_CAPTIONS); - preference.setDefaultValue(true); - preference.setTitle(R.string.post_viewer_show_captions); - preference.setIconSpaceReserved(false); - return preference; - } - private Preference getToggleKeywordFilterPreference(@NonNull final Context context) { final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context); preference.setKey(PreferenceKeys.TOGGLE_KEYWORD_FILTER); diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.kt b/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.kt index a2a9649d..415bd829 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.kt +++ b/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.kt @@ -32,7 +32,7 @@ object PreferenceKeys { const val PLAY_IN_BACKGROUND = "play_in_background" const val AUTOPLAY_VIDEOS_STORIES = "autoplay_videos" const val MUTED_VIDEOS = "muted_videos" - const val SHOW_CAPTIONS = "show_captions" +// const val SHOW_CAPTIONS = "show_captions" const val CUSTOM_DATE_TIME_FORMAT_ENABLED = "data_time_custom_enabled" const val SWAP_DATE_TIME_FORMAT_ENABLED = "swap_date_time_enabled" const val MARK_AS_SEEN = "mark_as_seen" diff --git a/app/src/main/java/awais/instagrabber/utils/SettingsHelper.kt b/app/src/main/java/awais/instagrabber/utils/SettingsHelper.kt index 57f93af3..8c6905cd 100755 --- a/app/src/main/java/awais/instagrabber/utils/SettingsHelper.kt +++ b/app/src/main/java/awais/instagrabber/utils/SettingsHelper.kt @@ -119,7 +119,7 @@ class SettingsHelper(context: Context) { PreferenceKeys.DOWNLOAD_PREPEND_USER_NAME, PreferenceKeys.AUTOPLAY_VIDEOS_STORIES, PreferenceKeys.MUTED_VIDEOS, - PreferenceKeys.SHOW_CAPTIONS, +// PreferenceKeys.SHOW_CAPTIONS, PreferenceKeys.CUSTOM_DATE_TIME_FORMAT_ENABLED, PreferenceKeys.MARK_AS_SEEN, PreferenceKeys.DM_MARK_AS_SEEN, From 7522b5f083d0873d9d6ade33ab8add49208ef8fc Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Tue, 6 Jul 2021 11:26:24 -0400 Subject: [PATCH 09/14] finish up stories viewer UI close #537, close #1554 --- .../fragments/StoryViewerFragment.kt | 39 +++++++++++++++---- .../fragments/settings/PreferenceKeys.kt | 1 + .../settings/StoriesPreferencesFragment.java | 10 +++++ .../instagrabber/utils/SettingsHelper.kt | 1 + .../viewmodels/StoryFragmentViewModel.kt | 4 +- app/src/main/res/values/strings.xml | 2 + 6 files changed, 48 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.kt b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.kt index 121c8150..db28e689 100644 --- a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.kt +++ b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.kt @@ -240,6 +240,15 @@ class StoryViewerFragment : Fragment() { storyMedias.set(0, newItem) storiesAdapter!!.submitList(storyMedias) storiesViewModel.setMedia(0) + binding.listToggle.setEnabled(true) + binding.storiesList.setVisibility( + if (Utils.settingsHelper.getBoolean(PreferenceKeys.PREF_STORY_SHOW_LIST)) View.VISIBLE + else View.GONE + ) + } + else { + binding.listToggle.setEnabled(false) + binding.storiesList.setVisibility(View.GONE) } }) storiesViewModel.getDate().observe(fragmentActivity, { @@ -268,6 +277,12 @@ class StoryViewerFragment : Fragment() { binding.btnShare.setOnClickListener({ _ -> shareStoryViaDm() }) binding.btnReply.setOnClickListener({ _ -> createReplyDialog(null) }) binding.stickers.setOnClickListener({ _ -> showStickerMenu() }) + binding.listToggle.setOnClickListener({ _ -> + binding.storiesList.setVisibility( + if (binding.storiesList.visibility == View.GONE) View.VISIBLE + else View.GONE + ) + }) } @SuppressLint("ClickableViewAccessibility") @@ -297,10 +312,22 @@ class StoryViewerFragment : Fragment() { if (models != null) { when (it) { StoryPaginationType.FORWARD -> { - paginateStories(false, currentFeedStoryIndex == models.size - 2) + if (currentFeedStoryIndex == models.size - 1) + Toast.makeText( + context, + R.string.no_more_stories, + Toast.LENGTH_SHORT + ).show() + else paginateStories(false, currentFeedStoryIndex == models.size - 2) } StoryPaginationType.BACKWARD -> { - paginateStories(true, false) + if (currentFeedStoryIndex == 0) + Toast.makeText( + context, + R.string.no_more_stories, + Toast.LENGTH_SHORT + ).show() + else paginateStories(true, false) } StoryPaginationType.ERROR -> { Toast.makeText( @@ -356,7 +383,6 @@ class StoryViewerFragment : Fragment() { val context = context ?: return live = null if (menuProfile != null) menuProfile!!.isVisible = false - profileVisible = false binding.imageViewer.controller = null releasePlayer() val type = options!!.type @@ -435,6 +461,9 @@ class StoryViewerFragment : Fragment() { releasePlayer() + profileVisible = currentStory.user?.username != null + if (menuProfile != null) menuProfile!!.isVisible = profileVisible + binding.btnDownload.isEnabled = false binding.btnShare.isEnabled = currentStory.canReshare binding.btnReply.isEnabled = currentStory.canReply @@ -544,10 +573,6 @@ class StoryViewerFragment : Fragment() { wasCanceled: Boolean ) { binding.btnDownload.isEnabled = false - if (menuProfile != null) { - profileVisible = false - menuProfile!!.isVisible = false - } binding.progressView.visibility = View.GONE } }) diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.kt b/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.kt index 415bd829..d104c7b8 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.kt +++ b/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.kt @@ -11,6 +11,7 @@ object PreferenceKeys { const val PREF_SHOWN_COUNT_TOOLTIP = "shown_count_tooltip" const val PREF_SEARCH_FOCUS_KEYBOARD = "search_focus_keyboard" const val PREF_AUTO_BACKUP_ENABLED = "auto_backup_enabled" + const val PREF_STORY_SHOW_LIST = "story_show_list" // string prefs const val FOLDER_PATH = "custom_path" diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/StoriesPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/StoriesPreferencesFragment.java index 2983d216..62b7c0ae 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/StoriesPreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/StoriesPreferencesFragment.java @@ -19,6 +19,7 @@ public class StoriesPreferencesFragment extends BasePreferencesFragment { screen.addPreference(getHideMutedReelsPreference(context)); screen.addPreference(getMarkStoriesSeenPreference(context)); screen.addPreference(getAutoPlayPreference(context)); + screen.addPreference(getStoryListPreference(context)); } private Preference getStorySortPreference(@NonNull final Context context) { @@ -62,4 +63,13 @@ public class StoriesPreferencesFragment extends BasePreferencesFragment { preference.setIconSpaceReserved(false); return preference; } + + private Preference getStoryListPreference(@NonNull final Context context) { + final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context); + preference.setKey(PreferenceKeys.PREF_STORY_SHOW_LIST); + preference.setTitle(R.string.story_list_setting); + preference.setSummary(R.string.story_list_setting_summary); + preference.setIconSpaceReserved(false); + return preference; + } } diff --git a/app/src/main/java/awais/instagrabber/utils/SettingsHelper.kt b/app/src/main/java/awais/instagrabber/utils/SettingsHelper.kt index 8c6905cd..fdcd40b5 100755 --- a/app/src/main/java/awais/instagrabber/utils/SettingsHelper.kt +++ b/app/src/main/java/awais/instagrabber/utils/SettingsHelper.kt @@ -135,6 +135,7 @@ class SettingsHelper(context: Context) { PreferenceKeys.PLAY_IN_BACKGROUND, PreferenceKeys.PREF_SHOWN_COUNT_TOOLTIP, PreferenceKeys.PREF_SEARCH_FOCUS_KEYBOARD, + PreferenceKeys.PREF_STORY_SHOW_LIST, PreferenceKeys.PREF_AUTO_BACKUP_ENABLED ) annotation class BooleanSettings diff --git a/app/src/main/java/awais/instagrabber/viewmodels/StoryFragmentViewModel.kt b/app/src/main/java/awais/instagrabber/viewmodels/StoryFragmentViewModel.kt index 8fe4d0ab..a1040ca2 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/StoryFragmentViewModel.kt +++ b/app/src/main/java/awais/instagrabber/viewmodels/StoryFragmentViewModel.kt @@ -28,7 +28,7 @@ import kotlinx.coroutines.launch class StoryFragmentViewModel : ViewModel() { // large data - private val currentStory = MutableLiveData() + private val currentStory = MutableLiveData() private val currentMedia = MutableLiveData() // small data @@ -181,7 +181,7 @@ class StoryFragmentViewModel : ViewModel() { /* get functions */ - fun getCurrentStory(): LiveData { + fun getCurrentStory(): LiveData { return currentStory } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5b17f849..3d934a96 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -32,6 +32,8 @@ Mark DM as seen after viewing Other members will know you viewed it Autoplay video stories + Display story list by default + For viewing stories Enable activity notifications Feed stories sort Error loading profile! Is the username valid? If so, you may be ratelimited. From cd1b1d5da2d7ba947db750ae86f4e6e0156581ca Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Tue, 6 Jul 2021 12:35:19 -0400 Subject: [PATCH 10/14] some profile viewmodel refactoring --- .../fragments/main/ProfileFragment.kt | 8 ---- .../viewmodels/ProfileFragmentViewModel.kt | 45 +++++++------------ 2 files changed, 17 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt index b043e0f0..56df4e5b 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt +++ b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt @@ -304,15 +304,7 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall viewModel = ViewModelProvider( this, ProfileFragmentViewModelFactory( - csrfToken, - deviceUuid, - UserRepository.getInstance(), - FriendshipRepository.getInstance(), - StoriesRepository.getInstance(), - MediaRepository.getInstance(), - GraphQLRepository.getInstance(), FavoriteRepository.getInstance(requireContext()), - DirectMessagesRepository.getInstance(), if (isLoggedIn) DirectMessagesManager else null, this, arguments diff --git a/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt b/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt index 5f4a0678..81541c40 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt +++ b/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt @@ -16,9 +16,12 @@ import awais.instagrabber.repositories.responses.User import awais.instagrabber.repositories.responses.UserProfileContextLink import awais.instagrabber.repositories.responses.directmessages.RankedRecipient import awais.instagrabber.repositories.responses.stories.Story +import awais.instagrabber.utils.Constants import awais.instagrabber.utils.ControlledRunner import awais.instagrabber.utils.Event +import awais.instagrabber.utils.getCsrfTokenFromCookie import awais.instagrabber.utils.SingleRunner +import awais.instagrabber.utils.Utils import awais.instagrabber.utils.extensions.TAG import awais.instagrabber.utils.extensions.isReallyPrivate import awais.instagrabber.viewmodels.ProfileFragmentViewModel.ProfileAction.* @@ -31,18 +34,20 @@ import java.time.LocalDateTime class ProfileFragmentViewModel( private val state: SavedStateHandle, - private val csrfToken: String?, - private val deviceUuid: String?, - private val userRepository: UserRepository, - private val friendshipRepository: FriendshipRepository, - private val storiesRepository: StoriesRepository, - private val mediaRepository: MediaRepository, - private val graphQLRepository: GraphQLRepository, - private val favoriteRepository: FavoriteRepository, - private val directMessagesRepository: DirectMessagesRepository, + private val favoriteRepository: FavoriteRepository?, private val messageManager: DirectMessagesManager?, ioDispatcher: CoroutineDispatcher, ) : ViewModel() { + private val cookie: String = Utils.settingsHelper.getString(Constants.COOKIE) + private val csrfToken: String? = getCsrfTokenFromCookie(cookie) + private val deviceUuid: String = Utils.settingsHelper.getString(Constants.DEVICE_UUID) + private val userRepository: UserRepository by lazy { UserRepository.getInstance() } + private val friendshipRepository: FriendshipRepository by lazy { FriendshipRepository.getInstance() } + private val storiesRepository: StoriesRepository by lazy { StoriesRepository.getInstance() } + private val mediaRepository: MediaRepository by lazy { MediaRepository.getInstance() } + private val graphQLRepository: GraphQLRepository by lazy { GraphQLRepository.getInstance() } + private val directMessagesRepository: DirectMessagesRepository by lazy { DirectMessagesRepository.getInstance() } + private val _currentUser = MutableLiveData>(Resource.loading(null)) private val _isFavorite = MutableLiveData(false) private val profileAction = MutableLiveData(INIT) @@ -238,7 +243,7 @@ class ProfileFragmentViewModel( private suspend fun checkAndUpdateFavorite(fetchedUser: User) { try { - val favorite = favoriteRepository.getFavorite(fetchedUser.username, FavoriteType.USER) + val favorite = favoriteRepository!!.getFavorite(fetchedUser.username, FavoriteType.USER) if (favorite == null) { _isFavorite.postValue(false) return @@ -286,7 +291,7 @@ class ProfileFragmentViewModel( viewModelScope.launch(Dispatchers.IO) { toggleFavoriteControlledRunner.afterPrevious { try { - val favorite = favoriteRepository.getFavorite(username, FavoriteType.USER) + val favorite = favoriteRepository!!.getFavorite(username, FavoriteType.USER) if (favorite == null) { // insert favoriteRepository.insertOrUpdateFavorite( @@ -592,15 +597,7 @@ class ProfileFragmentViewModel( @Suppress("UNCHECKED_CAST") class ProfileFragmentViewModelFactory( - private val csrfToken: String?, - private val deviceUuid: String?, - private val userRepository: UserRepository, - private val friendshipRepository: FriendshipRepository, - private val storiesRepository: StoriesRepository, - private val mediaRepository: MediaRepository, - private val graphQLRepository: GraphQLRepository, - private val favoriteRepository: FavoriteRepository, - private val directMessagesRepository: DirectMessagesRepository, + private val favoriteRepository: FavoriteRepository?, private val messageManager: DirectMessagesManager?, owner: SavedStateRegistryOwner, defaultArgs: Bundle? = null, @@ -612,15 +609,7 @@ class ProfileFragmentViewModelFactory( ): T { return ProfileFragmentViewModel( handle, - csrfToken, - deviceUuid, - userRepository, - friendshipRepository, - storiesRepository, - mediaRepository, - graphQLRepository, favoriteRepository, - directMessagesRepository, messageManager, Dispatchers.IO, ) as T From 86b6df315c519a4fe824bdf088bd25def7b10e34 Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Tue, 6 Jul 2021 14:34:20 -0400 Subject: [PATCH 11/14] *sigh* --- .github/workflows/label-bugs.yml | 2 +- .github/workflows/label-duplicates.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/label-bugs.yml b/.github/workflows/label-bugs.yml index ad4d191a..0b1e2963 100644 --- a/.github/workflows/label-bugs.yml +++ b/.github/workflows/label-bugs.yml @@ -20,4 +20,4 @@ jobs: uses: GongT/cancel-previous-workflows@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - DELETE: true + DELETE: 'yes' diff --git a/.github/workflows/label-duplicates.yml b/.github/workflows/label-duplicates.yml index 4cc35774..4cfd39f5 100644 --- a/.github/workflows/label-duplicates.yml +++ b/.github/workflows/label-duplicates.yml @@ -20,4 +20,4 @@ jobs: uses: GongT/cancel-previous-workflows@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - DELETE: true + DELETE: 'yes' From 379468d57785076ebcf998c69828f55a5ec103c6 Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Tue, 6 Jul 2021 15:08:10 -0400 Subject: [PATCH 12/14] re-implement viewing highlights --- .../fragments/StoryViewerFragment.kt | 169 +++++++++--------- .../fragments/main/ProfileFragment.kt | 2 +- .../viewmodels/HighlightsViewModel.java | 19 -- .../viewmodels/ProfileFragmentViewModel.kt | 15 +- 4 files changed, 89 insertions(+), 116 deletions(-) delete mode 100644 app/src/main/java/awais/instagrabber/viewmodels/HighlightsViewModel.java diff --git a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.kt b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.kt index db28e689..b3512d22 100644 --- a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.kt +++ b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.kt @@ -18,7 +18,9 @@ import androidx.appcompat.widget.PopupMenu import androidx.core.view.GestureDetectorCompat import androidx.fragment.app.Fragment import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.LiveData import androidx.lifecycle.Observer +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.navigation.NavController @@ -29,6 +31,7 @@ import awais.instagrabber.R import awais.instagrabber.adapters.StoriesAdapter import awais.instagrabber.customviews.helpers.SwipeGestureListener import awais.instagrabber.databinding.FragmentStoryViewerBinding +import awais.instagrabber.fragments.main.ProfileFragment import awais.instagrabber.fragments.settings.PreferenceKeys import awais.instagrabber.interfaces.SwipeEvent import awais.instagrabber.models.Resource @@ -38,14 +41,13 @@ import awais.instagrabber.models.enums.StoryPaginationType import awais.instagrabber.repositories.requests.StoryViewerOptions import awais.instagrabber.repositories.responses.directmessages.RankedRecipient import awais.instagrabber.repositories.responses.stories.* -import awais.instagrabber.utils.* +import awais.instagrabber.utils.Constants import awais.instagrabber.utils.DownloadUtils.download import awais.instagrabber.utils.TextUtils.epochSecondToString +import awais.instagrabber.utils.ResponseBodyUtils +import awais.instagrabber.utils.Utils import awais.instagrabber.utils.extensions.TAG -import awais.instagrabber.viewmodels.ArchivesViewModel -import awais.instagrabber.viewmodels.FeedStoriesViewModel -import awais.instagrabber.viewmodels.HighlightsViewModel -import awais.instagrabber.viewmodels.StoryFragmentViewModel +import awais.instagrabber.viewmodels.* import awais.instagrabber.webservices.MediaRepository import awais.instagrabber.webservices.StoriesRepository import com.facebook.drawee.backends.pipeline.Fresco @@ -68,10 +70,8 @@ import java.util.* class StoryViewerFragment : Fragment() { private val TAG = "StoryViewerFragment" - private val cookie = Utils.settingsHelper.getString(Constants.COOKIE) private var root: View? = null private var currentStoryUsername: String? = null - private var highlightTitle: String? = null private var storiesAdapter: StoriesAdapter? = null private var swipeEvent: SwipeEvent? = null private var gestureDetector: GestureDetectorCompat? = null @@ -84,10 +84,7 @@ class StoryViewerFragment : Fragment() { private var actionBarTitle: String? = null private var actionBarSubtitle: String? = null - private var fetching = false - private val sticking = false private var shouldRefresh = true - private var dmVisible = false private var currentFeedStoryIndex = 0 private var sliderValue = 0.0 private var options: StoryViewerOptions? = null @@ -95,6 +92,7 @@ class StoryViewerFragment : Fragment() { private var backStackSavedStateResultLiveData: MutableLiveData? = null private lateinit var fragmentActivity: AppCompatActivity private lateinit var storiesViewModel: StoryFragmentViewModel + private lateinit var appStateViewModel: AppStateViewModel private lateinit var binding: FragmentStoryViewerBinding @Suppress("UNCHECKED_CAST") @@ -123,6 +121,7 @@ class StoryViewerFragment : Fragment() { super.onCreate(savedInstanceState) fragmentActivity = requireActivity() as AppCompatActivity storiesViewModel = ViewModelProvider(this).get(StoryFragmentViewModel::class.java) + appStateViewModel = ViewModelProvider(fragmentActivity).get(AppStateViewModel::class.java) setHasOptionsMenu(true) } @@ -152,12 +151,7 @@ class StoryViewerFragment : Fragment() { menuProfile!!.isVisible = profileVisible } - override fun onPrepareOptionsMenu(menu: Menu) { - // hide menu items from activity - } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - val context = context ?: return false val itemId = item.itemId if (itemId == R.id.action_profile) { val username = storiesViewModel.getCurrentStory().value?.user?.username @@ -205,18 +199,18 @@ class StoryViewerFragment : Fragment() { val type = options!!.type if (currentFeedStoryIndex >= 0) { listViewModel = when (type) { - StoryViewerOptions.Type.HIGHLIGHT -> ViewModelProvider(fragmentActivity).get( - HighlightsViewModel::class.java - ) - StoryViewerOptions.Type.STORY_ARCHIVE -> ViewModelProvider(fragmentActivity).get( - ArchivesViewModel::class.java - ) - StoryViewerOptions.Type.FEED_STORY_POSITION -> ViewModelProvider(fragmentActivity).get( - FeedStoriesViewModel::class.java - ) - else -> ViewModelProvider(fragmentActivity).get( - FeedStoriesViewModel::class.java - ) + StoryViewerOptions.Type.HIGHLIGHT -> { + val pArgs = Bundle() + pArgs.putString("username", options!!.name) + ViewModelProvider( + this, ProfileFragmentViewModelFactory(null, null, this, pArgs) + ).get(ProfileFragmentViewModel::class.java) + } + StoryViewerOptions.Type.STORY_ARCHIVE -> + ViewModelProvider(fragmentActivity).get(ArchivesViewModel::class.java) + StoryViewerOptions.Type.FEED_STORY_POSITION -> + ViewModelProvider(fragmentActivity).get(FeedStoriesViewModel::class.java) + else -> ViewModelProvider(fragmentActivity).get(FeedStoriesViewModel::class.java) } } setupButtons() @@ -228,7 +222,7 @@ class StoryViewerFragment : Fragment() { val context = context ?: return binding.storiesList.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) - storiesAdapter = StoriesAdapter { model: StoryMedia?, position: Int -> + storiesAdapter = StoriesAdapter { _, position -> storiesViewModel.setMedia(position) } binding.storiesList.adapter = storiesAdapter @@ -240,15 +234,13 @@ class StoryViewerFragment : Fragment() { storyMedias.set(0, newItem) storiesAdapter!!.submitList(storyMedias) storiesViewModel.setMedia(0) - binding.listToggle.setEnabled(true) - binding.storiesList.setVisibility( - if (Utils.settingsHelper.getBoolean(PreferenceKeys.PREF_STORY_SHOW_LIST)) View.VISIBLE - else View.GONE - ) + binding.listToggle.isEnabled = true + binding.storiesList.visibility = if (Utils.settingsHelper.getBoolean(PreferenceKeys.PREF_STORY_SHOW_LIST)) View.VISIBLE + else View.GONE } else { - binding.listToggle.setEnabled(false) - binding.storiesList.setVisibility(View.GONE) + binding.listToggle.isEnabled = false + binding.storiesList.visibility = View.GONE } }) storiesViewModel.getDate().observe(fragmentActivity, { @@ -266,8 +258,6 @@ class StoryViewerFragment : Fragment() { storiesViewModel.getOptions().observe(fragmentActivity, { binding.stickers.isEnabled = it.first.size > 0 }) - - resetView() } private fun setupButtons() { @@ -278,72 +268,78 @@ class StoryViewerFragment : Fragment() { binding.btnReply.setOnClickListener({ _ -> createReplyDialog(null) }) binding.stickers.setOnClickListener({ _ -> showStickerMenu() }) binding.listToggle.setOnClickListener({ _ -> - binding.storiesList.setVisibility( - if (binding.storiesList.visibility == View.GONE) View.VISIBLE - else View.GONE - ) + binding.storiesList.visibility = if (binding.storiesList.visibility == View.GONE) View.VISIBLE + else View.GONE }) } @SuppressLint("ClickableViewAccessibility") private fun setupListeners() { - val hasFeedStories: Boolean - var models: List? = null + var liveModels: LiveData?>? = null if (currentFeedStoryIndex >= 0) { val type = options!!.type when (type) { StoryViewerOptions.Type.HIGHLIGHT -> { - val highlightsViewModel = listViewModel as HighlightsViewModel? - models = highlightsViewModel!!.list.value + val profileFragmentViewModel = listViewModel as ProfileFragmentViewModel? + appStateViewModel.currentUserLiveData.observe( + viewLifecycleOwner, profileFragmentViewModel!!::setCurrentUser + ) + profileFragmentViewModel.currentUserProfileActionLiveData.observe(viewLifecycleOwner) {} + profileFragmentViewModel.userHighlights.observe(viewLifecycleOwner) {} + liveModels = profileFragmentViewModel.highlights } StoryViewerOptions.Type.FEED_STORY_POSITION -> { val feedStoriesViewModel = listViewModel as FeedStoriesViewModel? - models = feedStoriesViewModel!!.list.value + liveModels = feedStoriesViewModel!!.list } StoryViewerOptions.Type.STORY_ARCHIVE -> { val archivesViewModel = listViewModel as ArchivesViewModel? - models = archivesViewModel!!.list.value + liveModels = archivesViewModel!!.list } } } - hasFeedStories = models != null && !models.isEmpty() - - storiesViewModel.getPagination().observe(fragmentActivity, { - if (models != null) { - when (it) { - StoryPaginationType.FORWARD -> { - if (currentFeedStoryIndex == models.size - 1) + if (liveModels != null) liveModels.observe(viewLifecycleOwner, { models -> + Log.d("austin_debug", "models (observer): " + models) + storiesViewModel.getPagination().observe(fragmentActivity, { + if (models != null) { + when (it) { + StoryPaginationType.FORWARD -> { + if (currentFeedStoryIndex == models.size - 1) + Toast.makeText( + context, + R.string.no_more_stories, + Toast.LENGTH_SHORT + ).show() + else paginateStories(false, currentFeedStoryIndex == models.size - 2) + } + StoryPaginationType.BACKWARD -> { + if (currentFeedStoryIndex == 0) + Toast.makeText( + context, + R.string.no_more_stories, + Toast.LENGTH_SHORT + ).show() + else paginateStories(true, false) + } + StoryPaginationType.ERROR -> { Toast.makeText( context, - R.string.no_more_stories, + R.string.downloader_unknown_error, Toast.LENGTH_SHORT ).show() - else paginateStories(false, currentFeedStoryIndex == models.size - 2) + } } - StoryPaginationType.BACKWARD -> { - if (currentFeedStoryIndex == 0) - Toast.makeText( - context, - R.string.no_more_stories, - Toast.LENGTH_SHORT - ).show() - else paginateStories(true, false) - } - StoryPaginationType.ERROR -> { - Toast.makeText( - context, - R.string.downloader_unknown_error, - Toast.LENGTH_SHORT - ).show() - } - StoryPaginationType.DO_NOTHING -> { - } // do nothing } + }) + if (models != null && !models.isEmpty()) { + binding.btnBackward.isEnabled = currentFeedStoryIndex != 0 + binding.btnForward.isEnabled = currentFeedStoryIndex != models.size - 1 + resetView() } }) val context = context ?: return - swipeEvent = label@ SwipeEvent { isRightSwipe: Boolean -> + swipeEvent = SwipeEvent { isRightSwipe: Boolean -> storiesViewModel.paginate(isRightSwipe) } gestureDetector = GestureDetectorCompat(context, SwipeGestureListener(swipeEvent)) @@ -370,13 +366,7 @@ class StoryViewerFragment : Fragment() { return false } } - if (hasFeedStories) { - binding.btnBackward.isEnabled = currentFeedStoryIndex != 0 - binding.btnForward.isEnabled = currentFeedStoryIndex != models!!.size - 1 - } binding.imageViewer.setTapListener(simpleOnGestureListener) - - // process stickers } private fun resetView() { @@ -389,15 +379,14 @@ class StoryViewerFragment : Fragment() { var fetchOptions: StoryViewerOptions? = null when (type) { StoryViewerOptions.Type.HIGHLIGHT -> { - val highlightsViewModel = listViewModel as HighlightsViewModel? - val models = highlightsViewModel!!.list.value + val profileFragmentViewModel = listViewModel as ProfileFragmentViewModel? + val models = profileFragmentViewModel!!.highlights.value + Log.d("austin_debug", "models (resetView): " + models) if (models == null || models.isEmpty() || currentFeedStoryIndex >= models.size || currentFeedStoryIndex < 0) { - Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT) - .show() + Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show() return } - val (id, _, _, _, _, _, _, _, _, title) = models[currentFeedStoryIndex] - fetchOptions = StoryViewerOptions.forHighlight(id) + fetchOptions = StoryViewerOptions.forHighlight(models[currentFeedStoryIndex].id) } StoryViewerOptions.Type.FEED_STORY_POSITION -> { val feedStoriesViewModel = listViewModel as FeedStoriesViewModel? @@ -434,7 +423,9 @@ class StoryViewerFragment : Fragment() { return } storiesViewModel.fetchStory(fetchOptions).observe(fragmentActivity, { - // toast error if necessary? + if (it.status == Resource.Status.ERROR) { + Toast.makeText(context, "Error: " + it.message, Toast.LENGTH_SHORT).show() + } }) } @@ -578,7 +569,7 @@ class StoryViewerFragment : Fragment() { }) player!!.setMediaSource(mediaSource) player!!.prepare() - binding.playerView.setOnClickListener { v: View? -> + binding.playerView.setOnClickListener { _ -> if (player != null) { if (player!!.playbackState == Player.STATE_ENDED) player!!.seekTo(0) player!!.playWhenReady = diff --git a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt index 56df4e5b..21f0d5d9 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt +++ b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt @@ -843,7 +843,7 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall private fun setupHighlights() { val context = context ?: return highlightsAdapter = HighlightsAdapter { model, position -> - val options = StoryViewerOptions.forHighlight(model.title) + val options = StoryViewerOptions.forHighlight(model.user?.username) options.currentFeedStoryIndex = position val action = ProfileFragmentDirections.actionProfileFragmentToStoryViewerFragment(options) NavHostFragment.findNavController(this).navigate(action) diff --git a/app/src/main/java/awais/instagrabber/viewmodels/HighlightsViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/HighlightsViewModel.java deleted file mode 100644 index ccc8b843..00000000 --- a/app/src/main/java/awais/instagrabber/viewmodels/HighlightsViewModel.java +++ /dev/null @@ -1,19 +0,0 @@ -package awais.instagrabber.viewmodels; - -import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.ViewModel; - -import java.util.List; - -import awais.instagrabber.repositories.responses.stories.Story; - -public class HighlightsViewModel extends ViewModel { - private MutableLiveData> list; - - public MutableLiveData> getList() { - if (list == null) { - list = new MutableLiveData<>(); - } - return list; - } -} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt b/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt index 81541c40..ffbf5b90 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt +++ b/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt @@ -218,6 +218,7 @@ class ProfileFragmentViewModel( } } } + val highlights: LiveData?> = userHighlights.map { it.data } private suspend fun fetchUser( currentUser: User?, @@ -326,7 +327,7 @@ class ProfileFragmentViewModel( val currentUserId = currentUser.value?.data?.pk ?: return@afterPrevious val targetUserId = profile.value?.data?.pk ?: return@afterPrevious val csrfToken = csrfToken ?: return@afterPrevious - val deviceUuid = deviceUuid ?: return@afterPrevious + val deviceUuid = deviceUuid if (following) { if (!confirmed) { _eventLiveData.postValue(Event(ShowConfirmUnfollowDialog)) @@ -365,7 +366,7 @@ class ProfileFragmentViewModel( val currentUserId = currentUser.value?.data?.pk ?: return@afterPrevious val targetUserId = profile.value?.data?.pk ?: return@afterPrevious val csrfToken = csrfToken ?: return@afterPrevious - val deviceUuid = deviceUuid ?: return@afterPrevious + val deviceUuid = deviceUuid val username = profile.value?.data?.username ?: return@afterPrevious val thread = directMessagesRepository.createThread( csrfToken, @@ -399,7 +400,7 @@ class ProfileFragmentViewModel( val profile = profile.value?.data ?: return@afterPrevious friendshipRepository.toggleRestrict( csrfToken ?: return@afterPrevious, - deviceUuid ?: return@afterPrevious, + deviceUuid, profile.pk, !(profile.friendshipStatus?.isRestricted ?: false), ) @@ -421,7 +422,7 @@ class ProfileFragmentViewModel( friendshipRepository.changeBlock( csrfToken ?: return@afterPrevious, currentUser.value?.data?.pk ?: return@afterPrevious, - deviceUuid ?: return@afterPrevious, + deviceUuid, profile.friendshipStatus?.blocking ?: return@afterPrevious, profile.pk ) @@ -443,7 +444,7 @@ class ProfileFragmentViewModel( friendshipRepository.changeMute( csrfToken ?: return@afterPrevious, currentUser.value?.data?.pk ?: return@afterPrevious, - deviceUuid ?: return@afterPrevious, + deviceUuid, profile.friendshipStatus?.isMutingReel ?: return@afterPrevious, profile.pk, true @@ -466,7 +467,7 @@ class ProfileFragmentViewModel( friendshipRepository.changeMute( csrfToken ?: return@afterPrevious, currentUser.value?.data?.pk ?: return@afterPrevious, - deviceUuid ?: return@afterPrevious, + deviceUuid, profile.friendshipStatus?.muting ?: return@afterPrevious, profile.pk, false @@ -488,7 +489,7 @@ class ProfileFragmentViewModel( friendshipRepository.removeFollower( csrfToken ?: return@afterPrevious, currentUser.value?.data?.pk ?: return@afterPrevious, - deviceUuid ?: return@afterPrevious, + deviceUuid, profile.value?.data?.pk ?: return@afterPrevious ) profileAction.postValue(REFRESH_FRIENDSHIP) From 7e9e3b0fbf1be152f982c4299c7e07610a677780 Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Tue, 6 Jul 2021 15:30:37 -0400 Subject: [PATCH 13/14] re-implement viewing individual user stories --- .../instagrabber/fragments/StoryViewerFragment.kt | 13 ++++++++++--- .../instagrabber/fragments/main/ProfileFragment.kt | 2 +- .../instagrabber/repositories/StoriesService.kt | 2 +- .../viewmodels/ProfileFragmentViewModel.kt | 1 + .../instagrabber/webservices/StoriesRepository.kt | 2 +- 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.kt b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.kt index b3512d22..6d0d4fdd 100644 --- a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.kt +++ b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.kt @@ -199,7 +199,7 @@ class StoryViewerFragment : Fragment() { val type = options!!.type if (currentFeedStoryIndex >= 0) { listViewModel = when (type) { - StoryViewerOptions.Type.HIGHLIGHT -> { + StoryViewerOptions.Type.HIGHLIGHT, StoryViewerOptions.Type.USER -> { val pArgs = Bundle() pArgs.putString("username", options!!.name) ViewModelProvider( @@ -288,6 +288,15 @@ class StoryViewerFragment : Fragment() { profileFragmentViewModel.userHighlights.observe(viewLifecycleOwner) {} liveModels = profileFragmentViewModel.highlights } + StoryViewerOptions.Type.USER -> { + val profileFragmentViewModel = listViewModel as ProfileFragmentViewModel? + appStateViewModel.currentUserLiveData.observe( + viewLifecycleOwner, profileFragmentViewModel!!::setCurrentUser + ) + profileFragmentViewModel.currentUserProfileActionLiveData.observe(viewLifecycleOwner) {} + profileFragmentViewModel.userStories.observe(viewLifecycleOwner) {} + liveModels = profileFragmentViewModel.stories + } StoryViewerOptions.Type.FEED_STORY_POSITION -> { val feedStoriesViewModel = listViewModel as FeedStoriesViewModel? liveModels = feedStoriesViewModel!!.list @@ -299,7 +308,6 @@ class StoryViewerFragment : Fragment() { } } if (liveModels != null) liveModels.observe(viewLifecycleOwner, { models -> - Log.d("austin_debug", "models (observer): " + models) storiesViewModel.getPagination().observe(fragmentActivity, { if (models != null) { when (it) { @@ -381,7 +389,6 @@ class StoryViewerFragment : Fragment() { StoryViewerOptions.Type.HIGHLIGHT -> { val profileFragmentViewModel = listViewModel as ProfileFragmentViewModel? val models = profileFragmentViewModel!!.highlights.value - Log.d("austin_debug", "models (resetView): " + models) if (models == null || models.isEmpty() || currentFeedStoryIndex >= models.size || currentFeedStoryIndex < 0) { Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show() return diff --git a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt index 21f0d5d9..ab731719 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt +++ b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt @@ -932,7 +932,7 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall val action = ProfileFragmentDirections.actionProfileFragmentToStoryViewerFragment( StoryViewerOptions.forUser( viewModel.profile.value?.data?.pk ?: return, - viewModel.profile.value?.data?.fullName ?: return, + viewModel.profile.value?.data?.username ?: return, ) ) NavHostFragment.findNavController(this).navigate(action) diff --git a/app/src/main/java/awais/instagrabber/repositories/StoriesService.kt b/app/src/main/java/awais/instagrabber/repositories/StoriesService.kt index f7793767..c8920d74 100644 --- a/app/src/main/java/awais/instagrabber/repositories/StoriesService.kt +++ b/app/src/main/java/awais/instagrabber/repositories/StoriesService.kt @@ -29,7 +29,7 @@ interface StoriesService { suspend fun getStories(@Path("type") type: String, @Path("id") id: String): ReelsResponse @GET("/api/v1/feed/user/{id}/story/") - suspend fun getUserStories(@Path("id") id: String): ReelsResponse + suspend fun getUserStories(@Path("id") id: Long): ReelsResponse @FormUrlEncoded @POST("/api/v1/media/{storyId}/{stickerId}/{action}/") diff --git a/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt b/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt index ffbf5b90..013cfd48 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt +++ b/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt @@ -187,6 +187,7 @@ class ProfileFragmentViewModel( } } } + val stories: LiveData?> = userStories.map { if (it.data == null) listOf() else listOf(it.data) } private val highlightsFetchControlledRunner = ControlledRunner?>() val userHighlights: LiveData?>> = currentUserProfileActionLiveData.switchMap { currentUserAndProfilePair -> diff --git a/app/src/main/java/awais/instagrabber/webservices/StoriesRepository.kt b/app/src/main/java/awais/instagrabber/webservices/StoriesRepository.kt index e3713df1..205983c9 100644 --- a/app/src/main/java/awais/instagrabber/webservices/StoriesRepository.kt +++ b/app/src/main/java/awais/instagrabber/webservices/StoriesRepository.kt @@ -74,7 +74,7 @@ open class StoriesRepository(private val service: StoriesService) { response.reels?.get(options.name) } StoryViewerOptions.Type.USER -> { - val response = service.getUserStories(options.id.toString()) + val response = service.getUserStories(options.id) response.reel } // should not reach beyond this point From fef2552bdfa618711977413965ea4146e2e28e4b Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Tue, 6 Jul 2021 16:56:45 -0400 Subject: [PATCH 14/14] actually support highlights and user stories properly, meaning not trying to init another ProfileFragmentViewModel --- .../fragments/StoryViewerFragment.kt | 44 +++++-------------- .../fragments/main/ProfileFragment.kt | 2 +- .../requests/StoryViewerOptions.java | 4 +- .../viewmodels/ProfileFragmentViewModel.kt | 2 - .../viewmodels/StoriesViewModel.java | 19 -------- .../viewmodels/StoryFragmentViewModel.kt | 17 +++++++ 6 files changed, 32 insertions(+), 56 deletions(-) delete mode 100644 app/src/main/java/awais/instagrabber/viewmodels/StoriesViewModel.java diff --git a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.kt b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.kt index 6d0d4fdd..40362e0c 100644 --- a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.kt +++ b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.kt @@ -47,7 +47,9 @@ import awais.instagrabber.utils.TextUtils.epochSecondToString import awais.instagrabber.utils.ResponseBodyUtils import awais.instagrabber.utils.Utils import awais.instagrabber.utils.extensions.TAG -import awais.instagrabber.viewmodels.* +import awais.instagrabber.viewmodels.ArchivesViewModel +import awais.instagrabber.viewmodels.FeedStoriesViewModel +import awais.instagrabber.viewmodels.StoryFragmentViewModel import awais.instagrabber.webservices.MediaRepository import awais.instagrabber.webservices.StoriesRepository import com.facebook.drawee.backends.pipeline.Fresco @@ -92,7 +94,6 @@ class StoryViewerFragment : Fragment() { private var backStackSavedStateResultLiveData: MutableLiveData? = null private lateinit var fragmentActivity: AppCompatActivity private lateinit var storiesViewModel: StoryFragmentViewModel - private lateinit var appStateViewModel: AppStateViewModel private lateinit var binding: FragmentStoryViewerBinding @Suppress("UNCHECKED_CAST") @@ -121,7 +122,6 @@ class StoryViewerFragment : Fragment() { super.onCreate(savedInstanceState) fragmentActivity = requireActivity() as AppCompatActivity storiesViewModel = ViewModelProvider(this).get(StoryFragmentViewModel::class.java) - appStateViewModel = ViewModelProvider(fragmentActivity).get(AppStateViewModel::class.java) setHasOptionsMenu(true) } @@ -191,26 +191,18 @@ class StoryViewerFragment : Fragment() { } private fun init() { - val args = arguments - if (args == null) return + val args = arguments ?: return val fragmentArgs = StoryViewerFragmentArgs.fromBundle(args) options = fragmentArgs.options currentFeedStoryIndex = options!!.currentFeedStoryIndex val type = options!!.type if (currentFeedStoryIndex >= 0) { listViewModel = when (type) { - StoryViewerOptions.Type.HIGHLIGHT, StoryViewerOptions.Type.USER -> { - val pArgs = Bundle() - pArgs.putString("username", options!!.name) - ViewModelProvider( - this, ProfileFragmentViewModelFactory(null, null, this, pArgs) - ).get(ProfileFragmentViewModel::class.java) - } StoryViewerOptions.Type.STORY_ARCHIVE -> ViewModelProvider(fragmentActivity).get(ArchivesViewModel::class.java) StoryViewerOptions.Type.FEED_STORY_POSITION -> ViewModelProvider(fragmentActivity).get(FeedStoriesViewModel::class.java) - else -> ViewModelProvider(fragmentActivity).get(FeedStoriesViewModel::class.java) + else -> null } } setupButtons() @@ -280,22 +272,8 @@ class StoryViewerFragment : Fragment() { val type = options!!.type when (type) { StoryViewerOptions.Type.HIGHLIGHT -> { - val profileFragmentViewModel = listViewModel as ProfileFragmentViewModel? - appStateViewModel.currentUserLiveData.observe( - viewLifecycleOwner, profileFragmentViewModel!!::setCurrentUser - ) - profileFragmentViewModel.currentUserProfileActionLiveData.observe(viewLifecycleOwner) {} - profileFragmentViewModel.userHighlights.observe(viewLifecycleOwner) {} - liveModels = profileFragmentViewModel.highlights - } - StoryViewerOptions.Type.USER -> { - val profileFragmentViewModel = listViewModel as ProfileFragmentViewModel? - appStateViewModel.currentUserLiveData.observe( - viewLifecycleOwner, profileFragmentViewModel!!::setCurrentUser - ) - profileFragmentViewModel.currentUserProfileActionLiveData.observe(viewLifecycleOwner) {} - profileFragmentViewModel.userStories.observe(viewLifecycleOwner) {} - liveModels = profileFragmentViewModel.stories + storiesViewModel.fetchHighlights(options!!.id) + liveModels = storiesViewModel.getHighlights() } StoryViewerOptions.Type.FEED_STORY_POSITION -> { val feedStoriesViewModel = listViewModel as FeedStoriesViewModel? @@ -305,6 +283,9 @@ class StoryViewerFragment : Fragment() { val archivesViewModel = listViewModel as ArchivesViewModel? liveModels = archivesViewModel!!.list } + StoryViewerOptions.Type.USER -> { + resetView() + } } } if (liveModels != null) liveModels.observe(viewLifecycleOwner, { models -> @@ -387,13 +368,12 @@ class StoryViewerFragment : Fragment() { var fetchOptions: StoryViewerOptions? = null when (type) { StoryViewerOptions.Type.HIGHLIGHT -> { - val profileFragmentViewModel = listViewModel as ProfileFragmentViewModel? - val models = profileFragmentViewModel!!.highlights.value + val models = storiesViewModel.getHighlights().value if (models == null || models.isEmpty() || currentFeedStoryIndex >= models.size || currentFeedStoryIndex < 0) { Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show() return } - fetchOptions = StoryViewerOptions.forHighlight(models[currentFeedStoryIndex].id) + fetchOptions = StoryViewerOptions.forHighlight(0L, models[currentFeedStoryIndex].id) } StoryViewerOptions.Type.FEED_STORY_POSITION -> { val feedStoriesViewModel = listViewModel as FeedStoriesViewModel? diff --git a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt index ab731719..c8dd85e2 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt +++ b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt @@ -843,7 +843,7 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall private fun setupHighlights() { val context = context ?: return highlightsAdapter = HighlightsAdapter { model, position -> - val options = StoryViewerOptions.forHighlight(model.user?.username) + val options = StoryViewerOptions.forHighlight(model.user!!.pk, "") options.currentFeedStoryIndex = position val action = ProfileFragmentDirections.actionProfileFragmentToStoryViewerFragment(options) NavHostFragment.findNavController(this).navigate(action) diff --git a/app/src/main/java/awais/instagrabber/repositories/requests/StoryViewerOptions.java b/app/src/main/java/awais/instagrabber/repositories/requests/StoryViewerOptions.java index 8202ec7d..7a82a298 100644 --- a/app/src/main/java/awais/instagrabber/repositories/requests/StoryViewerOptions.java +++ b/app/src/main/java/awais/instagrabber/repositories/requests/StoryViewerOptions.java @@ -45,8 +45,8 @@ public class StoryViewerOptions implements Serializable { return new StoryViewerOptions(id, name, Type.USER); } - public static StoryViewerOptions forHighlight(final String highlight) { - return new StoryViewerOptions(highlight, Type.HIGHLIGHT); + public static StoryViewerOptions forHighlight(final long id, final String highlight) { + return new StoryViewerOptions(id, highlight, Type.HIGHLIGHT); } public static StoryViewerOptions forStory(final long mediaId, final String username) { diff --git a/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt b/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt index 013cfd48..3acd3e66 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt +++ b/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt @@ -187,7 +187,6 @@ class ProfileFragmentViewModel( } } } - val stories: LiveData?> = userStories.map { if (it.data == null) listOf() else listOf(it.data) } private val highlightsFetchControlledRunner = ControlledRunner?>() val userHighlights: LiveData?>> = currentUserProfileActionLiveData.switchMap { currentUserAndProfilePair -> @@ -219,7 +218,6 @@ class ProfileFragmentViewModel( } } } - val highlights: LiveData?> = userHighlights.map { it.data } private suspend fun fetchUser( currentUser: User?, diff --git a/app/src/main/java/awais/instagrabber/viewmodels/StoriesViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/StoriesViewModel.java deleted file mode 100644 index 0f512403..00000000 --- a/app/src/main/java/awais/instagrabber/viewmodels/StoriesViewModel.java +++ /dev/null @@ -1,19 +0,0 @@ -package awais.instagrabber.viewmodels; - -import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.ViewModel; - -import java.util.List; - -import awais.instagrabber.repositories.responses.stories.StoryMedia; - -public class StoriesViewModel extends ViewModel { - private MutableLiveData> list; - - public MutableLiveData> getList() { - if (list == null) { - list = new MutableLiveData<>(); - } - return list; - } -} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/viewmodels/StoryFragmentViewModel.kt b/app/src/main/java/awais/instagrabber/viewmodels/StoryFragmentViewModel.kt index a1040ca2..d463c251 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/StoryFragmentViewModel.kt +++ b/app/src/main/java/awais/instagrabber/viewmodels/StoryFragmentViewModel.kt @@ -59,6 +59,9 @@ class StoryFragmentViewModel : ViewModel() { private val storiesRepository: StoriesRepository by lazy { StoriesRepository.getInstance() } private val mediaRepository: MediaRepository by lazy { MediaRepository.getInstance() } + // for highlights ONLY + private val highlights = MutableLiveData?>() + /* set functions */ fun setStory(story: Story) { @@ -181,6 +184,10 @@ class StoryFragmentViewModel : ViewModel() { /* get functions */ + fun getHighlights(): LiveData?> { + return highlights + } + fun getCurrentStory(): LiveData { return currentStory } @@ -441,6 +448,16 @@ class StoryFragmentViewModel : ViewModel() { return data } + fun fetchHighlights(id: Long) { + viewModelScope.launch(Dispatchers.IO) { + try { + val result = storiesRepository.fetchHighlights(id) + highlights.postValue(result) + } catch (e: Exception) { + } + } + } + fun fetchSingleMedia(mediaId: Long): LiveData> { val data = MutableLiveData>() data.postValue(loading(null))