mirror of
				https://github.com/KokaKiwi/BarInsta
				synced 2025-10-30 19:15:35 +00:00 
			
		
		
		
	Merge branch 'master' into retrofit-intercept-errors
This commit is contained in:
		
						commit
						807cf91cd7
					
				| @ -66,11 +66,13 @@ android { | ||||
|             dimension "repo" | ||||
|             // versionNameSuffix "-github" // appended in assemble task | ||||
|             buildConfigField("String", "dsn", SENTRY_DSN) | ||||
|             buildConfigField("boolean", "isPre", "false") | ||||
|         } | ||||
| 
 | ||||
|         fdroid { | ||||
|             dimension "repo" | ||||
|             versionNameSuffix "-fdroid" | ||||
|             buildConfigField("boolean", "isPre", "false") | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -84,6 +86,7 @@ android { | ||||
| 
 | ||||
|             def suffix = "${versionName}-${flavor}_${builtType}" // eg. 19.1.0-github_debug or release | ||||
|             if (builtType.toString() == 'release' && project.hasProperty("pre")) { | ||||
|                 buildConfigField("boolean", "isPre", "true") | ||||
|                 // append latest commit short hash for pre-release | ||||
|                 suffix = "${versionName}.${getGitHash()}-${flavor}" // eg. 19.1.0.b123456-github | ||||
|             } | ||||
|  | ||||
| @ -0,0 +1,64 @@ | ||||
| package awais.instagrabber.utils; | ||||
| 
 | ||||
| import android.util.Log; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.appcompat.app.AppCompatActivity; | ||||
| 
 | ||||
| import org.json.JSONObject; | ||||
| 
 | ||||
| import java.net.HttpURLConnection; | ||||
| import java.net.URL; | ||||
| 
 | ||||
| public class UpdateChecker { | ||||
|     private static final Object LOCK = new Object(); | ||||
|     private static final String TAG = UpdateChecker.class.getSimpleName(); | ||||
| 
 | ||||
|     private static UpdateChecker instance; | ||||
| 
 | ||||
|     public static UpdateChecker getInstance() { | ||||
|         if (instance == null) { | ||||
|             synchronized (LOCK) { | ||||
|                 if (instance == null) { | ||||
|                     instance = new UpdateChecker(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return instance; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Needs to be called asynchronously | ||||
|      * | ||||
|      * @return the latest version from f-droid | ||||
|      */ | ||||
|     @Nullable | ||||
|     public String getLatestVersion() { | ||||
|         HttpURLConnection conn = null; | ||||
|         try { | ||||
|             conn = (HttpURLConnection) new URL("https://f-droid.org/api/v1/packages/me.austinhuang.instagrabber").openConnection(); | ||||
|             conn.setUseCaches(false); | ||||
|             conn.setRequestProperty("User-Agent", "https://Barinsta.AustinHuang.me / mailto:Barinsta@AustinHuang.me"); | ||||
|             conn.connect(); | ||||
|             final int responseCode = conn.getResponseCode(); | ||||
|             if (responseCode == HttpURLConnection.HTTP_OK) { | ||||
|                 final JSONObject data = new JSONObject(NetworkUtils.readFromConnection(conn)); | ||||
|                 return "v" + data.getJSONArray("packages").getJSONObject(0).getString("versionName"); | ||||
|                 // if (BuildConfig.VERSION_CODE < data.getInt("suggestedVersionCode")) { | ||||
|                 // } | ||||
|             } | ||||
|         } catch (final Exception e) { | ||||
|             Log.e(TAG, "", e); | ||||
|         } finally { | ||||
|             if (conn != null) { | ||||
|                 conn.disconnect(); | ||||
|             } | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     public void onDownload(@NonNull final AppCompatActivity context) { | ||||
|         Utils.openURL(context, "https://f-droid.org/packages/me.austinhuang.instagrabber/"); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,61 @@ | ||||
| package awais.instagrabber.utils; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.util.Log; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| 
 | ||||
| import java.net.HttpURLConnection; | ||||
| import java.net.URL; | ||||
| 
 | ||||
| public class UpdateChecker { | ||||
|     private static final Object LOCK = new Object(); | ||||
|     private static final String TAG = UpdateChecker.class.getSimpleName(); | ||||
| 
 | ||||
|     private static UpdateChecker instance; | ||||
| 
 | ||||
|     public static UpdateChecker getInstance() { | ||||
|         if (instance == null) { | ||||
|             synchronized (LOCK) { | ||||
|                 if (instance == null) { | ||||
|                     instance = new UpdateChecker(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return instance; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Needs to be called asynchronously | ||||
|      * | ||||
|      * @return the latest version from Github | ||||
|      */ | ||||
|     @Nullable | ||||
|     public String getLatestVersion() { | ||||
|         HttpURLConnection conn = null; | ||||
|         try { | ||||
|             conn = (HttpURLConnection) new URL("https://github.com/austinhuang0131/barinsta/releases/latest").openConnection(); | ||||
|             conn.setInstanceFollowRedirects(false); | ||||
|             conn.setUseCaches(false); | ||||
|             conn.setRequestProperty("User-Agent", "https://Barinsta.AustinHuang.me / mailto:Barinsta@AustinHuang.me"); | ||||
|             conn.connect(); | ||||
|             final int responseCode = conn.getResponseCode(); | ||||
|             if (responseCode == HttpURLConnection.HTTP_MOVED_TEMP) { | ||||
|                 return "v" + conn.getHeaderField("Location").split("/v")[1]; | ||||
|                 // return !version.equals(BuildConfig.VERSION_NAME); | ||||
|             } | ||||
|         } catch (final Exception e) { | ||||
|             Log.e(TAG, "", e); | ||||
|         } finally { | ||||
|             if (conn != null) { | ||||
|                 conn.disconnect(); | ||||
|             } | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     public void onDownload(@NonNull final Context context) { | ||||
|         Utils.openURL(context, "https://github.com/austinhuang0131/instagrabber/releases/latest"); | ||||
|     } | ||||
| } | ||||
| @ -130,6 +130,9 @@ | ||||
|                 android:name="android.support.PARENT_ACTIVITY" | ||||
|                 android:value=".activities.MainActivity" /> | ||||
|         </activity> | ||||
|         <activity | ||||
|             android:name=".utils.ProcessPhoenix" | ||||
|             android:theme="@style/Theme.AppCompat.Translucent" /> | ||||
| 
 | ||||
|         <provider | ||||
|             android:name="androidx.core.content.FileProvider" | ||||
|  | ||||
| @ -59,6 +59,7 @@ import java.util.Deque; | ||||
| import java.util.List; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| import awais.instagrabber.BuildConfig; | ||||
| import awais.instagrabber.R; | ||||
| import awais.instagrabber.adapters.SuggestionsAdapter; | ||||
| import awais.instagrabber.asyncs.PostFetcher; | ||||
| @ -156,8 +157,10 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage | ||||
|             setupBottomNavigationBar(true); | ||||
|         } | ||||
|         setupSuggestions(); | ||||
|         final boolean checkUpdates = settingsHelper.getBoolean(Constants.CHECK_UPDATES); | ||||
|         if (checkUpdates) FlavorTown.updateCheck(this); | ||||
|         if (!BuildConfig.isPre) { | ||||
|             final boolean checkUpdates = settingsHelper.getBoolean(Constants.CHECK_UPDATES); | ||||
|             if (checkUpdates) FlavorTown.updateCheck(this); | ||||
|         } | ||||
|         FlavorTown.changelogCheck(this); | ||||
|         new ViewModelProvider(this).get(AppStateViewModel.class); // Just initiate the App state here | ||||
|         final Intent intent = getIntent(); | ||||
|  | ||||
| @ -12,7 +12,6 @@ import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.appcompat.app.AlertDialog; | ||||
| import androidx.fragment.app.DialogFragment; | ||||
| import androidx.fragment.app.FragmentActivity; | ||||
| import androidx.recyclerview.widget.LinearLayoutManager; | ||||
| 
 | ||||
| import java.util.Collections; | ||||
| @ -25,8 +24,10 @@ import awais.instagrabber.db.datasources.AccountDataSource; | ||||
| import awais.instagrabber.db.entities.Account; | ||||
| import awais.instagrabber.db.repositories.AccountRepository; | ||||
| import awais.instagrabber.db.repositories.RepositoryCallback; | ||||
| import awais.instagrabber.utils.AppExecutors; | ||||
| import awais.instagrabber.utils.Constants; | ||||
| import awais.instagrabber.utils.CookieUtils; | ||||
| import awais.instagrabber.utils.ProcessPhoenix; | ||||
| import awais.instagrabber.utils.TextUtils; | ||||
| import awais.instagrabber.utils.Utils; | ||||
| 
 | ||||
| @ -55,9 +56,14 @@ public class AccountSwitcherDialogFragment extends DialogFragment { | ||||
|         } | ||||
|         CookieUtils.setupCookies(model.getCookie()); | ||||
|         settingsHelper.putString(Constants.COOKIE, model.getCookie()); | ||||
|         final FragmentActivity activity = getActivity(); | ||||
|         if (activity != null) activity.recreate(); | ||||
|         dismiss(); | ||||
|         // final FragmentActivity activity = getActivity(); | ||||
|         // if (activity != null) activity.recreate(); | ||||
|         // dismiss(); | ||||
|         AppExecutors.getInstance().mainThread().execute(() -> { | ||||
|             final Context context = getContext(); | ||||
|             if (context == null) return; | ||||
|             ProcessPhoenix.triggerRebirth(context); | ||||
|         }, 200); | ||||
|     }; | ||||
| 
 | ||||
|     private final AccountSwitcherAdapter.OnAccountLongClickListener accountLongClickListener = (model, isCurrent) -> { | ||||
|  | ||||
| @ -31,7 +31,6 @@ import awais.instagrabber.R; | ||||
| import awais.instagrabber.adapters.NotificationsAdapter; | ||||
| import awais.instagrabber.adapters.NotificationsAdapter.OnNotificationClickListener; | ||||
| import awais.instagrabber.databinding.FragmentNotificationsViewerBinding; | ||||
| import awais.instagrabber.fragments.settings.MorePreferencesFragmentDirections; | ||||
| import awais.instagrabber.models.enums.NotificationType; | ||||
| import awais.instagrabber.repositories.requests.StoryViewerOptions; | ||||
| import awais.instagrabber.repositories.responses.FriendshipChangeResponse; | ||||
| @ -79,8 +78,7 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe | ||||
|             try { | ||||
|                 binding.swipeRefreshLayout.setRefreshing(false); | ||||
|                 Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); | ||||
|             } | ||||
|             catch(Throwable e) {} | ||||
|             } catch (Throwable ignored) {} | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
| @ -93,10 +91,10 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe | ||||
|         @Override | ||||
|         public void onPreviewClick(final Notification model) { | ||||
|             final NotificationImage notificationImage = model.getArgs().getMedia().get(0); | ||||
|             final long mediaId = Long.valueOf(notificationImage.getId().split("_")[0]); | ||||
|             final long mediaId = Long.parseLong(notificationImage.getId().split("_")[0]); | ||||
|             if (model.getType() == NotificationType.RESPONDED_STORY) { | ||||
|                 final NavDirections action = NotificationsViewerFragmentDirections | ||||
|                         .actionNotificationsViewerFragmentToStoryViewerFragment( | ||||
|                         .actionNotificationsToStory( | ||||
|                                 StoryViewerOptions.forStory( | ||||
|                                         mediaId, | ||||
|                                         model.getArgs().getUsername())); | ||||
| @ -278,8 +276,7 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe | ||||
|     } | ||||
| 
 | ||||
|     private void openProfile(final String username) { | ||||
|         final NavDirections action = MorePreferencesFragmentDirections | ||||
|                 .actionGlobalProfileFragment("@" + username); | ||||
|         final NavDirections action = NotificationsViewerFragmentDirections.actionGlobalProfileFragment("@" + username); | ||||
|         NavHostFragment.findNavController(this).navigate(action); | ||||
|     } | ||||
| } | ||||
| @ -137,14 +137,14 @@ public class StoryViewerFragment extends Fragment { | ||||
|     private String[] mentions; | ||||
|     private QuizModel quiz; | ||||
|     private SliderModel slider; | ||||
|     private MenuItem menuDownload; | ||||
|     private MenuItem menuDm; | ||||
|     private MenuItem menuDownload, menuDm, menuProfile; | ||||
|     private SimpleExoPlayer player; | ||||
|     // private boolean isHashtag; | ||||
|     // private boolean isLoc; | ||||
|     // private String highlight; | ||||
|     private String actionBarTitle; | ||||
|     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; | ||||
| @ -195,8 +195,10 @@ public class StoryViewerFragment extends Fragment { | ||||
|         menuInflater.inflate(R.menu.story_menu, menu); | ||||
|         menuDownload = menu.findItem(R.id.action_download); | ||||
|         menuDm = menu.findItem(R.id.action_dms); | ||||
|         menuDownload.setVisible(false); | ||||
|         menuDm.setVisible(false); | ||||
|         menuProfile = menu.findItem(R.id.action_profile); | ||||
|         menuDownload.setVisible(downloadVisible); | ||||
|         menuDm.setVisible(dmVisible); | ||||
|         menuProfile.setVisible(profileVisible); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
| @ -215,7 +217,8 @@ public class StoryViewerFragment extends Fragment { | ||||
|             else | ||||
|                 ActivityCompat.requestPermissions(requireActivity(), DownloadUtils.PERMS, 8020); | ||||
|             return true; | ||||
|         } else if (itemId == R.id.action_dms) { | ||||
|         } | ||||
|         if (itemId == R.id.action_dms) { | ||||
|             final EditText input = new EditText(context); | ||||
|             input.setHint(R.string.reply_hint); | ||||
|             new AlertDialog.Builder(context) | ||||
| @ -259,6 +262,9 @@ public class StoryViewerFragment extends Fragment { | ||||
|                     .show(); | ||||
|             return true; | ||||
|         } | ||||
|         if (itemId == R.id.action_profile) { | ||||
|             openProfile("@" + currentStory.getUsername()); | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
| @ -281,7 +287,9 @@ public class StoryViewerFragment extends Fragment { | ||||
|         final ActionBar actionBar = fragmentActivity.getSupportActionBar(); | ||||
|         if (actionBar != null) { | ||||
|             actionBar.setTitle(actionBarTitle); | ||||
|             actionBar.setSubtitle(actionBarSubtitle); | ||||
|         } | ||||
|         setHasOptionsMenu(true); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
| @ -697,6 +705,10 @@ public class StoryViewerFragment extends Fragment { | ||||
|         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; | ||||
| @ -846,7 +858,6 @@ public class StoryViewerFragment extends Fragment { | ||||
| 
 | ||||
|         final MediaItemType itemType = currentStory.getItemType(); | ||||
| 
 | ||||
|         if (menuDownload != null) menuDownload.setVisible(false); | ||||
|         url = itemType == MediaItemType.MEDIA_TYPE_IMAGE ? currentStory.getStoryUrl() : currentStory.getVideoUrl(); | ||||
| 
 | ||||
|         if (itemType != MediaItemType.MEDIA_TYPE_LIVE) { | ||||
| @ -900,9 +911,10 @@ public class StoryViewerFragment extends Fragment { | ||||
|         else setupImage(); | ||||
| 
 | ||||
|         final ActionBar actionBar = fragmentActivity.getSupportActionBar(); | ||||
|         actionBarSubtitle = Utils.datetimeParser.format(new Date(currentStory.getTimestamp() * 1000L)); | ||||
|         if (actionBar != null) { | ||||
|             try { | ||||
|                 actionBar.setSubtitle(Utils.datetimeParser.format(new Date(currentStory.getTimestamp() * 1000L))); | ||||
|                 actionBar.setSubtitle(actionBarSubtitle); | ||||
|             } catch (Exception e) { | ||||
|                 Log.e(TAG, "refreshStory: ", e); | ||||
|             } | ||||
| @ -948,11 +960,17 @@ public class StoryViewerFragment extends Fragment { | ||||
|                                                                                   final ImageInfo imageInfo, | ||||
|                                                                                   final Animatable animatable) { | ||||
|                                                           if (menuDownload != null) { | ||||
|                                                               downloadVisible = true; | ||||
|                                                               menuDownload.setVisible(true); | ||||
|                                                           } | ||||
|                                                           if (currentStory.canReply() && menuDm != null) { | ||||
|                                                               dmVisible = true; | ||||
|                                                               menuDm.setVisible(true); | ||||
|                                                           } | ||||
|                                                           if (!TextUtils.isEmpty(currentStory.getUsername())) { | ||||
|                                                               profileVisible = true; | ||||
|                                                               menuProfile.setVisible(true); | ||||
|                                                           } | ||||
|                                                           binding.progressView.setVisibility(View.GONE); | ||||
|                                                       } | ||||
|                                                   }) | ||||
| @ -982,9 +1000,18 @@ public class StoryViewerFragment extends Fragment { | ||||
|                                         @Nullable final MediaSource.MediaPeriodId mediaPeriodId, | ||||
|                                         @NonNull final LoadEventInfo loadEventInfo, | ||||
|                                         @NonNull final MediaLoadData mediaLoadData) { | ||||
|                 if (menuDownload != null) menuDownload.setVisible(true); | ||||
|                 if (currentStory.canReply() && menuDm != null) | ||||
|                 if (menuDownload != null) { | ||||
|                     downloadVisible = true; | ||||
|                     menuDownload.setVisible(true); | ||||
|                 } | ||||
|                 if (currentStory.canReply() && menuDm != null) { | ||||
|                     dmVisible = true; | ||||
|                     menuDm.setVisible(true); | ||||
|                 } | ||||
|                 if (!TextUtils.isEmpty(currentStory.getUsername()) && menuProfile != null) { | ||||
|                     profileVisible = true; | ||||
|                     menuProfile.setVisible(true); | ||||
|                 } | ||||
|                 binding.progressView.setVisibility(View.GONE); | ||||
|             } | ||||
| 
 | ||||
| @ -993,9 +1020,18 @@ public class StoryViewerFragment extends Fragment { | ||||
|                                       @Nullable final MediaSource.MediaPeriodId mediaPeriodId, | ||||
|                                       @NonNull final LoadEventInfo loadEventInfo, | ||||
|                                       @NonNull final MediaLoadData mediaLoadData) { | ||||
|                 if (menuDownload != null) menuDownload.setVisible(true); | ||||
|                 if (currentStory.canReply() && menuDm != null) | ||||
|                 if (menuDownload != null) { | ||||
|                     downloadVisible = true; | ||||
|                     menuDownload.setVisible(true); | ||||
|                 } | ||||
|                 if (currentStory.canReply() && menuDm != null) { | ||||
|                     dmVisible = true; | ||||
|                     menuDm.setVisible(true); | ||||
|                 } | ||||
|                 if (!TextUtils.isEmpty(currentStory.getUsername()) && menuProfile != null) { | ||||
|                     profileVisible = true; | ||||
|                     menuProfile.setVisible(true); | ||||
|                 } | ||||
|                 binding.progressView.setVisibility(View.VISIBLE); | ||||
|             } | ||||
| 
 | ||||
| @ -1014,8 +1050,18 @@ public class StoryViewerFragment extends Fragment { | ||||
|                                     @NonNull final MediaLoadData mediaLoadData, | ||||
|                                     @NonNull final IOException error, | ||||
|                                     final boolean wasCanceled) { | ||||
|                 if (menuDownload != null) menuDownload.setVisible(false); | ||||
|                 if (menuDm != null) menuDm.setVisible(false); | ||||
|                 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); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
| @ -83,9 +83,12 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre | ||||
|             new FeedStoriesAdapter.OnFeedStoryClickListener() { | ||||
|                 @Override | ||||
|                 public void onFeedStoryClick(FeedStoryModel model, int position) { | ||||
|                     final NavDirections action = FeedFragmentDirections | ||||
|                             .actionFeedFragmentToStoryViewerFragment(StoryViewerOptions.forFeedStoryPosition(position)); | ||||
|                     NavHostFragment.findNavController(FeedFragment.this).navigate(action); | ||||
|                     final NavController navController = NavHostFragment.findNavController(FeedFragment.this); | ||||
|                     if (isSafeToNavigate(navController)) { | ||||
|                         final NavDirections action = FeedFragmentDirections | ||||
|                                 .actionFeedFragmentToStoryViewerFragment(StoryViewerOptions.forFeedStoryPosition(position)); | ||||
|                         navController.navigate(action); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 @Override | ||||
| @ -437,4 +440,9 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre | ||||
|         binding.feedRecyclerView.smoothScrollToPosition(0); | ||||
|         // binding.storiesContainer.setExpanded(true); | ||||
|     } | ||||
| 
 | ||||
|     private boolean isSafeToNavigate(final NavController navController) { | ||||
|         return navController.getCurrentDestination() != null | ||||
|                 && navController.getCurrentDestination().getId() == R.id.feedFragment; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -29,6 +29,7 @@ public class DownloadsPreferencesFragment extends BasePreferencesFragment { | ||||
|         if (context == null) return; | ||||
|         screen.addPreference(getDownloadUserFolderPreference(context)); | ||||
|         screen.addPreference(getSaveToCustomFolderPreference(context)); | ||||
|         screen.addPreference(getPrependUsernameToFilenamePreference(context)); | ||||
|     } | ||||
| 
 | ||||
|     private Preference getDownloadUserFolderPreference(@NonNull final Context context) { | ||||
| @ -49,6 +50,14 @@ public class DownloadsPreferencesFragment extends BasePreferencesFragment { | ||||
|                 .show(getParentFragmentManager(), null)); | ||||
|     } | ||||
| 
 | ||||
|     private Preference getPrependUsernameToFilenamePreference(@NonNull final Context context) { | ||||
|         final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context); | ||||
|         preference.setKey(Constants.DOWNLOAD_PREPEND_USER_NAME); | ||||
|         preference.setTitle(R.string.download_prepend_username); | ||||
|         preference.setIconSpaceReserved(false); | ||||
|         return preference; | ||||
|     } | ||||
| 
 | ||||
|     public static class SaveToCustomFolderPreference extends Preference { | ||||
|         private AppCompatTextView customPathTextView; | ||||
|         private final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener; | ||||
|  | ||||
| @ -11,8 +11,6 @@ import android.widget.Toast; | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.appcompat.app.AlertDialog; | ||||
| import androidx.appcompat.app.AppCompatActivity; | ||||
| import androidx.fragment.app.FragmentActivity; | ||||
| import androidx.fragment.app.FragmentManager; | ||||
| import androidx.navigation.NavController; | ||||
| import androidx.navigation.NavDirections; | ||||
| @ -35,9 +33,11 @@ import awais.instagrabber.db.repositories.AccountRepository; | ||||
| import awais.instagrabber.db.repositories.RepositoryCallback; | ||||
| import awais.instagrabber.dialogs.AccountSwitcherDialogFragment; | ||||
| import awais.instagrabber.repositories.responses.User; | ||||
| import awais.instagrabber.utils.AppExecutors; | ||||
| import awais.instagrabber.utils.Constants; | ||||
| import awais.instagrabber.utils.CookieUtils; | ||||
| import awais.instagrabber.utils.FlavorTown; | ||||
| import awais.instagrabber.utils.ProcessPhoenix; | ||||
| import awais.instagrabber.utils.TextUtils; | ||||
| import awais.instagrabber.utils.Utils; | ||||
| import awais.instagrabber.webservices.ServiceCallback; | ||||
| @ -71,11 +71,15 @@ public class MorePreferencesFragment extends BasePreferencesFragment { | ||||
|             accountCategory.setSummary(R.string.account_hint); | ||||
|             accountCategory.addPreference(getAccountSwitcherPreference(cookie, context)); | ||||
|             accountCategory.addPreference(getPreference(R.string.logout, R.string.logout_summary, R.drawable.ic_logout_24, preference -> { | ||||
|                 if (getContext() == null) return false; | ||||
|                 final Context context1 = getContext(); | ||||
|                 if (context1 == null) return false; | ||||
|                 CookieUtils.setupCookies("LOGOUT"); | ||||
|                 shouldRecreate(); | ||||
|                 Toast.makeText(context, R.string.logout_success, Toast.LENGTH_SHORT).show(); | ||||
|                 // shouldRecreate(); | ||||
|                 Toast.makeText(context1, R.string.logout_success, Toast.LENGTH_SHORT).show(); | ||||
|                 settingsHelper.putString(Constants.COOKIE, ""); | ||||
|                 AppExecutors.getInstance().mainThread().execute(() -> { | ||||
|                     ProcessPhoenix.triggerRebirth(context1); | ||||
|                 }, 200); | ||||
|                 return true; | ||||
|             })); | ||||
|         } | ||||
| @ -103,9 +107,14 @@ public class MorePreferencesFragment extends BasePreferencesFragment { | ||||
|                                             CookieUtils.removeAllAccounts(context, new RepositoryCallback<Void>() { | ||||
|                                                 @Override | ||||
|                                                 public void onSuccess(final Void result) { | ||||
|                                                     shouldRecreate(); | ||||
|                                                     Toast.makeText(context, R.string.logout_success, Toast.LENGTH_SHORT).show(); | ||||
|                                                     // shouldRecreate(); | ||||
|                                                     final Context context1 = getContext(); | ||||
|                                                     if (context1 == null) return; | ||||
|                                                     Toast.makeText(context1, R.string.logout_success, Toast.LENGTH_SHORT).show(); | ||||
|                                                     settingsHelper.putString(Constants.COOKIE, ""); | ||||
|                                                     AppExecutors.getInstance().mainThread().execute(() -> { | ||||
|                                                         ProcessPhoenix.triggerRebirth(context1); | ||||
|                                                     }, 200); | ||||
|                                                 } | ||||
| 
 | ||||
|                                                 @Override | ||||
| @ -222,7 +231,8 @@ public class MorePreferencesFragment extends BasePreferencesFragment { | ||||
|                                            BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")", | ||||
|                                            -1, | ||||
|                                            preference -> { | ||||
|                                                FlavorTown.updateCheck((AppCompatActivity) requireActivity(), true); | ||||
|                                                if (BuildConfig.isPre) return true; | ||||
|                                                FlavorTown.updateCheck(activity, true); | ||||
|                                                return true; | ||||
|                                            })); | ||||
|         screen.addPreference(getDivider(context)); | ||||
| @ -264,9 +274,14 @@ public class MorePreferencesFragment extends BasePreferencesFragment { | ||||
|                                 new RepositoryCallback<Account>() { | ||||
|                                     @Override | ||||
|                                     public void onSuccess(final Account result) { | ||||
|                                         final FragmentActivity activity = getActivity(); | ||||
|                                         if (activity == null) return; | ||||
|                                         activity.recreate(); | ||||
|                                         // final FragmentActivity activity = getActivity(); | ||||
|                                         // if (activity == null) return; | ||||
|                                         // activity.recreate(); | ||||
|                                         AppExecutors.getInstance().mainThread().execute(() -> { | ||||
|                                             final Context context = getContext(); | ||||
|                                             if (context == null) return; | ||||
|                                             ProcessPhoenix.triggerRebirth(context); | ||||
|                                         }, 200); | ||||
|                                     } | ||||
| 
 | ||||
|                                     @Override | ||||
|  | ||||
| @ -37,6 +37,14 @@ public class StoriesPreferencesFragment extends BasePreferencesFragment { | ||||
|         return preference; | ||||
|     } | ||||
| 
 | ||||
|     private Preference getHideMutedReelsPreference(@NonNull final Context context) { | ||||
|         final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context); | ||||
|         preference.setKey(Constants.HIDE_MUTED_REELS); | ||||
|         preference.setTitle(R.string.hide_muted_reels_setting); | ||||
|         preference.setIconSpaceReserved(false); | ||||
|         return preference; | ||||
|     } | ||||
| 
 | ||||
|     private Preference getMarkStoriesSeenPreference(@NonNull final Context context) { | ||||
|         final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context); | ||||
|         preference.setKey(Constants.MARK_AS_SEEN); | ||||
|  | ||||
| @ -20,6 +20,7 @@ public final class Constants { | ||||
|     // boolean prefs | ||||
|     public static final String DOWNLOAD_USER_FOLDER = "download_user_folder"; | ||||
|     public static final String TOGGLE_KEYWORD_FILTER = "toggle_keyword_filter"; | ||||
|     public static final String DOWNLOAD_PREPEND_USER_NAME = "download_user_name"; | ||||
|     // deprecated: public static final String BOTTOM_TOOLBAR = "bottom_toolbar"; | ||||
|     public static final String FOLDER_SAVE_TO = "saved_to"; | ||||
|     public static final String AUTOPLAY_VIDEOS = "autoplay_videos"; | ||||
| @ -28,6 +29,7 @@ public final class Constants { | ||||
|     public static final String CUSTOM_DATE_TIME_FORMAT_ENABLED = "data_time_custom_enabled"; | ||||
|     public static final String SWAP_DATE_TIME_FORMAT_ENABLED = "swap_date_time_enabled"; | ||||
|     public static final String MARK_AS_SEEN = "mark_as_seen"; | ||||
|     public static final String HIDE_MUTED_REELS = "hide_muted_reels"; | ||||
|     public static final String DM_MARK_AS_SEEN = "dm_mark_as_seen"; | ||||
|     // deprecated: public static final String INSTADP = "instadp"; | ||||
|     // deprecated: public static final String STORIESIG = "storiesig"; | ||||
| @ -79,7 +81,6 @@ public final class Constants { | ||||
| //    public static final String SIGNATURE_KEY = "9193488027538fd3450b83b7d05286d4ca9599a0f7eeed90d8c85925698a05dc"; | ||||
|     public static final String BREADCRUMB_KEY = "iN4$aGr0m"; | ||||
|     public static final int LOGIN_RESULT_CODE = 5000; | ||||
|     public static final String FDROID_SHA1_FINGERPRINT = "C1661EB8FD09F618307E687786D5E5056F65084D"; | ||||
|     public static final String SKIPPED_VERSION = "skipped_version"; | ||||
|     public static final String DEFAULT_TAB = "default_tab"; | ||||
|     public static final String PREF_DARK_THEME = "dark_theme"; | ||||
|  | ||||
| @ -98,41 +98,52 @@ public final class DownloadUtils { | ||||
|     //        } | ||||
|     //    } | ||||
| 
 | ||||
|     private static void dmDownloadImpl(@NonNull final Context context, | ||||
|                                        @Nullable final String username, | ||||
|                                        final String modelId, | ||||
|                                        final String url) { | ||||
|         final File dir = getDownloadDir(context, username); | ||||
|         if (dir.exists() || dir.mkdirs()) { | ||||
|             download(context, | ||||
|                      url, | ||||
|                      getDownloadSaveFile(dir, modelId, url).getAbsolutePath()); | ||||
|             return; | ||||
|         } | ||||
|         Toast.makeText(context, R.string.error_creating_folders, Toast.LENGTH_SHORT).show(); | ||||
|     } | ||||
| //    private static void dmDownloadImpl(@NonNull final Context context, | ||||
| //                                       @Nullable final String username, | ||||
| //                                       final String modelId, | ||||
| //                                       final String url) { | ||||
| //        final File dir = getDownloadDir(context, username); | ||||
| //        if (dir.exists() || dir.mkdirs()) { | ||||
| //            download(context, | ||||
| //                     url, | ||||
| //                     getDownloadSaveFile(dir, modelId, url).getAbsolutePath()); | ||||
| //            return; | ||||
| //        } | ||||
| //        Toast.makeText(context, R.string.error_creating_folders, Toast.LENGTH_SHORT).show(); | ||||
| //    } | ||||
| 
 | ||||
|     @NonNull | ||||
|     private static File getDownloadSaveFile(final File finalDir, | ||||
|                                             final String postId, | ||||
|                                             final String displayUrl) { | ||||
|         return getDownloadSaveFile(finalDir, postId, "", displayUrl); | ||||
|         return getDownloadSaveFile(finalDir, postId, "", displayUrl, ""); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     private static File getDownloadSaveFile(final File finalDir, | ||||
|                                             final String postId, | ||||
|                                             final String displayUrl, | ||||
|                                             final String username) { | ||||
|         return getDownloadSaveFile(finalDir, postId, "", displayUrl, username); | ||||
|     } | ||||
| 
 | ||||
|     private static File getDownloadChildSaveFile(final File downloadDir, | ||||
|                                                  final String postId, | ||||
|                                                  final int childPosition, | ||||
|                                                  final String url) { | ||||
|                                                  final String url, | ||||
|                                                  final String username) { | ||||
|         final String sliderPostfix = "_slide_" + childPosition; | ||||
|         return getDownloadSaveFile(downloadDir, postId, sliderPostfix, url); | ||||
|         return getDownloadSaveFile(downloadDir, postId, sliderPostfix, url, username); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     private static File getDownloadSaveFile(final File finalDir, | ||||
|                                             final String postId, | ||||
|                                             final String sliderPostfix, | ||||
|                                             final String displayUrl) { | ||||
|         final String fileName = postId + sliderPostfix + getFileExtensionFromUrl(displayUrl); | ||||
|                                             final String displayUrl, | ||||
|                                             final String username) { | ||||
|         final String usernamePrepend = TextUtils.isEmpty(username) ? "" : (username + "_"); | ||||
|         final String fileName = usernamePrepend + postId + sliderPostfix + getFileExtensionFromUrl(displayUrl); | ||||
|         return new File(finalDir, fileName); | ||||
|     } | ||||
| 
 | ||||
| @ -205,8 +216,9 @@ public final class DownloadUtils { | ||||
|             case MEDIA_TYPE_IMAGE: | ||||
|             case MEDIA_TYPE_VIDEO: { | ||||
|                 final String url = ResponseBodyUtils.getImageUrl(media); | ||||
|                 final File file = getDownloadSaveFile(downloadDir, media.getCode(), url); | ||||
|                 checkList.add(file.exists()); | ||||
|                 final File file = getDownloadSaveFile(downloadDir, media.getCode(), url, ""); | ||||
|                 final File usernamePrependedFile = getDownloadSaveFile(downloadDir, media.getCode(), url, username); | ||||
|                 checkList.add(file.exists() || usernamePrependedFile.exists()); | ||||
|                 break; | ||||
|             } | ||||
|             case MEDIA_TYPE_SLIDER: | ||||
| @ -215,8 +227,9 @@ public final class DownloadUtils { | ||||
|                     final Media child = sliderItems.get(i); | ||||
|                     if (child == null) continue; | ||||
|                     final String url = ResponseBodyUtils.getImageUrl(child); | ||||
|                     final File file = getDownloadChildSaveFile(downloadDir, media.getCode(), i + 1, url); | ||||
|                     checkList.add(file.exists()); | ||||
|                     final File file = getDownloadChildSaveFile(downloadDir, media.getCode(), i + 1, url, ""); | ||||
|                     final File usernamePrependedFile = getDownloadChildSaveFile(downloadDir, media.getCode(), i + 1, url, username); | ||||
|                     checkList.add(file.exists() || usernamePrependedFile.exists()); | ||||
|                 } | ||||
|                 break; | ||||
|             default: | ||||
| @ -262,10 +275,12 @@ public final class DownloadUtils { | ||||
|         final String url = storyModel.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO | ||||
|                            ? storyModel.getVideoUrl() | ||||
|                            : storyModel.getStoryUrl(); | ||||
|         final String baseFileName = storyModel.getStoryMediaId() + "_" | ||||
|                 + storyModel.getTimestamp() + DownloadUtils.getFileExtensionFromUrl(url); | ||||
|         final String usernamePrepend = Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_PREPEND_USER_NAME) | ||||
|                 && storyModel.getUsername() != null ? storyModel.getUsername() + "_" : ""; | ||||
|         final File saveFile = new File(downloadDir, | ||||
|                                        storyModel.getStoryMediaId() | ||||
|                                                + "_" + storyModel.getTimestamp() | ||||
|                                                + DownloadUtils.getFileExtensionFromUrl(url)); | ||||
|                  usernamePrepend + baseFileName); | ||||
|         download(context, url, saveFile.getAbsolutePath()); | ||||
|     } | ||||
| 
 | ||||
| @ -291,13 +306,23 @@ public final class DownloadUtils { | ||||
|         final Map<String, String> map = new HashMap<>(); | ||||
|         for (final Media media : feedModels) { | ||||
|             final User mediaUser = media.getUser(); | ||||
|             final File downloadDir = getDownloadDir(context, mediaUser == null ? "" : "@" + mediaUser.getUsername()); | ||||
|             final File downloadDir = getDownloadDir(context, mediaUser == null ? "" : mediaUser.getUsername()); | ||||
|             if (downloadDir == null) return; | ||||
|             switch (media.getMediaType()) { | ||||
|                 case MEDIA_TYPE_IMAGE: | ||||
|                 case MEDIA_TYPE_VIDEO: { | ||||
|                     final String url = getUrlOfType(media); | ||||
|                     final File file = getDownloadSaveFile(downloadDir, media.getCode(), url); | ||||
|                     String fileName = media.getId(); | ||||
|                     if (mediaUser != null && TextUtils.isEmpty(media.getCode())) { | ||||
|                         fileName = mediaUser.getUsername() + "_" + fileName; | ||||
|                     } | ||||
|                     if (!TextUtils.isEmpty(media.getCode())) { | ||||
|                         fileName = media.getCode(); | ||||
|                         if (Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_PREPEND_USER_NAME) && mediaUser != null) { | ||||
|                             fileName = mediaUser.getUsername() + "_" + fileName; | ||||
|                         } | ||||
|                     } | ||||
|                     final File file = getDownloadSaveFile(downloadDir, fileName, url); | ||||
|                     map.put(url, file.getAbsolutePath()); | ||||
|                     break; | ||||
|                 } | ||||
| @ -319,7 +344,8 @@ public final class DownloadUtils { | ||||
|                         } | ||||
|                         final Media child = sliderItems.get(i); | ||||
|                         final String url = getUrlOfType(child); | ||||
|                         final File file = getDownloadChildSaveFile(downloadDir, media.getCode(), i + 1, url); | ||||
|                         final String usernamePrepend = Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_PREPEND_USER_NAME) && mediaUser != null ? mediaUser.getUsername() : ""; | ||||
|                         final File file = getDownloadChildSaveFile(downloadDir, media.getCode(), i + 1, url, usernamePrepend); | ||||
|                         map.put(url, file.getAbsolutePath()); | ||||
|                     } | ||||
|                     break; | ||||
|  | ||||
| @ -156,12 +156,21 @@ public final class ExportImportUtils { | ||||
|                     query, | ||||
|                     favoriteType, | ||||
|                     favsObject.optString("s"), | ||||
|                     favoriteType == FavoriteType.HASHTAG ? null | ||||
|                                                          : favsObject.optString("pic_url"), | ||||
|                     favoriteType == FavoriteType.USER ? favsObject.optString("pic_url") : null, | ||||
|                     new Date(favsObject.getLong("d"))); | ||||
|             // Log.d(TAG, "importJson: favoriteModel: " + favoriteModel); | ||||
|             FavoriteRepository.getInstance(FavoriteDataSource.getInstance(context)) | ||||
|                               .insertOrUpdateFavorite(favorite, null); | ||||
|             final FavoriteRepository favRepo = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(context)); | ||||
|             favRepo.getFavorite(query, favoriteType, new RepositoryCallback<Favorite>() { | ||||
|                 @Override | ||||
|                 public void onSuccess(final Favorite result) { | ||||
|                     // local has priority since it's more frequently updated | ||||
|                 } | ||||
| 
 | ||||
|                 @Override | ||||
|                 public void onDataNotAvailable() { | ||||
|                     favRepo.insertOrUpdateFavorite(favorite, null); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -1,104 +1,72 @@ | ||||
| package awais.instagrabber.utils; | ||||
| 
 | ||||
| import android.annotation.SuppressLint; | ||||
| import android.content.ActivityNotFoundException; | ||||
| import android.content.Context; | ||||
| import android.content.DialogInterface; | ||||
| import android.content.Intent; | ||||
| import android.content.pm.PackageInfo; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.content.pm.Signature; | ||||
| import android.content.res.Resources; | ||||
| import android.net.Uri; | ||||
| import android.os.AsyncTask; | ||||
| import android.util.Log; | ||||
| import android.widget.Toast; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.appcompat.app.AlertDialog; | ||||
| import androidx.appcompat.app.AppCompatActivity; | ||||
| 
 | ||||
| import java.security.MessageDigest; | ||||
| import java.security.NoSuchAlgorithmException; | ||||
| import java.util.Objects; | ||||
| import java.util.concurrent.ThreadLocalRandom; | ||||
| 
 | ||||
| import javax.security.cert.CertificateException; | ||||
| import javax.security.cert.X509Certificate; | ||||
| import java.util.regex.Matcher; | ||||
| import java.util.regex.Pattern; | ||||
| 
 | ||||
| import awais.instagrabber.BuildConfig; | ||||
| import awais.instagrabber.R; | ||||
| import awais.instagrabber.databinding.DialogUpdateBinding; | ||||
| 
 | ||||
| import static awais.instagrabber.utils.Utils.settingsHelper; | ||||
| 
 | ||||
| public final class FlavorTown { | ||||
|     private static final String TAG = "FlavorTown"; | ||||
|     private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); | ||||
|     private static AlertDialog dialog; | ||||
|     private static final UpdateChecker UPDATE_CHECKER = UpdateChecker.getInstance(); | ||||
|     private static final Pattern VERSION_NAME_PATTERN = Pattern.compile("v?(\\d+\\.\\d+\\.\\d+)(?:_?)(\\w*)(?:-?)(\\w*)"); | ||||
| 
 | ||||
|     private static boolean checking = false; | ||||
| 
 | ||||
|     public static void updateCheck(@NonNull final AppCompatActivity context) { | ||||
|         updateCheck(context, false); | ||||
|     } | ||||
| 
 | ||||
|     @SuppressLint("PackageManagerGetSignatures") | ||||
|     public static void updateCheck(@NonNull final AppCompatActivity context, final boolean force) { | ||||
|         boolean isInstalledFromFdroid = false; | ||||
|         final PackageInfo packageInfo; | ||||
|         try { | ||||
|             packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES); | ||||
|             for (Signature signature : packageInfo.signatures) { | ||||
|                 final X509Certificate cert = X509Certificate.getInstance(signature.toByteArray()); | ||||
|                 final String fingerprint = bytesToHex(MessageDigest.getInstance("SHA-1").digest(cert.getEncoded())); | ||||
|                 isInstalledFromFdroid = fingerprint.equals(Constants.FDROID_SHA1_FINGERPRINT); | ||||
|                 // Log.d(TAG, "fingerprint:" + fingerprint); | ||||
|             } | ||||
|         } catch (PackageManager.NameNotFoundException | NoSuchAlgorithmException | CertificateException e) { | ||||
|             Log.e(TAG, "Error", e); | ||||
|         } | ||||
|         if (isInstalledFromFdroid) return; | ||||
|         final DialogUpdateBinding binding = DialogUpdateBinding.inflate(context.getLayoutInflater(), null, false); | ||||
|         binding.skipUpdate.setOnCheckedChangeListener((buttonView, isChecked) -> { | ||||
|             if (dialog == null) return; | ||||
|             dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(!isChecked); | ||||
|         }); | ||||
|         Resources res = context.getResources(); | ||||
|         new UpdateChecker(version -> { | ||||
|             if (force && version.equals(BuildConfig.VERSION_NAME)) { | ||||
|                 Toast.makeText(context, "You're already on the latest version", Toast.LENGTH_SHORT).show(); | ||||
|     public static void updateCheck(@NonNull final AppCompatActivity context, | ||||
|                                    final boolean force) { | ||||
|         if (checking) return; | ||||
|         checking = true; | ||||
|         AppExecutors.getInstance().networkIO().execute(() -> { | ||||
|             final String onlineVersionName = UPDATE_CHECKER.getLatestVersion(); | ||||
|             if (onlineVersionName == null) return; | ||||
|             final String onlineVersion = getVersion(onlineVersionName); | ||||
|             final String localVersion = getVersion(BuildConfig.VERSION_NAME); | ||||
|             if (Objects.equals(onlineVersion, localVersion)) { | ||||
|                 if (force) { | ||||
|                     AppExecutors.getInstance().mainThread().execute(() -> { | ||||
|                         final Context applicationContext = context.getApplicationContext(); | ||||
|                         // Check if app was closed or crashed before reaching here | ||||
|                         if (applicationContext == null) return; | ||||
|                         // Show toast if version number preference was tapped | ||||
|                         Toast.makeText(applicationContext, R.string.on_latest_version, Toast.LENGTH_SHORT).show(); | ||||
|                     }); | ||||
|                 } | ||||
|                 return; | ||||
|             } | ||||
|             final String skippedVersion = settingsHelper.getString(Constants.SKIPPED_VERSION); | ||||
|             final boolean shouldShowDialog = force || (!version.equals(BuildConfig.VERSION_NAME) && !BuildConfig.DEBUG && !skippedVersion | ||||
|                     .equals(version)); | ||||
|             final boolean shouldShowDialog = UpdateCheckCommon.shouldShowUpdateDialog(force, onlineVersionName); | ||||
|             if (!shouldShowDialog) return; | ||||
|             dialog = new AlertDialog.Builder(context) | ||||
|                     .setTitle(res.getString(R.string.update_available, version)) | ||||
|                     .setView(binding.getRoot()) | ||||
|                     .setNeutralButton(R.string.cancel, (dialog, which) -> { | ||||
|                         if (binding.skipUpdate.isChecked()) { | ||||
|                             settingsHelper.putString(Constants.SKIPPED_VERSION, version); | ||||
|                         } | ||||
|                         dialog.dismiss(); | ||||
|                     }) | ||||
|                     .setPositiveButton(R.string.action_github, (dialog1, which) -> { | ||||
|                         try { | ||||
|                             context.startActivity(new Intent(Intent.ACTION_VIEW).setData( | ||||
|                                     Uri.parse("https://github.com/austinhuang0131/instagrabber/releases/latest"))); | ||||
|                         } catch (final ActivityNotFoundException e) { | ||||
|                             // do nothing | ||||
|                         } | ||||
|                     }) | ||||
|                     // if we don't show dialog for fdroid users, is the below required? | ||||
|                     .setNegativeButton(R.string.action_fdroid, (dialog, which) -> { | ||||
|                         try { | ||||
|                             context.startActivity(new Intent(Intent.ACTION_VIEW).setData( | ||||
|                                     Uri.parse("https://f-droid.org/packages/me.austinhuang.instagrabber/"))); | ||||
|                         } catch (final ActivityNotFoundException e) { | ||||
|                             // do nothing | ||||
|                         } | ||||
|                     }) | ||||
|                     .show(); | ||||
|         }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); | ||||
|             UpdateCheckCommon.showUpdateDialog(context, onlineVersionName, (dialog, which) -> { | ||||
|                 UPDATE_CHECKER.onDownload(context); | ||||
|                 dialog.dismiss(); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private static String getVersion(@NonNull final String versionName) { | ||||
|         final Matcher matcher = VERSION_NAME_PATTERN.matcher(versionName); | ||||
|         if (!matcher.matches()) return versionName; | ||||
|         try { | ||||
|             return matcher.group(1); | ||||
|         } catch (Exception e) { | ||||
|             Log.e(TAG, "getVersion: ", e); | ||||
|         } | ||||
|         return versionName; | ||||
|     } | ||||
| 
 | ||||
|     public static void changelogCheck(@NonNull final Context context) { | ||||
| @ -121,14 +89,4 @@ public final class FlavorTown { | ||||
|             settingsHelper.putInteger(Constants.PREV_INSTALL_VERSION, BuildConfig.VERSION_CODE); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static String bytesToHex(byte[] bytes) { | ||||
|         char[] hexChars = new char[bytes.length * 2]; | ||||
|         for (int j = 0; j < bytes.length; j++) { | ||||
|             int v = bytes[j] & 0xFF; | ||||
|             hexChars[j * 2] = HEX_ARRAY[v >>> 4]; | ||||
|             hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; | ||||
|         } | ||||
|         return new String(hexChars); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										111
									
								
								app/src/main/java/awais/instagrabber/utils/ProcessPhoenix.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								app/src/main/java/awais/instagrabber/utils/ProcessPhoenix.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,111 @@ | ||||
| /* | ||||
|  * Copyright (C) 2014 Jake Wharton | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *      http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| package awais.instagrabber.utils; | ||||
| 
 | ||||
| import android.app.Activity; | ||||
| import android.app.ActivityManager; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.os.Bundle; | ||||
| import android.os.Process; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; | ||||
| import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; | ||||
| 
 | ||||
| /** | ||||
|  * Process Phoenix facilitates restarting your application process. This should only be used for | ||||
|  * things like fundamental state changes in your debug builds (e.g., changing from staging to | ||||
|  * production). | ||||
|  * <p> | ||||
|  * Trigger process recreation by calling {@link #triggerRebirth} with a {@link Context} instance. | ||||
|  */ | ||||
| public final class ProcessPhoenix extends Activity { | ||||
|     private static final String KEY_RESTART_INTENTS = "phoenix_restart_intents"; | ||||
| 
 | ||||
|     /** | ||||
|      * Call to restart the application process using the {@linkplain Intent#CATEGORY_DEFAULT default} | ||||
|      * activity as an intent. | ||||
|      * <p> | ||||
|      * Behavior of the current process after invoking this method is undefined. | ||||
|      */ | ||||
|     public static void triggerRebirth(Context context) { | ||||
|         triggerRebirth(context, getRestartIntent(context)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Call to restart the application process using the specified intents. | ||||
|      * <p> | ||||
|      * Behavior of the current process after invoking this method is undefined. | ||||
|      */ | ||||
|     public static void triggerRebirth(Context context, Intent... nextIntents) { | ||||
|         Intent intent = new Intent(context, ProcessPhoenix.class); | ||||
|         intent.addFlags(FLAG_ACTIVITY_NEW_TASK); // In case we are called with non-Activity context. | ||||
|         intent.putParcelableArrayListExtra(KEY_RESTART_INTENTS, new ArrayList<>(Arrays.asList(nextIntents))); | ||||
|         context.startActivity(intent); | ||||
|         if (context instanceof Activity) { | ||||
|             ((Activity) context).finish(); | ||||
|         } | ||||
|         Runtime.getRuntime().exit(0); // Kill kill kill! | ||||
|     } | ||||
| 
 | ||||
|     private static Intent getRestartIntent(Context context) { | ||||
|         String packageName = context.getPackageName(); | ||||
|         Intent defaultIntent = context.getPackageManager().getLaunchIntentForPackage(packageName); | ||||
|         if (defaultIntent != null) { | ||||
|             defaultIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK); | ||||
|             return defaultIntent; | ||||
|         } | ||||
| 
 | ||||
|         throw new IllegalStateException("Unable to determine default activity for " | ||||
|                                                 + packageName | ||||
|                                                 + ". Does an activity specify the DEFAULT category in its intent filter?"); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
| 
 | ||||
|         ArrayList<Intent> intents = getIntent().getParcelableArrayListExtra(KEY_RESTART_INTENTS); | ||||
|         startActivities(intents.toArray(new Intent[intents.size()])); | ||||
|         finish(); | ||||
|         Runtime.getRuntime().exit(0); // Kill kill kill! | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Checks if the current process is a temporary Phoenix Process. | ||||
|      * This can be used to avoid initialisation of unused resources or to prevent running code that | ||||
|      * is not multi-process ready. | ||||
|      * | ||||
|      * @return true if the current process is a temporary Phoenix Process | ||||
|      */ | ||||
|     public static boolean isPhoenixProcess(Context context) { | ||||
|         int currentPid = Process.myPid(); | ||||
|         ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); | ||||
|         List<ActivityManager.RunningAppProcessInfo> runningProcesses = manager.getRunningAppProcesses(); | ||||
|         if (runningProcesses != null) { | ||||
|             for (ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) { | ||||
|                 if (processInfo.pid == currentPid && processInfo.processName.endsWith(":phoenix")) { | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
| @ -1090,11 +1090,19 @@ public final class ResponseBodyUtils { | ||||
|         if (imageVersions2 == null) return null; | ||||
|         final List<MediaCandidate> candidates = imageVersions2.getCandidates(); | ||||
|         if (candidates == null || candidates.isEmpty()) return null; | ||||
|         final boolean isSquare = Integer.compare(media.getOriginalWidth(), media.getOriginalHeight()) == 0; | ||||
|         final List<MediaCandidate> sortedCandidates = candidates.stream() | ||||
|                 .sorted((c1, c2) -> Integer.compare(c2.getWidth(), c1.getWidth())) | ||||
|                 .filter(c -> c.getWidth() < type.getValue()) | ||||
|                 .collect(Collectors.toList()); | ||||
|         final MediaCandidate candidate = sortedCandidates.get(0); | ||||
|         if (sortedCandidates.size() == 1) return sortedCandidates.get(0).getUrl(); | ||||
|         final List<MediaCandidate> filteredCandidates = sortedCandidates.stream() | ||||
|                 .filter(c -> | ||||
|                         c.getWidth() <= media.getOriginalWidth() | ||||
|                                 && c.getWidth() <= type.getValue() | ||||
|                                 && (isSquare || Integer.compare(c.getWidth(), c.getHeight()) != 0) | ||||
|                 ) | ||||
|                 .collect(Collectors.toList()); | ||||
|         final MediaCandidate candidate = filteredCandidates.get(0); | ||||
|         if (candidate == null) return null; | ||||
|         return candidate.getUrl(); | ||||
|     } | ||||
|  | ||||
| @ -34,10 +34,12 @@ import static awais.instagrabber.utils.Constants.DATE_TIME_SELECTION; | ||||
| import static awais.instagrabber.utils.Constants.DEFAULT_TAB; | ||||
| import static awais.instagrabber.utils.Constants.DEVICE_UUID; | ||||
| import static awais.instagrabber.utils.Constants.DM_MARK_AS_SEEN; | ||||
| import static awais.instagrabber.utils.Constants.DOWNLOAD_PREPEND_USER_NAME; | ||||
| import static awais.instagrabber.utils.Constants.DOWNLOAD_USER_FOLDER; | ||||
| import static awais.instagrabber.utils.Constants.FLAG_SECURE; | ||||
| import static awais.instagrabber.utils.Constants.FOLDER_PATH; | ||||
| import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO; | ||||
| import static awais.instagrabber.utils.Constants.HIDE_MUTED_REELS; | ||||
| import static awais.instagrabber.utils.Constants.KEYWORD_FILTERS; | ||||
| import static awais.instagrabber.utils.Constants.MARK_AS_SEEN; | ||||
| import static awais.instagrabber.utils.Constants.MUTED_VIDEOS; | ||||
| @ -159,10 +161,10 @@ public final class SettingsHelper { | ||||
|                     STORY_SORT, PREF_EMOJI_VARIANTS, PREF_REACTIONS, PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT, PREF_TAB_ORDER}) | ||||
|     public @interface StringSettings {} | ||||
| 
 | ||||
|     @StringDef({DOWNLOAD_USER_FOLDER, FOLDER_SAVE_TO, AUTOPLAY_VIDEOS, SHOW_QUICK_ACCESS_DIALOG, MUTED_VIDEOS, | ||||
|     @StringDef({DOWNLOAD_USER_FOLDER, DOWNLOAD_PREPEND_USER_NAME, FOLDER_SAVE_TO, AUTOPLAY_VIDEOS, SHOW_QUICK_ACCESS_DIALOG, MUTED_VIDEOS, | ||||
|                        SHOW_CAPTIONS, CUSTOM_DATE_TIME_FORMAT_ENABLED, MARK_AS_SEEN, DM_MARK_AS_SEEN, CHECK_ACTIVITY, | ||||
|                        CHECK_UPDATES, SWAP_DATE_TIME_FORMAT_ENABLED, PREF_ENABLE_DM_NOTIFICATIONS, PREF_ENABLE_DM_AUTO_REFRESH, | ||||
|                        FLAG_SECURE, TOGGLE_KEYWORD_FILTER, PREF_ENABLE_SENTRY}) | ||||
|                        FLAG_SECURE, TOGGLE_KEYWORD_FILTER, PREF_ENABLE_SENTRY, HIDE_MUTED_REELS}) | ||||
|     public @interface BooleanSettings {} | ||||
| 
 | ||||
|     @StringDef({PREV_INSTALL_VERSION, BROWSER_UA_CODE, APP_UA_CODE, PREF_ENABLE_DM_AUTO_REFRESH_FREQ_NUMBER}) | ||||
|  | ||||
| @ -0,0 +1,38 @@ | ||||
| package awais.instagrabber.utils; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.content.DialogInterface; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| 
 | ||||
| import com.google.android.material.dialog.MaterialAlertDialogBuilder; | ||||
| 
 | ||||
| import awais.instagrabber.BuildConfig; | ||||
| import awais.instagrabber.R; | ||||
| 
 | ||||
| import static awais.instagrabber.utils.Utils.settingsHelper; | ||||
| 
 | ||||
| public final class UpdateCheckCommon { | ||||
| 
 | ||||
|     public static boolean shouldShowUpdateDialog(final boolean force, | ||||
|                                                  @NonNull final String version) { | ||||
|         final String skippedVersion = settingsHelper.getString(Constants.SKIPPED_VERSION); | ||||
|         return force || (!BuildConfig.DEBUG && !skippedVersion.equals(version)); | ||||
|     } | ||||
| 
 | ||||
|     public static void showUpdateDialog(@NonNull final Context context, | ||||
|                                         @NonNull final String version, | ||||
|                                         @NonNull final DialogInterface.OnClickListener onDownloadClickListener) { | ||||
|         AppExecutors.getInstance().mainThread().execute(() -> { | ||||
|             new MaterialAlertDialogBuilder(context) | ||||
|                     .setTitle(context.getString(R.string.update_available, version)) | ||||
|                     .setNeutralButton(R.string.skip_update, (dialog, which) -> { | ||||
|                         settingsHelper.putString(Constants.SKIPPED_VERSION, version); | ||||
|                         dialog.dismiss(); | ||||
|                     }) | ||||
|                     .setPositiveButton(R.string.action_download, onDownloadClickListener) | ||||
|                     .setNegativeButton(R.string.cancel, null) | ||||
|                     .show(); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| @ -1,58 +0,0 @@ | ||||
| package awais.instagrabber.utils; | ||||
| 
 | ||||
| import android.os.AsyncTask; | ||||
| import android.util.Log; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| 
 | ||||
| import org.json.JSONObject; | ||||
| 
 | ||||
| import java.net.HttpURLConnection; | ||||
| import java.net.URL; | ||||
| 
 | ||||
| import awais.instagrabber.BuildConfig; | ||||
| import awais.instagrabber.interfaces.FetchListener; | ||||
| 
 | ||||
| public final class UpdateChecker extends AsyncTask<Void, Void, Boolean> { | ||||
|     private final FetchListener<String> fetchListener; | ||||
|     private String version; | ||||
| 
 | ||||
|     public UpdateChecker(final FetchListener<String> fetchListener) { | ||||
|         this.fetchListener = fetchListener; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     protected Boolean doInBackground(final Void... voids) { | ||||
|         try { | ||||
|             version = ""; | ||||
| 
 | ||||
|             HttpURLConnection conn = | ||||
|                     (HttpURLConnection) new URL("https://f-droid.org/api/v1/packages/me.austinhuang.instagrabber").openConnection(); | ||||
|             conn.setUseCaches(false); | ||||
|             conn.setRequestProperty("User-Agent", "https://Barinsta.AustinHuang.me / mailto:Barinsta@AustinHuang.me"); | ||||
|             conn.connect(); | ||||
| 
 | ||||
|             final int responseCode = conn.getResponseCode(); | ||||
|             if (responseCode == HttpURLConnection.HTTP_OK) { | ||||
|                 final JSONObject data = new JSONObject(NetworkUtils.readFromConnection(conn)); | ||||
|                 if (BuildConfig.VERSION_CODE < data.getInt("suggestedVersionCode")) { | ||||
|                     version = data.getJSONArray("packages").getJSONObject(0).getString("versionName"); | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             conn.disconnect(); | ||||
|         } catch (final Exception e) { | ||||
|             if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); | ||||
|         } | ||||
| 
 | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onPostExecute(final Boolean result) { | ||||
|         if (result != null && result && fetchListener != null) | ||||
|             fetchListener.onResult("v"+version); | ||||
|     } | ||||
| } | ||||
| @ -31,7 +31,6 @@ import android.webkit.MimeTypeMap; | ||||
| import android.widget.Toast; | ||||
| 
 | ||||
| import androidx.annotation.DrawableRes; | ||||
| import androidx.annotation.IdRes; | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.appcompat.app.ActionBar; | ||||
| @ -474,7 +473,11 @@ public final class Utils { | ||||
|         final List<String> navGraphNameList = Arrays.asList(navGraphNames); | ||||
|         if (TextUtils.isEmpty(tabOrderString)) { | ||||
|             // Use top 5 entries for default list | ||||
|             return navGraphNameList.subList(0, 5); | ||||
|             final List<String> top5navGraphNames = navGraphNameList.subList(0, 5); | ||||
|             final String newOrderString = android.text.TextUtils.join(",", top5navGraphNames); | ||||
|             Utils.settingsHelper.putString(PreferenceKeys.PREF_TAB_ORDER, newOrderString); | ||||
|             tabOrderString = newOrderString; | ||||
|             return top5navGraphNames; | ||||
|         } | ||||
|         // Make sure that the list from preference does not contain any invalid values | ||||
|         final List<String> orderGraphNames = Arrays.stream(tabOrderString.split(",")) | ||||
| @ -489,6 +492,7 @@ public final class Utils { | ||||
|     } | ||||
| 
 | ||||
|     public static boolean isNavRootInCurrentTabs(final String navRootString) { | ||||
|         if (navRootString == null || tabOrderString == null) return false; | ||||
|         return tabOrderString.contains(navRootString); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -54,9 +54,11 @@ public class NewsService extends BaseService { | ||||
|                     callback.onSuccess(null); | ||||
|                     return; | ||||
|                 } | ||||
|                 final List<Notification> result = new ArrayList<>(); | ||||
|                 result.addAll(body.getNewStories()); | ||||
|                 result.addAll(body.getOldStories()); | ||||
|                 final List<Notification> result = new ArrayList<Notification>(); | ||||
|                 final List<Notification> newStories = body.getNewStories(); | ||||
|                 if (newStories != null) result.addAll(newStories); | ||||
|                 final List<Notification> oldStories = body.getOldStories(); | ||||
|                 if (oldStories != null) result.addAll(oldStories); | ||||
|                 callback.onSuccess(result); | ||||
|             } | ||||
| 
 | ||||
|  | ||||
| @ -135,7 +135,7 @@ public class StoriesService extends BaseService { | ||||
|             final JSONArray feedStoriesReel = new JSONObject(body).getJSONArray("tray"); | ||||
|             for (int i = 0; i < feedStoriesReel.length(); ++i) { | ||||
|                 final JSONObject node = feedStoriesReel.getJSONObject(i); | ||||
|                 if (node.optBoolean("hide_from_feed_unit")) continue; | ||||
|                 if (node.optBoolean("hide_from_feed_unit") && Utils.settingsHelper.getBoolean(Constants.HIDE_MUTED_REELS)) continue; | ||||
|                 final JSONObject userJson = node.getJSONObject(node.has("user") ? "user" : "owner"); | ||||
|                 try { | ||||
|                     final User user = new User(userJson.getLong("pk"), | ||||
| @ -177,18 +177,24 @@ public class StoriesService extends BaseService { | ||||
|                                                null, | ||||
|                                                null | ||||
|                     ); | ||||
|                     final String id = node.getString("id"); | ||||
|                     final long timestamp = node.getLong("latest_reel_media"); | ||||
|                     final int mediaCount = node.getInt("media_count"); | ||||
|                     final boolean fullyRead = !node.isNull("seen") && node.getLong("seen") == timestamp; | ||||
|                     final JSONObject itemJson = node.has("items") ? node.getJSONArray("items").optJSONObject(0) : null; | ||||
|                     final boolean isBestie = node.optBoolean("has_besties_media", false); | ||||
|                     StoryModel firstStoryModel = null; | ||||
|                     if (itemJson != null) { | ||||
|                         firstStoryModel = ResponseBodyUtils.parseStoryItem(itemJson, false, null); | ||||
|                     } | ||||
|                     feedStoryModels.add(new FeedStoryModel(id, user, fullyRead, timestamp, firstStoryModel, mediaCount, false, isBestie)); | ||||
|                 } catch (Exception e) {} // to cover promotional reels with non-long user pk's | ||||
|                     feedStoryModels.add(new FeedStoryModel( | ||||
|                             node.getString("id"), | ||||
|                             user, | ||||
|                             fullyRead, | ||||
|                             timestamp, | ||||
|                             firstStoryModel, | ||||
|                             node.getInt("media_count"), | ||||
|                             false, | ||||
|                             node.optBoolean("has_besties_media"))); | ||||
|                 } | ||||
|                 catch (Exception e) {} // to cover promotional reels with non-long user pk's | ||||
|             } | ||||
|             final JSONArray broadcasts = new JSONObject(body).getJSONArray("broadcasts"); | ||||
|             for (int i = 0; i < broadcasts.length(); ++i) { | ||||
| @ -239,13 +245,16 @@ public class StoriesService extends BaseService { | ||||
|                                            null, | ||||
|                                            null | ||||
|                 ); | ||||
|                 final String id = node.getString("id"); | ||||
|                 final long timestamp = node.getLong("published_time"); | ||||
|                 // final JSONObject itemJson = node.has("items") ? node.getJSONArray("items").getJSONObject(0) : null; | ||||
|                 final StoryModel firstStoryModel = ResponseBodyUtils.parseBroadcastItem(node); | ||||
|                 // if (itemJson != null) { | ||||
|                 // } | ||||
|                 feedStoryModels.add(new FeedStoryModel(id, user, false, timestamp, firstStoryModel, 1, true, false)); | ||||
|                 feedStoryModels.add(new FeedStoryModel( | ||||
|                         node.getString("id"), | ||||
|                         user, | ||||
|                         false, | ||||
|                         node.getLong("published_time"), | ||||
|                         ResponseBodyUtils.parseBroadcastItem(node), | ||||
|                         1, | ||||
|                         true, | ||||
|                         false | ||||
|                 )); | ||||
|             } | ||||
|             callback.onSuccess(sort(feedStoryModels)); | ||||
|         } catch (JSONException e) { | ||||
|  | ||||
| @ -326,6 +326,7 @@ | ||||
|     <androidx.constraintlayout.widget.Barrier | ||||
|         android:id="@+id/highlights_barrier" | ||||
|         android:layout_width="wrap_content" | ||||
|         app:constraint_referenced_ids="mainPostCount, mainFollowers, mainFollowing" | ||||
|         android:layout_height="wrap_content" | ||||
|         app:barrierDirection="bottom" /> | ||||
| 
 | ||||
|  | ||||
| @ -5,13 +5,18 @@ | ||||
|     <item | ||||
|         android:id="@+id/action_dms" | ||||
|         android:icon="@drawable/ic_round_send_24" | ||||
|         android:title="@string/action_dms" | ||||
|         android:titleCondensed="@string/action_dms" | ||||
|         app:showAsAction="always" /> | ||||
|         android:title="@string/reply_story" | ||||
|         android:titleCondensed="@string/reply_story" | ||||
|         app:showAsAction="never" /> | ||||
|     <item | ||||
|         android:id="@+id/action_profile" | ||||
|         android:title="@string/open_profile" | ||||
|         android:titleCondensed="@string/open_profile" | ||||
|         app:showAsAction="never" /> | ||||
|     <item | ||||
|         android:id="@+id/action_download" | ||||
|         android:icon="@drawable/ic_download" | ||||
|         android:title="@string/action_download" | ||||
|         android:titleCondensed="@string/action_download" | ||||
|         app:showAsAction="always" /> | ||||
|         app:showAsAction="never" /> | ||||
| </menu> | ||||
| @ -5,24 +5,16 @@ | ||||
|     android:id="@+id/notification_viewer_nav_graph" | ||||
|     app:startDestination="@id/notificationsViewer"> | ||||
| 
 | ||||
|     <fragment | ||||
|         android:id="@+id/notificationsViewer" | ||||
|         android:name="awais.instagrabber.fragments.NotificationsViewerFragment" | ||||
|         android:label="@string/title_notifications" | ||||
|         tools:layout="@layout/fragment_notifications_viewer"> | ||||
|     <include app:graph="@navigation/profile_nav_graph" /> | ||||
| 
 | ||||
|     <action | ||||
|         android:id="@+id/action_global_profileFragment" | ||||
|         app:destination="@id/profile_nav_graph"> | ||||
|         <argument | ||||
|             android:name="type" | ||||
|             android:name="username" | ||||
|             app:argType="string" | ||||
|             app:nullable="false" | ||||
|             android:defaultValue="notif"/> | ||||
|         <argument | ||||
|             android:name="targetId" | ||||
|             android:defaultValue="0L" | ||||
|             app:argType="long" /> | ||||
|         <action | ||||
|             android:id="@+id/action_notificationsViewerFragment_to_storyViewerFragment" | ||||
|             app:destination="@id/storyViewerFragment" /> | ||||
|     </fragment> | ||||
|             app:nullable="true" /> | ||||
|     </action> | ||||
| 
 | ||||
|     <action | ||||
|         android:id="@+id/action_global_notificationsViewerFragment" | ||||
| @ -89,4 +81,23 @@ | ||||
|             android:name="options" | ||||
|             app:argType="awais.instagrabber.repositories.requests.StoryViewerOptions" /> | ||||
|     </fragment> | ||||
| 
 | ||||
|     <fragment | ||||
|         android:id="@+id/notificationsViewer" | ||||
|         android:name="awais.instagrabber.fragments.NotificationsViewerFragment" | ||||
|         android:label="@string/title_notifications" | ||||
|         tools:layout="@layout/fragment_notifications_viewer"> | ||||
|         <argument | ||||
|             android:name="type" | ||||
|             app:argType="string" | ||||
|             app:nullable="false" | ||||
|             android:defaultValue="notif"/> | ||||
|         <argument | ||||
|             android:name="targetId" | ||||
|             android:defaultValue="0L" | ||||
|             app:argType="long" /> | ||||
|         <action | ||||
|             android:id="@+id/action_notifications_to_story" | ||||
|             app:destination="@id/storyViewerFragment" /> | ||||
|     </fragment> | ||||
| </navigation> | ||||
| @ -71,11 +71,9 @@ | ||||
|             app:argType="long" /> | ||||
|     </action> | ||||
| 
 | ||||
|     <include app:graph="@navigation/notification_viewer_nav_graph" /> | ||||
| 
 | ||||
|     <action | ||||
|         android:id="@+id/action_global_notificationsViewerFragment" | ||||
|         app:destination="@id/notification_viewer_nav_graph"> | ||||
|         app:destination="@id/notificationsViewer"> | ||||
|         <argument | ||||
|             android:name="type" | ||||
|             app:argType="string" | ||||
| @ -86,6 +84,22 @@ | ||||
|             app:argType="long" /> | ||||
|     </action> | ||||
| 
 | ||||
|     <fragment | ||||
|         android:id="@+id/notificationsViewer" | ||||
|         android:name="awais.instagrabber.fragments.NotificationsViewerFragment" | ||||
|         android:label="@string/title_notifications" | ||||
|         tools:layout="@layout/fragment_notifications_viewer"> | ||||
|         <argument | ||||
|             android:name="type" | ||||
|             app:argType="string" | ||||
|             app:nullable="false" | ||||
|             android:defaultValue="notif"/> | ||||
|         <argument | ||||
|             android:name="targetId" | ||||
|             android:defaultValue="0L" | ||||
|             app:argType="long" /> | ||||
|     </fragment> | ||||
| 
 | ||||
|     <include app:graph="@navigation/saved_nav_graph" /> | ||||
| 
 | ||||
|     <action | ||||
|  | ||||
| @ -23,6 +23,7 @@ | ||||
|     <string name="update_check">Cerca actualitzacions a l\'inici</string> | ||||
|     <string name="flag_secure">Block screenshots & app preview</string> | ||||
|     <string name="download_user_folder">Descarrega les publicacions a carpetes de nom d\'usuari</string> | ||||
|     <string name="download_prepend_username">Prepend Username to Filename</string> | ||||
|     <string name="mark_as_seen_setting">Marca les històries com a vistes després de visualitzar-es</string> | ||||
|     <string name="mark_as_seen_setting_summary">L\'autor de la història sabrà que l\'has vista</string> | ||||
|     <string name="dm_mark_as_seen_setting">Marca els missatges com a vists després de visualitzar-los</string> | ||||
|  | ||||
| @ -23,6 +23,7 @@ | ||||
|     <string name="update_check">Zkontrolovat aktualizace při spuštění</string> | ||||
|     <string name="flag_secure">Block screenshots & app preview</string> | ||||
|     <string name="download_user_folder">Stáhnout příspěvky do složek s uživatelským jménem</string> | ||||
|     <string name="download_prepend_username">Prepend Username to Filename</string> | ||||
|     <string name="mark_as_seen_setting">Označit příběhy po zhlédnutí jako zobrazené</string> | ||||
|     <string name="mark_as_seen_setting_summary">Autor příběhu bude vědět, že jsi si ho zobrazili</string> | ||||
|     <string name="dm_mark_as_seen_setting">Označovat přímou zprávu po zobrazení jako zobrazenou</string> | ||||
|  | ||||
| @ -23,6 +23,7 @@ | ||||
|     <string name="update_check">Beim Start auf Aktualisierungen prüfen</string> | ||||
|     <string name="flag_secure">Block screenshots & app preview</string> | ||||
|     <string name="download_user_folder">Beiträge in Benutzernamen-Ordner herunterladen</string> | ||||
|     <string name="download_prepend_username">Prepend Username to Filename</string> | ||||
|     <string name="mark_as_seen_setting">Stories nach dem Ansehen als gesehen markieren</string> | ||||
|     <string name="mark_as_seen_setting_summary">Die Person wird wissen, dass du dir die Story angesehen hast</string> | ||||
|     <string name="dm_mark_as_seen_setting">Direktnachrichten nach dem Ansehen als gesehen markieren</string> | ||||
|  | ||||
| @ -23,6 +23,7 @@ | ||||
|     <string name="update_check">Έλεγχος για ενημερώσεις στο ξεκίνημα</string> | ||||
|     <string name="flag_secure">Block screenshots & app preview</string> | ||||
|     <string name="download_user_folder">Λήψη δημοσίευσης στους φακέλους με ονόματα χρηστών</string> | ||||
|     <string name="download_prepend_username">Prepend Username to Filename</string> | ||||
|     <string name="mark_as_seen_setting">Επισήμανση ιστοριών ως προβληθέντων μετά την προβολή</string> | ||||
|     <string name="mark_as_seen_setting_summary">Ο συντάκτης της ιστορίας θα ξέρει ότι την προβάλατε</string> | ||||
|     <string name="dm_mark_as_seen_setting">Σήμανση ΠΜ ως αναγνωσμένου μετά την προβολή</string> | ||||
|  | ||||
| @ -23,6 +23,7 @@ | ||||
|     <string name="update_check">Buscar actualizaciones al inicio</string> | ||||
|     <string name="flag_secure">Bloquea capturas de pantalla & vista previa de aplicaciones</string> | ||||
|     <string name="download_user_folder">Usar subcarpetas con el nombre de usuario</string> | ||||
|     <string name="download_prepend_username">Prepend Username to Filename</string> | ||||
|     <string name="mark_as_seen_setting">Marcar historias como vistas después de verlas</string> | ||||
|     <string name="mark_as_seen_setting_summary">El autor de la historia sabrá que lo has visto</string> | ||||
|     <string name="dm_mark_as_seen_setting">Marcar Mensaje Directo como visto después de verlo</string> | ||||
|  | ||||
| @ -23,6 +23,7 @@ | ||||
|     <string name="update_check">Bilatu eguneratzeak abioan</string> | ||||
|     <string name="flag_secure">Block screenshots & app preview</string> | ||||
|     <string name="download_user_folder">Deskargatu bidalketak erabiltzaile-izena duten karpetetara</string> | ||||
|     <string name="download_prepend_username">Prepend Username to Filename</string> | ||||
|     <string name="mark_as_seen_setting">Markatu istorioak ikusita gisa ikusi ondoren</string> | ||||
|     <string name="mark_as_seen_setting_summary">Istorioaren egileak ikusi duzula jakingo du</string> | ||||
|     <string name="dm_mark_as_seen_setting">Markatu MZ ikusita gisa ikusi ondoren</string> | ||||
|  | ||||
| @ -23,6 +23,7 @@ | ||||
|     <string name="update_check">بررسی بروزرسانی هنگام آغاز برنامه</string> | ||||
|     <string name="flag_secure">Block screenshots & app preview</string> | ||||
|     <string name="download_user_folder">بارگیری پست ها در پوشه های به نام کاربر</string> | ||||
|     <string name="download_prepend_username">Prepend Username to Filename</string> | ||||
|     <string name="mark_as_seen_setting">نشان کرد استوری ها به عنوان دیده شده بعد از دیدن</string> | ||||
|     <string name="mark_as_seen_setting_summary">نویسنده استوری می داند که شما آن را دیده اید</string> | ||||
|     <string name="dm_mark_as_seen_setting">نشان کردن پیام خصوصی بعنوان دیده شده بعد از دیدن</string> | ||||
|  | ||||
| @ -23,6 +23,7 @@ | ||||
|     <string name="update_check">Rechercher les mises à jours au démarrage</string> | ||||
|     <string name="flag_secure">Bloquer les captures d\'écran & l\'aperçu de l\'application</string> | ||||
|     <string name="download_user_folder">Télécharger les messages dans les dossiers des noms d\'utilisateurs</string> | ||||
|     <string name="download_prepend_username">Prepend Username to Filename</string> | ||||
|     <string name="mark_as_seen_setting">Marquer les stories comme vues après consultation</string> | ||||
|     <string name="mark_as_seen_setting_summary">L\'auteur de la story saura que vous l\'avez vue</string> | ||||
|     <string name="dm_mark_as_seen_setting">Marquer les messages privés comme vus après consultation</string> | ||||
|  | ||||
| @ -23,6 +23,7 @@ | ||||
|     <string name="update_check">खुलने पर अपडेट के लिए जाँच करें</string> | ||||
|     <string name="flag_secure">Block screenshots & app preview</string> | ||||
|     <string name="download_user_folder">पोस्ट को ब्यबहारकारी के नाम पर किये फोल्डरस में रखें</string> | ||||
|     <string name="download_prepend_username">Prepend Username to Filename</string> | ||||
|     <string name="mark_as_seen_setting">स्टोरि को दिखने के बाद \"दिखा गया\" दिखादें</string> | ||||
|     <string name="mark_as_seen_setting_summary">सटोरि के लेखक जानेगा कि तुम देखे हो इसको</string> | ||||
|     <string name="dm_mark_as_seen_setting">तुम देखने के बाद सीधा संदेश को \"दिखागया\" लिखा जाएगा</string> | ||||
|  | ||||
| @ -23,6 +23,7 @@ | ||||
|     <string name="update_check">Cek pembaruan saat memulai</string> | ||||
|     <string name="flag_secure">Block screenshots & app preview</string> | ||||
|     <string name="download_user_folder">Unduh kiriman ke folder nama pengguna</string> | ||||
|     <string name="download_prepend_username">Prepend Username to Filename</string> | ||||
|     <string name="mark_as_seen_setting">Tandai cerita dibaca setelah melihat</string> | ||||
|     <string name="mark_as_seen_setting_summary">Pembuat cerita akan tahu Anda melihatnya</string> | ||||
|     <string name="dm_mark_as_seen_setting">Tandai DM dibaca setelah melihat</string> | ||||
|  | ||||
| @ -23,6 +23,7 @@ | ||||
|     <string name="update_check">Verifica per aggiornamenti all\'avvio</string> | ||||
|     <string name="flag_secure">Blocca screenshot & anteprima app</string> | ||||
|     <string name="download_user_folder">Scarica i post nelle cartelle del nome utente</string> | ||||
|     <string name="download_prepend_username">Prepend Username to Filename</string> | ||||
|     <string name="mark_as_seen_setting">Segna le storie come viste dopo la visualizzazione</string> | ||||
|     <string name="mark_as_seen_setting_summary">L\'autore della storia saprà che l\'hai visualizzata</string> | ||||
|     <string name="dm_mark_as_seen_setting">Segna il DM come visto dopo la visualizzazione</string> | ||||
|  | ||||
| @ -23,6 +23,7 @@ | ||||
|     <string name="update_check">起動時にアップデートを確認</string> | ||||
|     <string name="flag_secure">Block screenshots & app preview</string> | ||||
|     <string name="download_user_folder">ユーザ名のフォルダに投稿をダウンロード</string> | ||||
|     <string name="download_prepend_username">Prepend Username to Filename</string> | ||||
|     <string name="mark_as_seen_setting">ストーリーズを表示後に既読にする</string> | ||||
|     <string name="mark_as_seen_setting_summary">ストーリーの作成者は、あなたが閲覧したことを知ることができます。</string> | ||||
|     <string name="dm_mark_as_seen_setting">DMを表示後に既読にする</string> | ||||
|  | ||||
| @ -23,6 +23,7 @@ | ||||
|     <string name="update_check">Check for updates at startup</string> | ||||
|     <string name="flag_secure">Block screenshots & app preview</string> | ||||
|     <string name="download_user_folder">Download posts to username folders</string> | ||||
|     <string name="download_prepend_username">Prepend Username to Filename</string> | ||||
|     <string name="mark_as_seen_setting">Mark stories as seen after viewing</string> | ||||
|     <string name="mark_as_seen_setting_summary">Story author will know you viewed it</string> | ||||
|     <string name="dm_mark_as_seen_setting">Mark DM as seen after viewing</string> | ||||
|  | ||||
| @ -23,6 +23,7 @@ | ||||
|     <string name="update_check">Провери за ажурирање</string> | ||||
|     <string name="flag_secure">Block screenshots & app preview</string> | ||||
|     <string name="download_user_folder">Превземи објави во папката со кориснички имиња</string> | ||||
|     <string name="download_prepend_username">Prepend Username to Filename</string> | ||||
|     <string name="mark_as_seen_setting">Означи ги приказните како видени</string> | ||||
|     <string name="mark_as_seen_setting_summary">Авторот на приказната ќе знае дека сте ја погледнале приказната</string> | ||||
|     <string name="dm_mark_as_seen_setting">Означи порака како видена</string> | ||||
|  | ||||
| @ -23,6 +23,7 @@ | ||||
|     <string name="update_check">Controleer op updates bij het opstarten</string> | ||||
|     <string name="flag_secure">Block screenshots & app preview</string> | ||||
|     <string name="download_user_folder">Download berichten naar gebruikersnaam mappen</string> | ||||
|     <string name="download_prepend_username">Prepend Username to Filename</string> | ||||
|     <string name="mark_as_seen_setting">Markeer verhalen als gelezen na bekijken</string> | ||||
|     <string name="mark_as_seen_setting_summary">Verhaalmaker zal het weten als je het bekeken hebt</string> | ||||
|     <string name="dm_mark_as_seen_setting">Markeer privéberichten als gelezen na bekijken</string> | ||||
|  | ||||
| @ -23,6 +23,7 @@ | ||||
|     <string name="update_check">ଖୋଲିବା ସମୟରେ ଅପଡେଟ ପାଇଁ ଯାଞ୍ଚ କରନ୍ତୁ</string> | ||||
|     <string name="flag_secure">Block screenshots & app preview</string> | ||||
|     <string name="download_user_folder">ଡାଉନଲୋଡ ପୋଷ୍ଟକୁ ବ୍ୟବହାରକାରୀଙ୍କ ନାମରେ ହୋଇଥିବା ସ୍ଥାନ ରେ ରଖ</string> | ||||
|     <string name="download_prepend_username">Prepend Username to Filename</string> | ||||
|     <string name="mark_as_seen_setting">କାହାଣୀଗୁଡିକ ଦେଖିବା ପରେ \'ଦେଖାଗଲା\' ଚିହ୍ନିତ କରନ୍ତୁ |</string> | ||||
|     <string name="mark_as_seen_setting_summary">କାହାଣୀ ପ୍ରେରକ ଜାଣିବେ ତୁମେ ଏହାକୁ ଦେଖିଛ</string> | ||||
|     <string name="dm_mark_as_seen_setting">ବାର୍ତା ଦେଖିବା ପରେ \'ଦେଖାଗଲା\' ଚିହ୍ନିତ କରନ୍ତୁ |</string> | ||||
|  | ||||
| @ -23,6 +23,7 @@ | ||||
|     <string name="update_check">Sprawdź aktualizacje przy starcie</string> | ||||
|     <string name="flag_secure">Blokuj zrzuty ekranu & podgląd aplikacji</string> | ||||
|     <string name="download_user_folder">Pobierz posty do folderów o nazwie użytkownika</string> | ||||
|     <string name="download_prepend_username">Prepend Username to Filename</string> | ||||
|     <string name="mark_as_seen_setting">Oznacz relacje jako widoczne po wyświetleniu</string> | ||||
|     <string name="mark_as_seen_setting_summary">Autor relacji będzie widział, że to wyświetliłeś</string> | ||||
|     <string name="dm_mark_as_seen_setting">Oznacz wiadomość jako przeczytaną</string> | ||||
|  | ||||
| @ -23,6 +23,7 @@ | ||||
|     <string name="update_check">Verificar se há atualizações ao iniciar</string> | ||||
|     <string name="flag_secure">Block screenshots & app preview</string> | ||||
|     <string name="download_user_folder">Baixar publicações para pastas com o nome de usuário</string> | ||||
|     <string name="download_prepend_username">Prepend Username to Filename</string> | ||||
|     <string name="mark_as_seen_setting">Marcar stories como vistos após a visualização</string> | ||||
|     <string name="mark_as_seen_setting_summary">O autor do story saberá que você viu</string> | ||||
|     <string name="dm_mark_as_seen_setting">Marcar DM como vista após a visualização</string> | ||||
|  | ||||
| @ -23,6 +23,7 @@ | ||||
|     <string name="update_check">Проверять наличие обновлений при запуске</string> | ||||
|     <string name="flag_secure">Block screenshots & app preview</string> | ||||
|     <string name="download_user_folder">Скачать публикации в папки с именем пользователя</string> | ||||
|     <string name="download_prepend_username">Prepend Username to Filename</string> | ||||
|     <string name="mark_as_seen_setting">Отметить истории как увиденные после просмотра</string> | ||||
|     <string name="mark_as_seen_setting_summary">Автор истории узнает, что вы просмотрели её</string> | ||||
|     <string name="dm_mark_as_seen_setting">Отметить ЛС как увиденные после просмотра</string> | ||||
|  | ||||
| @ -23,6 +23,7 @@ | ||||
|     <string name="update_check">Kontrolovať aktualizácie pri štarte</string> | ||||
|     <string name="flag_secure">Block screenshots & app preview</string> | ||||
|     <string name="download_user_folder">Ukľadať do priečinkov podľa mena</string> | ||||
|     <string name="download_prepend_username">Prepend Username to Filename</string> | ||||
|     <string name="mark_as_seen_setting">Označiť príbehy po videní ako videné</string> | ||||
|     <string name="mark_as_seen_setting_summary">Autor príbehu bude vedieť že ste ho videli</string> | ||||
|     <string name="dm_mark_as_seen_setting">Po prečítaní, označiť správu ako prečítanú</string> | ||||
|  | ||||
| @ -23,6 +23,7 @@ | ||||
|     <string name="update_check">Güncellemeleri başlangıçta kontrol et</string> | ||||
|     <string name="flag_secure">Block screenshots & app preview</string> | ||||
|     <string name="download_user_folder">İndirmeleri kullanıcı adından oluşan bir alt klasörün içine yap</string> | ||||
|     <string name="download_prepend_username">Prepend Username to Filename</string> | ||||
|     <string name="mark_as_seen_setting">Hikayeleri gördükten sonra görüldü olarak işaretle</string> | ||||
|     <string name="mark_as_seen_setting_summary">Hikayeyi paylaşan gördüğünüzü bilecek</string> | ||||
|     <string name="dm_mark_as_seen_setting">DM\'leri gördükten sonra görüldü olarak işaretle</string> | ||||
|  | ||||
| @ -23,6 +23,7 @@ | ||||
|     <string name="update_check">Kiểm tra cập nhật khi khởi động</string> | ||||
|     <string name="flag_secure">Block screenshots & app preview</string> | ||||
|     <string name="download_user_folder">Tải bài viết xuống theo thư mục tên người dùng trong Downloads</string> | ||||
|     <string name="download_prepend_username">Prepend Username to Filename</string> | ||||
|     <string name="mark_as_seen_setting">Đánh dấu story là đã xem sau khi xem</string> | ||||
|     <string name="mark_as_seen_setting_summary">Người đăng story sẽ biết bạn đã xem nó</string> | ||||
|     <string name="dm_mark_as_seen_setting">Đánh dấu DM là đã xem sau khi xem</string> | ||||
|  | ||||
| @ -23,6 +23,7 @@ | ||||
|     <string name="update_check">启动时检查更新</string> | ||||
|     <string name="flag_secure">屏蔽截图及应用预览</string> | ||||
|     <string name="download_user_folder">下载帖子到用户名文件夹</string> | ||||
|     <string name="download_prepend_username">Prepend Username to Filename</string> | ||||
|     <string name="mark_as_seen_setting">查看快拍后将其标记为已读</string> | ||||
|     <string name="mark_as_seen_setting_summary">快拍作者会知道您已看过</string> | ||||
|     <string name="dm_mark_as_seen_setting">查看私信后将其标记为已读</string> | ||||
|  | ||||
| @ -23,6 +23,7 @@ | ||||
|     <string name="update_check">啟動時檢查更新</string> | ||||
|     <string name="flag_secure">Block screenshots & app preview</string> | ||||
|     <string name="download_user_folder">將貼文下載到用戶名資料夾</string> | ||||
|     <string name="download_prepend_username">Prepend Username to Filename</string> | ||||
|     <string name="mark_as_seen_setting">檢視完限時動態後標記為已讀</string> | ||||
|     <string name="mark_as_seen_setting_summary">限時動態的作者會知道您已查看了此限時動態</string> | ||||
|     <string name="dm_mark_as_seen_setting">檢視完訊息後標記為已讀</string> | ||||
|  | ||||
| @ -26,8 +26,10 @@ | ||||
|     <string name="update_check">Check for updates at startup</string> | ||||
|     <string name="flag_secure">Block screenshots & app preview</string> | ||||
|     <string name="download_user_folder">Download posts to username folders</string> | ||||
|     <string name="download_prepend_username">Prepend Username to Filename</string> | ||||
|     <string name="mark_as_seen_setting">Mark stories as seen after viewing</string> | ||||
|     <string name="mark_as_seen_setting_summary">Story author will know you viewed it</string> | ||||
|     <string name="hide_muted_reels_setting">Hide muted stories from feed</string> | ||||
|     <string name="dm_mark_as_seen_setting">Mark DM as seen after viewing</string> | ||||
|     <string name="dm_mark_as_seen_setting_summary">Other members will know you viewed it</string> | ||||
|     <string name="activity_setting">Enable activity notifications</string> | ||||
| @ -483,6 +485,8 @@ | ||||
|     <string name="crash_report_title">Select an email app to send crash logs</string> | ||||
|     <string name="not_found">Not found!</string> | ||||
|     <string name="rate_limit">Your IP has been rate limited by Instagram. Wait for an hour and try again.</string> | ||||
|     <string name="skip_update">Skip this update</string> | ||||
|     <string name="on_latest_version">You\'re already on the latest version</string> | ||||
|     <string name="tab_order">Screen order</string> | ||||
|     <string name="other_tabs">Other tabs</string> | ||||
|     <string name="tab_order_start_next_launch">The tab order will be reflected on next launch</string> | ||||
|  | ||||
| @ -1 +0,0 @@ | ||||
| {} | ||||
| @ -1 +0,0 @@ | ||||
| {} | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user