From c57e3057329df21affc7a6877d06cfdf7649742c Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Mon, 22 Mar 2021 20:59:08 +0900 Subject: [PATCH 01/95] Create RetrofitFactory to reuse retrofit objects. --- .../instagrabber/InstaGrabberApplication.java | 10 +- .../instagrabber/utils/MediaUploader.java | 2 +- .../instagrabber/webservices/BaseService.java | 42 -------- .../webservices/CollectionService.java | 18 ++-- .../webservices/DirectMessagesService.java | 8 +- .../webservices/DiscoverService.java | 8 +- .../instagrabber/webservices/FeedService.java | 8 +- .../webservices/FriendshipService.java | 16 ++- .../instagrabber/webservices/GifService.java | 8 +- .../webservices/GraphQLService.java | 8 +- .../webservices/LocationService.java | 8 +- .../webservices/MediaService.java | 8 +- .../instagrabber/webservices/NewsService.java | 30 ++---- .../webservices/ProfileService.java | 14 ++- .../webservices/RetrofitFactory.java | 97 +++++++++++++++++++ .../webservices/StoriesService.java | 11 +-- .../instagrabber/webservices/TagsService.java | 8 +- .../instagrabber/webservices/UserService.java | 8 +- .../AddCookiesInterceptor.java | 2 +- .../LoggingInterceptor.java | 2 +- 20 files changed, 170 insertions(+), 146 deletions(-) create mode 100644 app/src/main/java/awais/instagrabber/webservices/RetrofitFactory.java rename app/src/main/java/awais/instagrabber/webservices/{ => interceptors}/AddCookiesInterceptor.java (96%) rename app/src/main/java/awais/instagrabber/webservices/{ => interceptors}/LoggingInterceptor.java (96%) diff --git a/app/src/main/java/awais/instagrabber/InstaGrabberApplication.java b/app/src/main/java/awais/instagrabber/InstaGrabberApplication.java index b90c8343..de555f94 100644 --- a/app/src/main/java/awais/instagrabber/InstaGrabberApplication.java +++ b/app/src/main/java/awais/instagrabber/InstaGrabberApplication.java @@ -17,17 +17,19 @@ import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.LocaleUtils; import awais.instagrabber.utils.SettingsHelper; import awais.instagrabber.utils.TextUtils; +import awais.instagrabber.webservices.RetrofitFactory; import awaisomereport.CrashReporter; -//import awaisomereport.LogCollector; import static awais.instagrabber.utils.CookieUtils.NET_COOKIE_MANAGER; import static awais.instagrabber.utils.Utils.applicationHandler; import static awais.instagrabber.utils.Utils.cacheDir; import static awais.instagrabber.utils.Utils.clipboardManager; import static awais.instagrabber.utils.Utils.datetimeParser; -//import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.settingsHelper; +//import awaisomereport.LogCollector; +//import static awais.instagrabber.utils.Utils.logCollector; + public final class InstaGrabberApplication extends Application { private static final String TAG = "InstaGrabberApplication"; @@ -56,7 +58,7 @@ public final class InstaGrabberApplication extends Application { } if (!BuildConfig.DEBUG) CrashReporter.get(this).start(); -// logCollector = new LogCollector(this); + // logCollector = new LogCollector(this); CookieHandler.setDefault(NET_COOKIE_MANAGER); @@ -85,5 +87,7 @@ public final class InstaGrabberApplication extends Application { if (TextUtils.isEmpty(settingsHelper.getString(Constants.DEVICE_UUID))) { settingsHelper.putString(Constants.DEVICE_UUID, UUID.randomUUID().toString()); } + + RetrofitFactory.setup(this); } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/utils/MediaUploader.java b/app/src/main/java/awais/instagrabber/utils/MediaUploader.java index f9c47a91..beb5c1af 100644 --- a/app/src/main/java/awais/instagrabber/utils/MediaUploader.java +++ b/app/src/main/java/awais/instagrabber/utils/MediaUploader.java @@ -17,7 +17,7 @@ import java.util.Map; import awais.instagrabber.models.UploadPhotoOptions; import awais.instagrabber.models.UploadVideoOptions; -import awais.instagrabber.webservices.AddCookiesInterceptor; +import awais.instagrabber.webservices.interceptors.AddCookiesInterceptor; import okhttp3.Call; import okhttp3.Headers; import okhttp3.MediaType; diff --git a/app/src/main/java/awais/instagrabber/webservices/BaseService.java b/app/src/main/java/awais/instagrabber/webservices/BaseService.java index e30aa992..a6cdf060 100644 --- a/app/src/main/java/awais/instagrabber/webservices/BaseService.java +++ b/app/src/main/java/awais/instagrabber/webservices/BaseService.java @@ -1,50 +1,8 @@ package awais.instagrabber.webservices; -import com.google.gson.FieldNamingPolicy; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - -import java.io.File; - -import awais.instagrabber.BuildConfig; -import awais.instagrabber.repositories.responses.Caption; -import awais.instagrabber.utils.Utils; -import okhttp3.Cache; -import okhttp3.OkHttpClient; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; -import retrofit2.converter.scalars.ScalarsConverterFactory; - public abstract class BaseService { private static final String TAG = "BaseService"; - private Retrofit.Builder builder; - private final int cacheSize = 10 * 1024 * 1024; // 10 MB - private final Cache cache = new Cache(new File(Utils.cacheDir), cacheSize); - - Retrofit.Builder getRetrofitBuilder() { - if (builder == null) { - final OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder() - .addInterceptor(new AddCookiesInterceptor()) - .followRedirects(false) - .followSslRedirects(false) - .cache(cache); - if (BuildConfig.DEBUG) { - // clientBuilder.addInterceptor(new LoggingInterceptor()); - } - final Gson gson = new GsonBuilder() - .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) - .registerTypeAdapter(Caption.class, new Caption.CaptionDeserializer()) - .setLenient() - .create(); - builder = new Retrofit.Builder() - .addConverterFactory(ScalarsConverterFactory.create()) - .addConverterFactory(GsonConverterFactory.create(gson)) - .client(clientBuilder.build()); - } - return builder; - } - // protected String userBreadcrumb(final int size) { // final long term = (random(2, 4) * 1000) + size + (random(15, 21) * 1000); // final float div = (float) size / random(2, 4); diff --git a/app/src/main/java/awais/instagrabber/webservices/CollectionService.java b/app/src/main/java/awais/instagrabber/webservices/CollectionService.java index faafb6d0..e0c50dc2 100644 --- a/app/src/main/java/awais/instagrabber/webservices/CollectionService.java +++ b/app/src/main/java/awais/instagrabber/webservices/CollectionService.java @@ -1,5 +1,7 @@ package awais.instagrabber.webservices; +import android.text.TextUtils; + import androidx.annotation.NonNull; import java.util.HashMap; @@ -14,7 +16,6 @@ import awais.instagrabber.utils.Utils; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; -import retrofit2.Retrofit; public class CollectionService extends BaseService { private static final String TAG = "ProfileService"; @@ -31,10 +32,9 @@ public class CollectionService extends BaseService { this.deviceUuid = deviceUuid; this.csrfToken = csrfToken; this.userId = userId; - final Retrofit retrofit = getRetrofitBuilder() - .baseUrl("https://i.instagram.com") - .build(); - repository = retrofit.create(CollectionRepository.class); + repository = RetrofitFactory.getInstance() + .getRetrofit() + .create(CollectionRepository.class); } public String getCsrfToken() { @@ -66,10 +66,10 @@ public class CollectionService extends BaseService { form.put("module_name", "feed_saved_add_to_collection"); final List ids; ids = posts.stream() - .map(Media::getPk) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - form.put("added_media_ids", "[" + String.join(",", ids) + "]"); + .map(Media::getPk) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + form.put("added_media_ids", "[" + TextUtils.join(",", ids) + "]"); changeCollection(collectionId, "edit", form, callback); } diff --git a/app/src/main/java/awais/instagrabber/webservices/DirectMessagesService.java b/app/src/main/java/awais/instagrabber/webservices/DirectMessagesService.java index 5e4afea5..0688c761 100644 --- a/app/src/main/java/awais/instagrabber/webservices/DirectMessagesService.java +++ b/app/src/main/java/awais/instagrabber/webservices/DirectMessagesService.java @@ -41,7 +41,6 @@ import awais.instagrabber.repositories.responses.giphy.GiphyGif; import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; import retrofit2.Call; -import retrofit2.Retrofit; public class DirectMessagesService extends BaseService { private static final String TAG = "DiscoverService"; @@ -59,10 +58,9 @@ public class DirectMessagesService extends BaseService { this.csrfToken = csrfToken; this.userId = userId; this.deviceUuid = deviceUuid; - final Retrofit retrofit = getRetrofitBuilder() - .baseUrl("https://i.instagram.com") - .build(); - repository = retrofit.create(DirectMessagesRepository.class); + repository = RetrofitFactory.getInstance() + .getRetrofit() + .create(DirectMessagesRepository.class); } public String getCsrfToken() { diff --git a/app/src/main/java/awais/instagrabber/webservices/DiscoverService.java b/app/src/main/java/awais/instagrabber/webservices/DiscoverService.java index 294a6a7d..880ae526 100644 --- a/app/src/main/java/awais/instagrabber/webservices/DiscoverService.java +++ b/app/src/main/java/awais/instagrabber/webservices/DiscoverService.java @@ -12,7 +12,6 @@ import awais.instagrabber.utils.TextUtils; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; -import retrofit2.Retrofit; public class DiscoverService extends BaseService { @@ -23,10 +22,9 @@ public class DiscoverService extends BaseService { private static DiscoverService instance; private DiscoverService() { - final Retrofit retrofit = getRetrofitBuilder() - .baseUrl("https://i.instagram.com") - .build(); - repository = retrofit.create(DiscoverRepository.class); + repository = RetrofitFactory.getInstance() + .getRetrofit() + .create(DiscoverRepository.class); } public static DiscoverService getInstance() { diff --git a/app/src/main/java/awais/instagrabber/webservices/FeedService.java b/app/src/main/java/awais/instagrabber/webservices/FeedService.java index 2bd91690..8b1626ed 100644 --- a/app/src/main/java/awais/instagrabber/webservices/FeedService.java +++ b/app/src/main/java/awais/instagrabber/webservices/FeedService.java @@ -22,7 +22,6 @@ import awais.instagrabber.utils.TextUtils; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; -import retrofit2.Retrofit; public class FeedService extends BaseService { private static final String TAG = "FeedService"; @@ -32,10 +31,9 @@ public class FeedService extends BaseService { private static FeedService instance; private FeedService() { - final Retrofit retrofit = getRetrofitBuilder() - .baseUrl("https://i.instagram.com") - .build(); - repository = retrofit.create(FeedRepository.class); + repository = RetrofitFactory.getInstance() + .getRetrofit() + .create(FeedRepository.class); } public static FeedService getInstance() { diff --git a/app/src/main/java/awais/instagrabber/webservices/FriendshipService.java b/app/src/main/java/awais/instagrabber/webservices/FriendshipService.java index 7c5da8cf..ced49305 100644 --- a/app/src/main/java/awais/instagrabber/webservices/FriendshipService.java +++ b/app/src/main/java/awais/instagrabber/webservices/FriendshipService.java @@ -25,7 +25,6 @@ import awais.instagrabber.utils.Utils; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; -import retrofit2.Retrofit; public class FriendshipService extends BaseService { private static final String TAG = "FriendshipService"; @@ -42,10 +41,9 @@ public class FriendshipService extends BaseService { this.deviceUuid = deviceUuid; this.csrfToken = csrfToken; this.userId = userId; - final Retrofit retrofit = getRetrofitBuilder() - .baseUrl("https://i.instagram.com") - .build(); - repository = retrofit.create(FriendshipRepository.class); + repository = RetrofitFactory.getInstance() + .getRetrofit() + .create(FriendshipRepository.class); } public String getCsrfToken() { @@ -168,8 +166,8 @@ public class FriendshipService extends BaseService { form.put("_uuid", deviceUuid); form.put(story ? "target_reel_author_id" : "target_posts_author_id", String.valueOf(targetUserId)); final Call request = repository.changeMute(unmute ? - "unmute_posts_or_story_from_follow" : - "mute_posts_or_story_from_follow", + "unmute_posts_or_story_from_follow" : + "mute_posts_or_story_from_follow", form); request.enqueue(new Callback() { @Override @@ -198,8 +196,8 @@ public class FriendshipService extends BaseService { if (maxId != null) queryMap.put("max_id", maxId); final Call request = repository.getList( targetUserId, - follower ? "followers" : "following", - queryMap); + follower ? "followers" : "following", + queryMap); request.enqueue(new Callback() { @Override public void onResponse(@NonNull final Call call, @NonNull final Response response) { diff --git a/app/src/main/java/awais/instagrabber/webservices/GifService.java b/app/src/main/java/awais/instagrabber/webservices/GifService.java index 6485efd1..e783f689 100644 --- a/app/src/main/java/awais/instagrabber/webservices/GifService.java +++ b/app/src/main/java/awais/instagrabber/webservices/GifService.java @@ -3,7 +3,6 @@ package awais.instagrabber.webservices; import awais.instagrabber.repositories.GifRepository; import awais.instagrabber.repositories.responses.giphy.GiphyGifResponse; import retrofit2.Call; -import retrofit2.Retrofit; public class GifService extends BaseService { @@ -12,10 +11,9 @@ public class GifService extends BaseService { private static GifService instance; private GifService() { - final Retrofit retrofit = getRetrofitBuilder() - .baseUrl("https://i.instagram.com") - .build(); - repository = retrofit.create(GifRepository.class); + repository = RetrofitFactory.getInstance() + .getRetrofit() + .create(GifRepository.class); } public static GifService getInstance() { diff --git a/app/src/main/java/awais/instagrabber/webservices/GraphQLService.java b/app/src/main/java/awais/instagrabber/webservices/GraphQLService.java index 4cd1c1b6..e56f5581 100644 --- a/app/src/main/java/awais/instagrabber/webservices/GraphQLService.java +++ b/app/src/main/java/awais/instagrabber/webservices/GraphQLService.java @@ -28,7 +28,6 @@ import awais.instagrabber.utils.TextUtils; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; -import retrofit2.Retrofit; public class GraphQLService extends BaseService { private static final String TAG = "GraphQLService"; @@ -39,10 +38,9 @@ public class GraphQLService extends BaseService { private static GraphQLService instance; private GraphQLService() { - final Retrofit retrofit = getRetrofitBuilder() - .baseUrl("https://www.instagram.com") - .build(); - repository = retrofit.create(GraphQLRepository.class); + repository = RetrofitFactory.getInstance() + .getRetrofitWeb() + .create(GraphQLRepository.class); } public static GraphQLService getInstance() { diff --git a/app/src/main/java/awais/instagrabber/webservices/LocationService.java b/app/src/main/java/awais/instagrabber/webservices/LocationService.java index 381ba440..2f8036a3 100644 --- a/app/src/main/java/awais/instagrabber/webservices/LocationService.java +++ b/app/src/main/java/awais/instagrabber/webservices/LocationService.java @@ -11,7 +11,6 @@ import awais.instagrabber.utils.TextUtils; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; -import retrofit2.Retrofit; public class LocationService extends BaseService { private static final String TAG = "LocationService"; @@ -21,10 +20,9 @@ public class LocationService extends BaseService { private static LocationService instance; private LocationService() { - final Retrofit retrofit = getRetrofitBuilder() - .baseUrl("https://i.instagram.com") - .build(); - repository = retrofit.create(LocationRepository.class); + repository = RetrofitFactory.getInstance() + .getRetrofit() + .create(LocationRepository.class); } public static LocationService getInstance() { diff --git a/app/src/main/java/awais/instagrabber/webservices/MediaService.java b/app/src/main/java/awais/instagrabber/webservices/MediaService.java index e1990e87..d820b954 100644 --- a/app/src/main/java/awais/instagrabber/webservices/MediaService.java +++ b/app/src/main/java/awais/instagrabber/webservices/MediaService.java @@ -31,7 +31,6 @@ import awais.instagrabber.utils.Utils; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; -import retrofit2.Retrofit; public class MediaService extends BaseService { private static final String TAG = "MediaService"; @@ -51,10 +50,9 @@ public class MediaService extends BaseService { this.deviceUuid = deviceUuid; this.csrfToken = csrfToken; this.userId = userId; - final Retrofit retrofit = getRetrofitBuilder() - .baseUrl("https://i.instagram.com") - .build(); - repository = retrofit.create(MediaRepository.class); + repository = RetrofitFactory.getInstance() + .getRetrofit() + .create(MediaRepository.class); } public String getCsrfToken() { diff --git a/app/src/main/java/awais/instagrabber/webservices/NewsService.java b/app/src/main/java/awais/instagrabber/webservices/NewsService.java index fe54c13d..667fb647 100644 --- a/app/src/main/java/awais/instagrabber/webservices/NewsService.java +++ b/app/src/main/java/awais/instagrabber/webservices/NewsService.java @@ -1,39 +1,27 @@ package awais.instagrabber.webservices; -import android.util.Log; - import androidx.annotation.NonNull; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; -import awais.instagrabber.BuildConfig; -import awais.instagrabber.models.enums.NotificationType; import awais.instagrabber.repositories.NewsRepository; import awais.instagrabber.repositories.responses.AymlResponse; import awais.instagrabber.repositories.responses.AymlUser; -import awais.instagrabber.repositories.responses.NotificationCounts; -import awais.instagrabber.repositories.responses.UserSearchResponse; import awais.instagrabber.repositories.responses.NewsInboxResponse; import awais.instagrabber.repositories.responses.Notification; import awais.instagrabber.repositories.responses.NotificationArgs; -import awais.instagrabber.repositories.responses.NotificationImage; +import awais.instagrabber.repositories.responses.NotificationCounts; import awais.instagrabber.repositories.responses.User; +import awais.instagrabber.repositories.responses.UserSearchResponse; import awais.instagrabber.utils.Constants; -import awais.instagrabber.utils.Utils; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; -import retrofit2.Retrofit; public class NewsService extends BaseService { private static final String TAG = "NewsService"; @@ -43,10 +31,9 @@ public class NewsService extends BaseService { private static NewsService instance; private NewsService() { - final Retrofit retrofit = getRetrofitBuilder() - .baseUrl("https://i.instagram.com") - .build(); - repository = retrofit.create(NewsRepository.class); + repository = RetrofitFactory.getInstance() + .getRetrofit() + .create(NewsRepository.class); } public static NewsService getInstance() { @@ -131,7 +118,8 @@ public class NewsService extends BaseService { aymlUsers.addAll(oldSuggestions); } - final List newsItems = aymlUsers.stream() + final List newsItems = aymlUsers + .stream() .map(i -> { final User u = i.getUser(); return new Notification( @@ -173,7 +161,9 @@ public class NewsService extends BaseService { return; } - final List newsItems = body.getUsers().stream() + final List newsItems = body + .getUsers() + .stream() .map(u -> { return new Notification( new NotificationArgs( diff --git a/app/src/main/java/awais/instagrabber/webservices/ProfileService.java b/app/src/main/java/awais/instagrabber/webservices/ProfileService.java index 65f787a6..cc8199fd 100644 --- a/app/src/main/java/awais/instagrabber/webservices/ProfileService.java +++ b/app/src/main/java/awais/instagrabber/webservices/ProfileService.java @@ -23,7 +23,6 @@ import awais.instagrabber.utils.Utils; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; -import retrofit2.Retrofit; public class ProfileService extends BaseService { private static final String TAG = "ProfileService"; @@ -33,10 +32,9 @@ public class ProfileService extends BaseService { private static ProfileService instance; private ProfileService() { - final Retrofit retrofit = getRetrofitBuilder() - .baseUrl("https://i.instagram.com") - .build(); - repository = retrofit.create(ProfileRepository.class); + repository = RetrofitFactory.getInstance() + .getRetrofit() + .create(ProfileRepository.class); } public static ProfileService getInstance() { @@ -104,9 +102,9 @@ public class ProfileService extends BaseService { posts = Collections.emptyList(); } else { posts = items.stream() - .map(WrappedMedia::getMedia) - .filter(Objects::nonNull) - .collect(Collectors.toList()); + .map(WrappedMedia::getMedia) + .filter(Objects::nonNull) + .collect(Collectors.toList()); } callback.onSuccess(new PostsFetchResponse( posts, diff --git a/app/src/main/java/awais/instagrabber/webservices/RetrofitFactory.java b/app/src/main/java/awais/instagrabber/webservices/RetrofitFactory.java new file mode 100644 index 00000000..ce8cbfa7 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/webservices/RetrofitFactory.java @@ -0,0 +1,97 @@ +package awais.instagrabber.webservices; + +import android.app.Application; + +import androidx.annotation.NonNull; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import java.io.File; + +import awais.instagrabber.BuildConfig; +import awais.instagrabber.repositories.responses.Caption; +import awais.instagrabber.utils.Utils; +import awais.instagrabber.webservices.interceptors.AddCookiesInterceptor; +import okhttp3.Cache; +import okhttp3.OkHttpClient; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; +import retrofit2.converter.scalars.ScalarsConverterFactory; + +public final class RetrofitFactory { + private static final Object LOCK = new Object(); + + private static RetrofitFactory instance; + + private final Application application; + private final int cacheSize = 10 * 1024 * 1024; // 10 MB + private final Cache cache = new Cache(new File(Utils.cacheDir), cacheSize); + + private Retrofit.Builder builder; + private Retrofit retrofit; + private Retrofit retrofitWeb; + + public static void setup(@NonNull final Application application) { + if (instance == null) { + synchronized (LOCK) { + if (instance == null) { + instance = new RetrofitFactory(application); + } + } + } + } + + public static RetrofitFactory getInstance() { + if (instance == null) { + throw new RuntimeException("Setup not done!"); + } + return instance; + } + + private RetrofitFactory(@NonNull final Application application) { + this.application = application; + } + + private Retrofit.Builder getRetrofitBuilder() { + if (builder == null) { + final OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder() + .addInterceptor(new AddCookiesInterceptor()) + .followRedirects(false) + .followSslRedirects(false) + .cache(cache); + if (BuildConfig.DEBUG) { + // clientBuilder.addInterceptor(new LoggingInterceptor()); + } + final Gson gson = new GsonBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .registerTypeAdapter(Caption.class, new Caption.CaptionDeserializer()) + .setLenient() + .create(); + builder = new Retrofit.Builder() + .addConverterFactory(ScalarsConverterFactory.create()) + .addConverterFactory(GsonConverterFactory.create(gson)) + .client(clientBuilder.build()); + } + return builder; + } + + public Retrofit getRetrofit() { + if (retrofit == null) { + retrofit = getRetrofitBuilder() + .baseUrl("https://i.instagram.com") + .build(); + } + return retrofit; + } + + public Retrofit getRetrofitWeb() { + if (retrofitWeb == null) { + retrofitWeb = getRetrofitBuilder() + .baseUrl("https://www.instagram.com") + .build(); + } + return retrofitWeb; + } +} diff --git a/app/src/main/java/awais/instagrabber/webservices/StoriesService.java b/app/src/main/java/awais/instagrabber/webservices/StoriesService.java index fd05af17..cc19b086 100644 --- a/app/src/main/java/awais/instagrabber/webservices/StoriesService.java +++ b/app/src/main/java/awais/instagrabber/webservices/StoriesService.java @@ -32,7 +32,6 @@ import awais.instagrabber.utils.Utils; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; -import retrofit2.Retrofit; public class StoriesService extends BaseService { private static final String TAG = "StoriesService"; @@ -50,10 +49,9 @@ public class StoriesService extends BaseService { this.csrfToken = csrfToken; this.userId = userId; this.deviceUuid = deviceUuid; - final Retrofit retrofit = getRetrofitBuilder() - .baseUrl("https://i.instagram.com") - .build(); - repository = retrofit.create(StoriesRepository.class); + repository = RetrofitFactory.getInstance() + .getRetrofit() + .create(StoriesRepository.class); } public String getCsrfToken() { @@ -190,8 +188,7 @@ public class StoriesService extends BaseService { firstStoryModel = ResponseBodyUtils.parseStoryItem(itemJson, false, 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 + } 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) { diff --git a/app/src/main/java/awais/instagrabber/webservices/TagsService.java b/app/src/main/java/awais/instagrabber/webservices/TagsService.java index 615d72af..276f0dc7 100644 --- a/app/src/main/java/awais/instagrabber/webservices/TagsService.java +++ b/app/src/main/java/awais/instagrabber/webservices/TagsService.java @@ -21,7 +21,6 @@ import awais.instagrabber.utils.Utils; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; -import retrofit2.Retrofit; public class TagsService extends BaseService { @@ -32,10 +31,9 @@ public class TagsService extends BaseService { private final TagsRepository repository; private TagsService() { - final Retrofit retrofit = getRetrofitBuilder() - .baseUrl("https://i.instagram.com/") - .build(); - repository = retrofit.create(TagsRepository.class); + repository = RetrofitFactory.getInstance() + .getRetrofit() + .create(TagsRepository.class); } public static TagsService getInstance() { diff --git a/app/src/main/java/awais/instagrabber/webservices/UserService.java b/app/src/main/java/awais/instagrabber/webservices/UserService.java index 52bb064d..1266662f 100644 --- a/app/src/main/java/awais/instagrabber/webservices/UserService.java +++ b/app/src/main/java/awais/instagrabber/webservices/UserService.java @@ -12,7 +12,6 @@ import awais.instagrabber.repositories.responses.WrappedUser; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; -import retrofit2.Retrofit; public class UserService extends BaseService { private static final String TAG = UserService.class.getSimpleName(); @@ -22,10 +21,9 @@ public class UserService extends BaseService { private static UserService instance; private UserService() { - final Retrofit retrofit = getRetrofitBuilder() - .baseUrl("https://i.instagram.com") - .build(); - repository = retrofit.create(UserRepository.class); + repository = RetrofitFactory.getInstance() + .getRetrofit() + .create(UserRepository.class); } public static UserService getInstance() { diff --git a/app/src/main/java/awais/instagrabber/webservices/AddCookiesInterceptor.java b/app/src/main/java/awais/instagrabber/webservices/interceptors/AddCookiesInterceptor.java similarity index 96% rename from app/src/main/java/awais/instagrabber/webservices/AddCookiesInterceptor.java rename to app/src/main/java/awais/instagrabber/webservices/interceptors/AddCookiesInterceptor.java index 3b5f709f..610b5f89 100644 --- a/app/src/main/java/awais/instagrabber/webservices/AddCookiesInterceptor.java +++ b/app/src/main/java/awais/instagrabber/webservices/interceptors/AddCookiesInterceptor.java @@ -1,4 +1,4 @@ -package awais.instagrabber.webservices; +package awais.instagrabber.webservices.interceptors; import androidx.annotation.NonNull; diff --git a/app/src/main/java/awais/instagrabber/webservices/LoggingInterceptor.java b/app/src/main/java/awais/instagrabber/webservices/interceptors/LoggingInterceptor.java similarity index 96% rename from app/src/main/java/awais/instagrabber/webservices/LoggingInterceptor.java rename to app/src/main/java/awais/instagrabber/webservices/interceptors/LoggingInterceptor.java index c9358a72..e7bedd3b 100644 --- a/app/src/main/java/awais/instagrabber/webservices/LoggingInterceptor.java +++ b/app/src/main/java/awais/instagrabber/webservices/interceptors/LoggingInterceptor.java @@ -1,4 +1,4 @@ -package awais.instagrabber.webservices; +package awais.instagrabber.webservices.interceptors; import android.util.Log; From 91e13a23ad288243a17ca59776063cbd5212d996 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Mon, 22 Mar 2021 22:02:48 +0900 Subject: [PATCH 02/95] Add ig error interceptor --- .../instagrabber/InstaGrabberApplication.java | 3 - .../instagrabber/activities/MainActivity.java | 2 + .../awais/instagrabber/utils/Constants.java | 1 + .../webservices/RetrofitFactory.java | 17 +-- .../interceptors/IgErrorsInterceptor.java | 108 ++++++++++++++++++ app/src/main/res/values/strings.xml | 6 + 6 files changed, 126 insertions(+), 11 deletions(-) create mode 100644 app/src/main/java/awais/instagrabber/webservices/interceptors/IgErrorsInterceptor.java diff --git a/app/src/main/java/awais/instagrabber/InstaGrabberApplication.java b/app/src/main/java/awais/instagrabber/InstaGrabberApplication.java index de555f94..36807a6c 100644 --- a/app/src/main/java/awais/instagrabber/InstaGrabberApplication.java +++ b/app/src/main/java/awais/instagrabber/InstaGrabberApplication.java @@ -17,7 +17,6 @@ import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.LocaleUtils; import awais.instagrabber.utils.SettingsHelper; import awais.instagrabber.utils.TextUtils; -import awais.instagrabber.webservices.RetrofitFactory; import awaisomereport.CrashReporter; import static awais.instagrabber.utils.CookieUtils.NET_COOKIE_MANAGER; @@ -87,7 +86,5 @@ public final class InstaGrabberApplication extends Application { if (TextUtils.isEmpty(settingsHelper.getString(Constants.DEVICE_UUID))) { settingsHelper.putString(Constants.DEVICE_UUID, UUID.randomUUID().toString()); } - - RetrofitFactory.setup(this); } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/activities/MainActivity.java b/app/src/main/java/awais/instagrabber/activities/MainActivity.java index df2369cc..66670534 100644 --- a/app/src/main/java/awais/instagrabber/activities/MainActivity.java +++ b/app/src/main/java/awais/instagrabber/activities/MainActivity.java @@ -83,6 +83,7 @@ import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.emoji.EmojiParser; import awais.instagrabber.viewmodels.AppStateViewModel; +import awais.instagrabber.webservices.RetrofitFactory; import static awais.instagrabber.utils.NavigationExtensions.setupWithNavController; import static awais.instagrabber.utils.Utils.settingsHelper; @@ -138,6 +139,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage @Override protected void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); + RetrofitFactory.setup(this); binding = ActivityMainBinding.inflate(getLayoutInflater()); final String cookie = settingsHelper.getString(Constants.COOKIE); CookieUtils.setupCookies(cookie); diff --git a/app/src/main/java/awais/instagrabber/utils/Constants.java b/app/src/main/java/awais/instagrabber/utils/Constants.java index 0c92f0fd..4c064e81 100644 --- a/app/src/main/java/awais/instagrabber/utils/Constants.java +++ b/app/src/main/java/awais/instagrabber/utils/Constants.java @@ -111,6 +111,7 @@ public final class Constants { public static final int SHOW_ACTIVITY_REQUEST_CODE = 1738; public static final int SHOW_DM_THREAD = 2000; public static final int DM_SYNC_SERVICE_REQUEST_CODE = 3000; + public static final int GLOBAL_NETWORK_ERROR_DIALOG_REQUEST_CODE = 7777; public static final String ACTION_SHOW_ACTIVITY = "show_activity"; public static final String ACTION_SHOW_DM_THREAD = "show_dm_thread"; diff --git a/app/src/main/java/awais/instagrabber/webservices/RetrofitFactory.java b/app/src/main/java/awais/instagrabber/webservices/RetrofitFactory.java index ce8cbfa7..71c78a4e 100644 --- a/app/src/main/java/awais/instagrabber/webservices/RetrofitFactory.java +++ b/app/src/main/java/awais/instagrabber/webservices/RetrofitFactory.java @@ -1,7 +1,5 @@ package awais.instagrabber.webservices; -import android.app.Application; - import androidx.annotation.NonNull; import com.google.gson.FieldNamingPolicy; @@ -11,9 +9,11 @@ import com.google.gson.GsonBuilder; import java.io.File; import awais.instagrabber.BuildConfig; +import awais.instagrabber.activities.MainActivity; import awais.instagrabber.repositories.responses.Caption; import awais.instagrabber.utils.Utils; import awais.instagrabber.webservices.interceptors.AddCookiesInterceptor; +import awais.instagrabber.webservices.interceptors.IgErrorsInterceptor; import okhttp3.Cache; import okhttp3.OkHttpClient; import retrofit2.Retrofit; @@ -25,7 +25,7 @@ public final class RetrofitFactory { private static RetrofitFactory instance; - private final Application application; + private final MainActivity mainActivity; private final int cacheSize = 10 * 1024 * 1024; // 10 MB private final Cache cache = new Cache(new File(Utils.cacheDir), cacheSize); @@ -33,11 +33,11 @@ public final class RetrofitFactory { private Retrofit retrofit; private Retrofit retrofitWeb; - public static void setup(@NonNull final Application application) { + public static void setup(@NonNull final MainActivity mainActivity) { if (instance == null) { synchronized (LOCK) { if (instance == null) { - instance = new RetrofitFactory(application); + instance = new RetrofitFactory(mainActivity); } } } @@ -50,20 +50,21 @@ public final class RetrofitFactory { return instance; } - private RetrofitFactory(@NonNull final Application application) { - this.application = application; + private RetrofitFactory(@NonNull final MainActivity mainActivity) { + this.mainActivity = mainActivity; } private Retrofit.Builder getRetrofitBuilder() { if (builder == null) { final OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder() - .addInterceptor(new AddCookiesInterceptor()) .followRedirects(false) .followSslRedirects(false) .cache(cache); if (BuildConfig.DEBUG) { // clientBuilder.addInterceptor(new LoggingInterceptor()); } + clientBuilder.addInterceptor(new AddCookiesInterceptor()) + .addInterceptor(new IgErrorsInterceptor(mainActivity)); final Gson gson = new GsonBuilder() .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .registerTypeAdapter(Caption.class, new Caption.CaptionDeserializer()) diff --git a/app/src/main/java/awais/instagrabber/webservices/interceptors/IgErrorsInterceptor.java b/app/src/main/java/awais/instagrabber/webservices/interceptors/IgErrorsInterceptor.java new file mode 100644 index 00000000..413f0e03 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/webservices/interceptors/IgErrorsInterceptor.java @@ -0,0 +1,108 @@ +package awais.instagrabber.webservices.interceptors; + +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; + +import org.json.JSONObject; + +import java.io.IOException; + +import awais.instagrabber.R; +import awais.instagrabber.activities.MainActivity; +import awais.instagrabber.dialogs.ConfirmDialogFragment; +import awais.instagrabber.utils.Constants; +import awais.instagrabber.utils.TextUtils; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; + +public class IgErrorsInterceptor implements Interceptor { + private static final String TAG = IgErrorsInterceptor.class.getSimpleName(); + + private final MainActivity mainActivity; + + public IgErrorsInterceptor(@NonNull final MainActivity mainActivity) { + this.mainActivity = mainActivity; + } + + @NonNull + @Override + public Response intercept(@NonNull final Chain chain) throws IOException { + final Request request = chain.request(); + final Response response = chain.proceed(request); + if (response.isSuccessful()) { + return response; + } + checkError(response); + return response; + } + + private void checkError(@NonNull final Response response) { + final int errorCode = response.code(); + switch (errorCode) { + case 429: // "429 Too Many Requests" + // ('Throttled by Instagram because of too many API requests.'); + showErrorDialog(R.string.throttle_error); + return; + case 431: // "431 Request Header Fields Too Large" + // show dialog? + Log.e(TAG, "Network error: " + getMessage(errorCode, "The request start-line and/or headers are too large to process.")); + return; + } + final ResponseBody body = response.body(); + if (body == null) return; + try { + final String bodyString = body.string(); + final JSONObject jsonObject = new JSONObject(bodyString); + String message = jsonObject.optString("message", null); + if (!TextUtils.isEmpty(message)) { + message = message.toLowerCase(); + switch (message) { + case "user_has_logged_out": + showErrorDialog(R.string.account_logged_out); + return; + case "login_required": + showErrorDialog(R.string.login_required); + return; + case "not authorized to view user": // Do we handle this in profile view fragment? + case "challenge_required": // Since we make users login using browser, we should not be getting this error in api requests + default: + Log.e(TAG, "checkError: " + bodyString); + return; + } + } + final String errorType = jsonObject.optString("error_type", null); + if (TextUtils.isEmpty(errorType)) return; + if (errorType.equals("sentry_block")) { + showErrorDialog(R.string.sentry_block); + return; + } + if (errorType.equals("inactive user")) { + showErrorDialog(R.string.inactive_user); + } + } catch (Exception e) { + Log.e(TAG, "checkError: ", e); + } + } + + @NonNull + private String getMessage(final int errorCode, final String message) { + return String.format("code: %s, internalMessage: %s", errorCode, message); + } + + private void showErrorDialog(@StringRes final int messageResId) { + if (messageResId == 0) return; + final ConfirmDialogFragment dialogFragment = ConfirmDialogFragment.newInstance( + Constants.GLOBAL_NETWORK_ERROR_DIALOG_REQUEST_CODE, + R.string.error, + messageResId, + R.string.ok, + 0, + 0 + ); + dialogFragment.show(mainActivity.getSupportFragmentManager(), "network_error_dialog"); + } +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f65ff8c9..af62d980 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -473,4 +473,10 @@ Removed keyword: %s from filter list Marked as seen Delete unsuccessful + Throttled by Instagram because of too many API requests. Wait for some time before retrying. + Error + This account has been logged out. + Login required! + Sentry block. + User is inactive! From 0d53e244e21aca6b4ee1e4adb5936244d3d15ff1 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Mon, 22 Mar 2021 23:46:47 +0900 Subject: [PATCH 03/95] Destroy references to solve leak --- .../instagrabber/activities/MainActivity.java | 1 + .../webservices/RetrofitFactory.java | 18 ++++++++++++++++-- .../interceptors/IgErrorsInterceptor.java | 7 ++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/activities/MainActivity.java b/app/src/main/java/awais/instagrabber/activities/MainActivity.java index 66670534..8f16cd5d 100644 --- a/app/src/main/java/awais/instagrabber/activities/MainActivity.java +++ b/app/src/main/java/awais/instagrabber/activities/MainActivity.java @@ -248,6 +248,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage Log.e(TAG, "onDestroy: ", e); } unbindActivityCheckerService(); + RetrofitFactory.getInstance().destroy(); } @Override diff --git a/app/src/main/java/awais/instagrabber/webservices/RetrofitFactory.java b/app/src/main/java/awais/instagrabber/webservices/RetrofitFactory.java index 71c78a4e..5062e5c7 100644 --- a/app/src/main/java/awais/instagrabber/webservices/RetrofitFactory.java +++ b/app/src/main/java/awais/instagrabber/webservices/RetrofitFactory.java @@ -25,10 +25,11 @@ public final class RetrofitFactory { private static RetrofitFactory instance; - private final MainActivity mainActivity; private final int cacheSize = 10 * 1024 * 1024; // 10 MB private final Cache cache = new Cache(new File(Utils.cacheDir), cacheSize); + private IgErrorsInterceptor igErrorsInterceptor; + private MainActivity mainActivity; private Retrofit.Builder builder; private Retrofit retrofit; private Retrofit retrofitWeb; @@ -56,6 +57,7 @@ public final class RetrofitFactory { private Retrofit.Builder getRetrofitBuilder() { if (builder == null) { + igErrorsInterceptor = new IgErrorsInterceptor(mainActivity); final OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder() .followRedirects(false) .followSslRedirects(false) @@ -64,7 +66,7 @@ public final class RetrofitFactory { // clientBuilder.addInterceptor(new LoggingInterceptor()); } clientBuilder.addInterceptor(new AddCookiesInterceptor()) - .addInterceptor(new IgErrorsInterceptor(mainActivity)); + .addInterceptor(igErrorsInterceptor); final Gson gson = new GsonBuilder() .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .registerTypeAdapter(Caption.class, new Caption.CaptionDeserializer()) @@ -95,4 +97,16 @@ public final class RetrofitFactory { } return retrofitWeb; } + + public void destroy() { + if (igErrorsInterceptor != null) { + igErrorsInterceptor.destroy(); + } + igErrorsInterceptor = null; + mainActivity = null; + retrofit = null; + retrofitWeb = null; + builder = null; + instance = null; + } } diff --git a/app/src/main/java/awais/instagrabber/webservices/interceptors/IgErrorsInterceptor.java b/app/src/main/java/awais/instagrabber/webservices/interceptors/IgErrorsInterceptor.java index 413f0e03..7d424a2d 100644 --- a/app/src/main/java/awais/instagrabber/webservices/interceptors/IgErrorsInterceptor.java +++ b/app/src/main/java/awais/instagrabber/webservices/interceptors/IgErrorsInterceptor.java @@ -22,7 +22,7 @@ import okhttp3.ResponseBody; public class IgErrorsInterceptor implements Interceptor { private static final String TAG = IgErrorsInterceptor.class.getSimpleName(); - private final MainActivity mainActivity; + private MainActivity mainActivity; public IgErrorsInterceptor(@NonNull final MainActivity mainActivity) { this.mainActivity = mainActivity; @@ -94,6 +94,7 @@ public class IgErrorsInterceptor implements Interceptor { } private void showErrorDialog(@StringRes final int messageResId) { + if (mainActivity == null) return; if (messageResId == 0) return; final ConfirmDialogFragment dialogFragment = ConfirmDialogFragment.newInstance( Constants.GLOBAL_NETWORK_ERROR_DIALOG_REQUEST_CODE, @@ -105,4 +106,8 @@ public class IgErrorsInterceptor implements Interceptor { ); dialogFragment.show(mainActivity.getSupportFragmentManager(), "network_error_dialog"); } + + public void destroy() { + mainActivity = null; + } } \ No newline at end of file From 07f41a8a8c524e4009ea1be8f7537f86573b6f80 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Tue, 30 Mar 2021 20:44:01 +0900 Subject: [PATCH 04/95] Add GraphQl error handling --- .../instagrabber/activities/MainActivity.java | 8 +++++-- .../instagrabber/webservices/NewsService.java | 8 ++----- .../webservices/SearchService.java | 19 +++++---------- .../interceptors/IgErrorsInterceptor.java | 23 +++++++++++++++++++ app/src/main/res/values/strings.xml | 2 ++ 5 files changed, 39 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/activities/MainActivity.java b/app/src/main/java/awais/instagrabber/activities/MainActivity.java index 2da9cb24..95c801ef 100644 --- a/app/src/main/java/awais/instagrabber/activities/MainActivity.java +++ b/app/src/main/java/awais/instagrabber/activities/MainActivity.java @@ -142,8 +142,8 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage @Override protected void onCreate(@Nullable final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); RetrofitFactory.setup(this); + super.onCreate(savedInstanceState); binding = ActivityMainBinding.inflate(getLayoutInflater()); final String cookie = settingsHelper.getString(Constants.COOKIE); CookieUtils.setupCookies(cookie); @@ -253,7 +253,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage Log.e(TAG, "onDestroy: ", e); } unbindActivityCheckerService(); - RetrofitFactory.getInstance().destroy(); + RetrofitFactory.getInstance().destroy(); } @Override @@ -853,4 +853,8 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage public Toolbar getToolbar() { return binding.toolbar; } + + public View getRootView() { + return binding.getRoot(); + } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/webservices/NewsService.java b/app/src/main/java/awais/instagrabber/webservices/NewsService.java index a7664a8a..15b34def 100644 --- a/app/src/main/java/awais/instagrabber/webservices/NewsService.java +++ b/app/src/main/java/awais/instagrabber/webservices/NewsService.java @@ -13,15 +13,11 @@ import awais.instagrabber.repositories.NewsRepository; import awais.instagrabber.repositories.responses.AymlResponse; import awais.instagrabber.repositories.responses.AymlUser; import awais.instagrabber.repositories.responses.NewsInboxResponse; -import awais.instagrabber.repositories.responses.Notification; -import awais.instagrabber.repositories.responses.NotificationArgs; -import awais.instagrabber.repositories.responses.NotificationCounts; -import awais.instagrabber.repositories.responses.NewsInboxResponse; +import awais.instagrabber.repositories.responses.User; +import awais.instagrabber.repositories.responses.UserSearchResponse; import awais.instagrabber.repositories.responses.notification.Notification; import awais.instagrabber.repositories.responses.notification.NotificationArgs; import awais.instagrabber.repositories.responses.notification.NotificationCounts; -import awais.instagrabber.repositories.responses.User; -import awais.instagrabber.repositories.responses.UserSearchResponse; import awais.instagrabber.utils.Constants; import retrofit2.Call; import retrofit2.Callback; diff --git a/app/src/main/java/awais/instagrabber/webservices/SearchService.java b/app/src/main/java/awais/instagrabber/webservices/SearchService.java index 39b5bd65..4144f85d 100644 --- a/app/src/main/java/awais/instagrabber/webservices/SearchService.java +++ b/app/src/main/java/awais/instagrabber/webservices/SearchService.java @@ -1,16 +1,10 @@ package awais.instagrabber.webservices; -import androidx.annotation.NonNull; - import com.google.common.collect.ImmutableMap; import awais.instagrabber.repositories.SearchRepository; import awais.instagrabber.repositories.responses.search.SearchResponse; -import awais.instagrabber.utils.TextUtils; import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; -import retrofit2.Retrofit; public class SearchService extends BaseService { private static final String TAG = "LocationService"; @@ -20,10 +14,9 @@ public class SearchService extends BaseService { private static SearchService instance; private SearchService() { - final Retrofit retrofit = getRetrofitBuilder() - .baseUrl("https://www.instagram.com") - .build(); - repository = retrofit.create(SearchRepository.class); + repository = RetrofitFactory.getInstance() + .getRetrofitWeb() + .create(SearchRepository.class); } public static SearchService getInstance() { @@ -43,8 +36,8 @@ public class SearchService extends BaseService { builder.put("context", context); builder.put("count", "50"); return repository.search(isLoggedIn - ? "https://i.instagram.com/api/v1/fbsearch/topsearch_flat/" - : "https://www.instagram.com/web/search/topsearch/", - builder.build()); + ? "https://i.instagram.com/api/v1/fbsearch/topsearch_flat/" + : "https://www.instagram.com/web/search/topsearch/", + builder.build()); } } diff --git a/app/src/main/java/awais/instagrabber/webservices/interceptors/IgErrorsInterceptor.java b/app/src/main/java/awais/instagrabber/webservices/interceptors/IgErrorsInterceptor.java index 7d424a2d..e27eeae2 100644 --- a/app/src/main/java/awais/instagrabber/webservices/interceptors/IgErrorsInterceptor.java +++ b/app/src/main/java/awais/instagrabber/webservices/interceptors/IgErrorsInterceptor.java @@ -1,10 +1,13 @@ package awais.instagrabber.webservices.interceptors; import android.util.Log; +import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.StringRes; +import com.google.android.material.snackbar.Snackbar; + import org.json.JSONObject; import java.io.IOException; @@ -51,6 +54,16 @@ public class IgErrorsInterceptor implements Interceptor { // show dialog? Log.e(TAG, "Network error: " + getMessage(errorCode, "The request start-line and/or headers are too large to process.")); return; + case 404: + showErrorDialog(R.string.not_found); + return; + case 302: // redirect + final String location = response.header("location"); + if (location.equals("https://www.instagram.com/accounts/login/")) { + // rate limited + showErrorDialog(R.string.rate_limit); + } + return; } final ResponseBody body = response.body(); if (body == null) return; @@ -67,9 +80,13 @@ public class IgErrorsInterceptor implements Interceptor { case "login_required": showErrorDialog(R.string.login_required); return; + case "execution failure": + showSnackbar(message); + return; case "not authorized to view user": // Do we handle this in profile view fragment? case "challenge_required": // Since we make users login using browser, we should not be getting this error in api requests default: + showSnackbar(message); Log.e(TAG, "checkError: " + bodyString); return; } @@ -88,6 +105,12 @@ public class IgErrorsInterceptor implements Interceptor { } } + private void showSnackbar(final String message) { + final View view = mainActivity.getRootView(); + if (view == null) return; + Snackbar.make(view, message, Snackbar.LENGTH_LONG).show(); + } + @NonNull private String getMessage(final int errorCode, final String message) { return String.format("code: %s, internalMessage: %s", errorCode, message); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ce22f77f..86b2d1ac 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -481,4 +481,6 @@ User is inactive! Barinsta Crash Report Select an email app to send crash logs + Not found! + Your IP has been rate limited by Instagram. Wait for an hour and try again. From a2be48029f3787028243ac01cb3d2cf17fb9cf9b Mon Sep 17 00:00:00 2001 From: junhuicoding Date: Wed, 31 Mar 2021 22:53:56 +0800 Subject: [PATCH 05/95] initial implementation --- .../DownloadsPreferencesFragment.java | 9 +++++++ .../awais/instagrabber/utils/Constants.java | 1 + .../instagrabber/utils/DownloadUtils.java | 25 +++++++++++++++---- .../instagrabber/utils/SettingsHelper.java | 3 ++- app/src/main/res/values-ca/strings.xml | 1 + app/src/main/res/values-cs/strings.xml | 1 + app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values-el/strings.xml | 1 + app/src/main/res/values-es/strings.xml | 1 + app/src/main/res/values-eu/strings.xml | 1 + app/src/main/res/values-fa/strings.xml | 1 + app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values-hi/strings.xml | 1 + app/src/main/res/values-in/strings.xml | 1 + app/src/main/res/values-it/strings.xml | 1 + app/src/main/res/values-ja/strings.xml | 1 + app/src/main/res/values-kn/strings.xml | 1 + app/src/main/res/values-mk/strings.xml | 1 + app/src/main/res/values-nl/strings.xml | 1 + app/src/main/res/values-or/strings.xml | 1 + app/src/main/res/values-pl/strings.xml | 1 + app/src/main/res/values-pt/strings.xml | 1 + app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values-sk/strings.xml | 1 + app/src/main/res/values-tr/strings.xml | 1 + app/src/main/res/values-vi/strings.xml | 1 + app/src/main/res/values-zh-rCN/strings.xml | 1 + app/src/main/res/values-zh-rTW/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 29 files changed, 57 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java index c2637b78..63a22dd8 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java @@ -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("Prepend Username to Filename"); + preference.setIconSpaceReserved(false); + return preference; + } + public static class SaveToCustomFolderPreference extends Preference { private AppCompatTextView customPathTextView; private final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener; diff --git a/app/src/main/java/awais/instagrabber/utils/Constants.java b/app/src/main/java/awais/instagrabber/utils/Constants.java index 969f5d6a..0f7618c4 100644 --- a/app/src/main/java/awais/instagrabber/utils/Constants.java +++ b/app/src/main/java/awais/instagrabber/utils/Constants.java @@ -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"; diff --git a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java index 85f45a21..aab36cea 100644 --- a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java @@ -116,7 +116,15 @@ public final class DownloadUtils { 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, @@ -131,8 +139,10 @@ public final class DownloadUtils { 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) { + String usernamePrepend = (username.equals("")) ? "" : "@" + username + "_"; + final String fileName = usernamePrepend + postId + sliderPostfix + getFileExtensionFromUrl(displayUrl); return new File(finalDir, fileName); } @@ -263,7 +273,7 @@ public final class DownloadUtils { ? storyModel.getVideoUrl() : storyModel.getStoryUrl(); final File saveFile = new File(downloadDir, - storyModel.getStoryMediaId() + storyModel.getUsername() + storyModel.getStoryMediaId() + "_" + storyModel.getTimestamp() + DownloadUtils.getFileExtensionFromUrl(url)); download(context, url, saveFile.getAbsolutePath()); @@ -297,7 +307,12 @@ public final class DownloadUtils { case MEDIA_TYPE_IMAGE: case MEDIA_TYPE_VIDEO: { final String url = getUrlOfType(media); - final File file = getDownloadSaveFile(downloadDir, media.getCode(), url); + final File file; + if (Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_PREPEND_USER_NAME) && mediaUser != null) { + file = getDownloadSaveFile(downloadDir, media.getCode(), url, mediaUser.getUsername()); + } else { + file = getDownloadSaveFile(downloadDir, media.getCode(), url); + } map.put(url, file.getAbsolutePath()); break; } diff --git a/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java b/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java index 1ff49767..dd3437de 100755 --- a/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java +++ b/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java @@ -33,6 +33,7 @@ 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; @@ -158,7 +159,7 @@ public final class SettingsHelper { STORY_SORT, PREF_EMOJI_VARIANTS, PREF_REACTIONS, PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT}) 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}) diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 18b0557d..02092f94 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -23,6 +23,7 @@ Cerca actualitzacions a l\'inici Block screenshots & app preview Descarrega les publicacions a carpetes de nom d\'usuari + Prepend Username to Filename Marca les històries com a vistes després de visualitzar-es L\'autor de la història sabrà que l\'has vista Marca els missatges com a vists després de visualitzar-los diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 0bb9e0ac..c5bc0f11 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -23,6 +23,7 @@ Zkontrolovat aktualizace při spuštění Block screenshots & app preview Stáhnout příspěvky do složek s uživatelským jménem + Prepend Username to Filename Označit příběhy po zhlédnutí jako zobrazené Autor příběhu bude vědět, že jsi si ho zobrazili Označovat přímou zprávu po zobrazení jako zobrazenou diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 1f541fcd..e023c6b9 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -23,6 +23,7 @@ Beim Start auf Aktualisierungen prüfen Block screenshots & app preview Beiträge in Benutzernamen-Ordner herunterladen + Prepend Username to Filename Stories nach dem Ansehen als gesehen markieren Die Person wird wissen, dass du dir die Story angesehen hast Direktnachrichten nach dem Ansehen als gesehen markieren diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index f9817a0a..b8c7df56 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -23,6 +23,7 @@ Έλεγχος για ενημερώσεις στο ξεκίνημα Block screenshots & app preview Λήψη δημοσίευσης στους φακέλους με ονόματα χρηστών + Prepend Username to Filename Επισήμανση ιστοριών ως προβληθέντων μετά την προβολή Ο συντάκτης της ιστορίας θα ξέρει ότι την προβάλατε Σήμανση ΠΜ ως αναγνωσμένου μετά την προβολή diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index f392ec86..9ee7d1d0 100755 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -23,6 +23,7 @@ Buscar actualizaciones al inicio Bloquea capturas de pantalla & vista previa de aplicaciones Usar subcarpetas con el nombre de usuario + Prepend Username to Filename Marcar historias como vistas después de verlas El autor de la historia sabrá que lo has visto Marcar Mensaje Directo como visto después de verlo diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index dd0321e2..8f217f17 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -23,6 +23,7 @@ Bilatu eguneratzeak abioan Block screenshots & app preview Deskargatu bidalketak erabiltzaile-izena duten karpetetara + Prepend Username to Filename Markatu istorioak ikusita gisa ikusi ondoren Istorioaren egileak ikusi duzula jakingo du Markatu MZ ikusita gisa ikusi ondoren diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index c6da89cf..75371835 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -23,6 +23,7 @@ بررسی بروزرسانی هنگام آغاز برنامه Block screenshots & app preview بارگیری پست ها در پوشه های به نام کاربر + Prepend Username to Filename نشان کرد استوری ها به عنوان دیده شده بعد از دیدن نویسنده استوری می داند که شما آن را دیده اید نشان کردن پیام خصوصی بعنوان دیده شده بعد از دیدن diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 15f8a5cc..d2fd93dd 100755 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -23,6 +23,7 @@ Rechercher les mises à jours au démarrage Bloquer les captures d\'écran & l\'aperçu de l\'application Télécharger les messages dans les dossiers des noms d\'utilisateurs + Prepend Username to Filename Marquer les stories comme vues après consultation L\'auteur de la story saura que vous l\'avez vue Marquer les messages privés comme vus après consultation diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index bc923135..623bc2ce 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -23,6 +23,7 @@ खुलने पर अपडेट के लिए जाँच करें Block screenshots & app preview पोस्ट को ब्यबहारकारी के नाम पर किये फोल्डरस में रखें + Prepend Username to Filename स्टोरि को दिखने के बाद \"दिखा गया\" दिखादें सटोरि के लेखक जानेगा कि तुम देखे हो इसको तुम देखने के बाद सीधा संदेश को \"दिखागया\" लिखा जाएगा diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 462c1a2e..6801759b 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -23,6 +23,7 @@ Cek pembaruan saat memulai Block screenshots & app preview Unduh kiriman ke folder nama pengguna + Prepend Username to Filename Tandai cerita dibaca setelah melihat Pembuat cerita akan tahu Anda melihatnya Tandai DM dibaca setelah melihat diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index d8202389..eefead25 100755 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -23,6 +23,7 @@ Verifica per aggiornamenti all\'avvio Blocca screenshot & anteprima app Scarica i post nelle cartelle del nome utente + Prepend Username to Filename Segna le storie come viste dopo la visualizzazione L\'autore della storia saprà che l\'hai visualizzata Segna il DM come visto dopo la visualizzazione diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 5a11dc06..178f3726 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -23,6 +23,7 @@ 起動時にアップデートを確認 Block screenshots & app preview ユーザ名のフォルダに投稿をダウンロード + Prepend Username to Filename ストーリーズを表示後に既読にする ストーリーの作成者は、あなたが閲覧したことを知ることができます。 DMを表示後に既読にする diff --git a/app/src/main/res/values-kn/strings.xml b/app/src/main/res/values-kn/strings.xml index 80d8b915..85c0390c 100644 --- a/app/src/main/res/values-kn/strings.xml +++ b/app/src/main/res/values-kn/strings.xml @@ -23,6 +23,7 @@ Check for updates at startup Block screenshots & app preview Download posts to username folders + Prepend Username to Filename Mark stories as seen after viewing Story author will know you viewed it Mark DM as seen after viewing diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index abb89551..118e07f4 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -23,6 +23,7 @@ Провери за ажурирање Block screenshots & app preview Превземи објави во папката со кориснички имиња + Prepend Username to Filename Означи ги приказните како видени Авторот на приказната ќе знае дека сте ја погледнале приказната Означи порака како видена diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index fda4c863..b904bed6 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -23,6 +23,7 @@ Controleer op updates bij het opstarten Block screenshots & app preview Download berichten naar gebruikersnaam mappen + Prepend Username to Filename Markeer verhalen als gelezen na bekijken Verhaalmaker zal het weten als je het bekeken hebt Markeer privéberichten als gelezen na bekijken diff --git a/app/src/main/res/values-or/strings.xml b/app/src/main/res/values-or/strings.xml index ce4427ec..12b95d7b 100644 --- a/app/src/main/res/values-or/strings.xml +++ b/app/src/main/res/values-or/strings.xml @@ -23,6 +23,7 @@ ଖୋଲିବା ସମୟରେ ଅପଡେଟ ପାଇଁ ଯାଞ୍ଚ କରନ୍ତୁ Block screenshots & app preview ଡାଉନଲୋଡ ପୋଷ୍ଟକୁ ବ୍ୟବହାରକାରୀଙ୍କ ନାମରେ ହୋଇଥିବା ସ୍ଥାନ ରେ ରଖ + Prepend Username to Filename କାହାଣୀଗୁଡିକ ଦେଖିବା ପରେ \'ଦେଖାଗଲା\' ଚିହ୍ନିତ କରନ୍ତୁ | କାହାଣୀ ପ୍ରେରକ ଜାଣିବେ ତୁମେ ଏହାକୁ ଦେଖିଛ ବାର୍ତା ଦେଖିବା ପରେ \'ଦେଖାଗଲା\' ଚିହ୍ନିତ କରନ୍ତୁ | diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 93fb3ff8..2c449634 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -23,6 +23,7 @@ Sprawdź aktualizacje przy starcie Blokuj zrzuty ekranu & podgląd aplikacji Pobierz posty do folderów o nazwie użytkownika + Prepend Username to Filename Oznacz relacje jako widoczne po wyświetleniu Autor relacji będzie widział, że to wyświetliłeś Oznacz wiadomość jako przeczytaną diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 4c7bbbc4..f8c3f401 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -23,6 +23,7 @@ Verificar se há atualizações ao iniciar Block screenshots & app preview Baixar publicações para pastas com o nome de usuário + Prepend Username to Filename Marcar stories como vistos após a visualização O autor do story saberá que você viu Marcar DM como vista após a visualização diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index b491a1c5..ca3155ef 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -23,6 +23,7 @@ Проверять наличие обновлений при запуске Block screenshots & app preview Скачать публикации в папки с именем пользователя + Prepend Username to Filename Отметить истории как увиденные после просмотра Автор истории узнает, что вы просмотрели её Отметить ЛС как увиденные после просмотра diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index f12178ec..7b1c175a 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -23,6 +23,7 @@ Kontrolovať aktualizácie pri štarte Block screenshots & app preview Ukľadať do priečinkov podľa mena + Prepend Username to Filename Označiť príbehy po videní ako videné Autor príbehu bude vedieť že ste ho videli Po prečítaní, označiť správu ako prečítanú diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 4ca6836d..fea3a299 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -23,6 +23,7 @@ Güncellemeleri başlangıçta kontrol et Block screenshots & app preview İndirmeleri kullanıcı adından oluşan bir alt klasörün içine yap + Prepend Username to Filename Hikayeleri gördükten sonra görüldü olarak işaretle Hikayeyi paylaşan gördüğünüzü bilecek DM\'leri gördükten sonra görüldü olarak işaretle diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index debb92fc..858193f6 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -23,6 +23,7 @@ Kiểm tra cập nhật khi khởi động Block screenshots & app preview Tải bài viết xuống theo thư mục tên người dùng trong Downloads + Prepend Username to Filename Đánh dấu story là đã xem sau khi xem Người đăng story sẽ biết bạn đã xem nó Đánh dấu DM là đã xem sau khi xem diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index a6fe9c77..0fb2193f 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -23,6 +23,7 @@ 启动时检查更新 屏蔽截图及应用预览 下载帖子到用户名文件夹 + Prepend Username to Filename 查看快拍后将其标记为已读 快拍作者会知道您已看过 查看私信后将其标记为已读 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 26371e94..3ce706fa 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -23,6 +23,7 @@ 啟動時檢查更新 Block screenshots & app preview 將貼文下載到用戶名資料夾 + Prepend Username to Filename 檢視完限時動態後標記為已讀 限時動態的作者會知道您已查看了此限時動態 檢視完訊息後標記為已讀 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d3b7cf2b..11a44e5d 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -26,6 +26,7 @@ Check for updates at startup Block screenshots & app preview Download posts to username folders + Prepend Username to Filename Mark stories as seen after viewing Story author will know you viewed it Mark DM as seen after viewing From 50432468e579317e646e1332274a2393cd287724 Mon Sep 17 00:00:00 2001 From: junhuicoding Date: Wed, 31 Mar 2021 23:58:17 +0800 Subject: [PATCH 06/95] Updated username prepending for sliders and stories --- .../instagrabber/utils/DownloadUtils.java | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java index aab36cea..fe92a0ce 100644 --- a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java @@ -130,9 +130,10 @@ public final class DownloadUtils { 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 @@ -211,11 +212,13 @@ public final class DownloadUtils { username = user.getUsername(); } final File downloadDir = getDownloadDir(null, "@" + username, true); + String usernamePrepend = (Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_PREPEND_USER_NAME) + && user != null) ? username : ""; switch (media.getMediaType()) { case MEDIA_TYPE_IMAGE: case MEDIA_TYPE_VIDEO: { final String url = ResponseBodyUtils.getImageUrl(media); - final File file = getDownloadSaveFile(downloadDir, media.getCode(), url); + final File file = getDownloadSaveFile(downloadDir, media.getCode(), url, usernamePrepend); checkList.add(file.exists()); break; } @@ -225,7 +228,7 @@ 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); + final File file = getDownloadChildSaveFile(downloadDir, media.getCode(), i + 1, url, username); checkList.add(file.exists()); } break; @@ -272,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); + String usernamePrepend = (Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_PREPEND_USER_NAME) + && storyModel.getUsername() != null) ? "@" + storyModel.getUsername() + "_" : ""; final File saveFile = new File(downloadDir, - storyModel.getUsername() + storyModel.getStoryMediaId() - + "_" + storyModel.getTimestamp() - + DownloadUtils.getFileExtensionFromUrl(url)); + usernamePrepend + baseFileName); download(context, url, saveFile.getAbsolutePath()); } @@ -307,7 +312,7 @@ public final class DownloadUtils { case MEDIA_TYPE_IMAGE: case MEDIA_TYPE_VIDEO: { final String url = getUrlOfType(media); - final File file; + File file; if (Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_PREPEND_USER_NAME) && mediaUser != null) { file = getDownloadSaveFile(downloadDir, media.getCode(), url, mediaUser.getUsername()); } else { @@ -334,7 +339,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); + 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; From 7f5b91e343f01e1266133d3cf87bcc27b1512796 Mon Sep 17 00:00:00 2001 From: junhuicoding Date: Thu, 1 Apr 2021 00:24:15 +0800 Subject: [PATCH 07/95] nullchecker fix for profileModel --- .../instagrabber/fragments/main/ProfileFragment.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java index 4b3bfcfd..2f040392 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java @@ -604,10 +604,12 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe usernameTemp = usernameTemp.substring(1); } if (TextUtils.isEmpty(usernameTemp)) { - profileModel = appStateViewModel.getCurrentUser(); - username = profileModel.getUsername(); - setUsernameDelayed(); - setProfileDetails(); + profileModel = appStateViewModel.getCurrentUser(); + if (profileModel != null) { + username = profileModel.getUsername(); + setUsernameDelayed(); + setProfileDetails(); + } } else if (isLoggedIn) { userService.getUsernameInfo(usernameTemp, new ServiceCallback() { From f1df75860b5f66fbe2fa9430d4ecd11b940fb77b Mon Sep 17 00:00:00 2001 From: junhuicoding Date: Thu, 1 Apr 2021 00:48:58 +0800 Subject: [PATCH 08/95] Fix Codacy Issues --- .../main/java/awais/instagrabber/utils/DownloadUtils.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java index fe92a0ce..d06ef850 100644 --- a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java @@ -142,7 +142,7 @@ public final class DownloadUtils { final String sliderPostfix, final String displayUrl, final String username) { - String usernamePrepend = (username.equals("")) ? "" : "@" + username + "_"; + final String usernamePrepend = "".equals(username) ? "" : "@" + username + "_"; final String fileName = usernamePrepend + postId + sliderPostfix + getFileExtensionFromUrl(displayUrl); return new File(finalDir, fileName); } @@ -212,8 +212,8 @@ public final class DownloadUtils { username = user.getUsername(); } final File downloadDir = getDownloadDir(null, "@" + username, true); - String usernamePrepend = (Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_PREPEND_USER_NAME) - && user != null) ? username : ""; + final String usernamePrepend = Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_PREPEND_USER_NAME) + && user != null ? username : ""; switch (media.getMediaType()) { case MEDIA_TYPE_IMAGE: case MEDIA_TYPE_VIDEO: { @@ -339,7 +339,7 @@ public final class DownloadUtils { } final Media child = sliderItems.get(i); final String url = getUrlOfType(child); - String usernamePrepend = (Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_PREPEND_USER_NAME) && mediaUser != null) ? mediaUser.getUsername() : ""; + 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()); } From 2d2ad6130b6ca12ec3e9e8fa91b64ad7103bf529 Mon Sep 17 00:00:00 2001 From: junhuicoding Date: Thu, 1 Apr 2021 00:55:20 +0800 Subject: [PATCH 09/95] Fix Codacy Issues --- app/src/main/java/awais/instagrabber/utils/DownloadUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java index d06ef850..cb3c5273 100644 --- a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java @@ -277,8 +277,8 @@ public final class DownloadUtils { : storyModel.getStoryUrl(); final String baseFileName = storyModel.getStoryMediaId() + "_" + storyModel.getTimestamp() + DownloadUtils.getFileExtensionFromUrl(url); - String usernamePrepend = (Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_PREPEND_USER_NAME) - && storyModel.getUsername() != null) ? "@" + storyModel.getUsername() + "_" : ""; + final String usernamePrepend = Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_PREPEND_USER_NAME) + && storyModel.getUsername() != null ? "@" + storyModel.getUsername() + "_" : ""; final File saveFile = new File(downloadDir, usernamePrepend + baseFileName); download(context, url, saveFile.getAbsolutePath()); From f0167f2d933fb6e7d09cc2dd8b1b83c220faa6bb Mon Sep 17 00:00:00 2001 From: junhuicoding Date: Sat, 3 Apr 2021 01:24:13 +0800 Subject: [PATCH 10/95] Resolve PR comments --- .../fragments/settings/DownloadsPreferencesFragment.java | 2 +- app/src/main/java/awais/instagrabber/utils/DownloadUtils.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java index 63a22dd8..4e75a97f 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java @@ -53,7 +53,7 @@ public class DownloadsPreferencesFragment extends BasePreferencesFragment { private Preference getPrependUsernameToFilenamePreference(@NonNull final Context context) { final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context); preference.setKey(Constants.DOWNLOAD_PREPEND_USER_NAME); - preference.setTitle("Prepend Username to Filename"); + preference.setTitle(R.string.download_prepend_username); preference.setIconSpaceReserved(false); return preference; } diff --git a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java index cb3c5273..52b6a41d 100644 --- a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java @@ -142,7 +142,7 @@ public final class DownloadUtils { final String sliderPostfix, final String displayUrl, final String username) { - final String usernamePrepend = "".equals(username) ? "" : "@" + username + "_"; + final String usernamePrepend = TextUtils.isEmpty(username) ? "" : "@" + username + "_"; final String fileName = usernamePrepend + postId + sliderPostfix + getFileExtensionFromUrl(displayUrl); return new File(finalDir, fileName); } @@ -228,7 +228,7 @@ 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, username); + final File file = getDownloadChildSaveFile(downloadDir, media.getCode(), i + 1, url, usernamePrepend); checkList.add(file.exists()); } break; From f9816ec335adcc18dcc74e714659c0f54831bcda Mon Sep 17 00:00:00 2001 From: junhuicoding Date: Sat, 3 Apr 2021 04:05:00 +0800 Subject: [PATCH 11/95] Fix checkDownloaded to check for both with and without username --- .../java/awais/instagrabber/utils/DownloadUtils.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java index 52b6a41d..b125b320 100644 --- a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java @@ -212,14 +212,13 @@ public final class DownloadUtils { username = user.getUsername(); } final File downloadDir = getDownloadDir(null, "@" + username, true); - final String usernamePrepend = Utils.settingsHelper.getBoolean(Constants.DOWNLOAD_PREPEND_USER_NAME) - && user != null ? username : ""; switch (media.getMediaType()) { case MEDIA_TYPE_IMAGE: case MEDIA_TYPE_VIDEO: { final String url = ResponseBodyUtils.getImageUrl(media); - final File file = getDownloadSaveFile(downloadDir, media.getCode(), url, usernamePrepend); - 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: @@ -228,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, usernamePrepend); - 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: From 043b0d50eb7aeb091f9026222e27729b390b2623 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Sat, 3 Apr 2021 23:08:37 +0900 Subject: [PATCH 12/95] Remove unused files --- app/src/main/resources/feed_response.json | 1 - app/src/main/resources/stories_response.json | 1 - 2 files changed, 2 deletions(-) delete mode 100644 app/src/main/resources/feed_response.json delete mode 100644 app/src/main/resources/stories_response.json diff --git a/app/src/main/resources/feed_response.json b/app/src/main/resources/feed_response.json deleted file mode 100644 index 9e26dfee..00000000 --- a/app/src/main/resources/feed_response.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/app/src/main/resources/stories_response.json b/app/src/main/resources/stories_response.json deleted file mode 100644 index 9e26dfee..00000000 --- a/app/src/main/resources/stories_response.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file From 70055271500c9ae72a070fa98a8fc8de09679c03 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Sat, 3 Apr 2021 23:44:46 +0900 Subject: [PATCH 13/95] Check navigation. Fixes https://github.com/austinhuang0131/barinsta/issues/380 --- .../instagrabber/fragments/main/FeedFragment.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java b/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java index f4b8d47e..bfc1dee3 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java @@ -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; + } } From 122f9b23818f6fd5de6841850dac47bf85494acf Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Sat, 3 Apr 2021 23:49:54 +0900 Subject: [PATCH 14/95] Fix highlights overlapping counts --- app/src/main/res/layout/layout_profile_details.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/layout/layout_profile_details.xml b/app/src/main/res/layout/layout_profile_details.xml index 035a04b0..ea4df164 100644 --- a/app/src/main/res/layout/layout_profile_details.xml +++ b/app/src/main/res/layout/layout_profile_details.xml @@ -326,6 +326,7 @@ From 0cc03b98c25c0675444eb028e27e62ae62b142e1 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Sun, 4 Apr 2021 00:43:01 +0900 Subject: [PATCH 15/95] Completely restart app on login logout --- app/src/main/AndroidManifest.xml | 3 + .../AccountSwitcherDialogFragment.java | 14 ++- .../settings/MorePreferencesFragment.java | 34 ++++-- .../instagrabber/utils/ProcessPhoenix.java | 111 ++++++++++++++++++ 4 files changed, 148 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/awais/instagrabber/utils/ProcessPhoenix.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ce5e77c9..a958a9a3 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -130,6 +130,9 @@ android:name="android.support.PARENT_ACTIVITY" android:value=".activities.MainActivity" /> + { + final Context context = getContext(); + if (context == null) return; + ProcessPhoenix.triggerRebirth(context); + }, 200); }; private final AccountSwitcherAdapter.OnAccountLongClickListener accountLongClickListener = (model, isCurrent) -> { diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java index a7d39fc8..8348123b 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java @@ -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() { @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 @@ -265,9 +274,14 @@ public class MorePreferencesFragment extends BasePreferencesFragment { new RepositoryCallback() { @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 diff --git a/app/src/main/java/awais/instagrabber/utils/ProcessPhoenix.java b/app/src/main/java/awais/instagrabber/utils/ProcessPhoenix.java new file mode 100644 index 00000000..5dcc6e19 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/utils/ProcessPhoenix.java @@ -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). + *

+ * 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. + *

+ * 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. + *

+ * 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 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 runningProcesses = manager.getRunningAppProcesses(); + if (runningProcesses != null) { + for (ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) { + if (processInfo.pid == currentPid && processInfo.processName.endsWith(":phoenix")) { + return true; + } + } + } + return false; + } +} From 04ee3883ea04768a2d2c66ffcd2025ae6a6df51b Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Sat, 3 Apr 2021 11:44:15 -0400 Subject: [PATCH 16/95] close #952 --- .../java/awais/instagrabber/webservices/NewsService.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/webservices/NewsService.java b/app/src/main/java/awais/instagrabber/webservices/NewsService.java index 63fede99..db20e984 100644 --- a/app/src/main/java/awais/instagrabber/webservices/NewsService.java +++ b/app/src/main/java/awais/instagrabber/webservices/NewsService.java @@ -56,9 +56,11 @@ public class NewsService extends BaseService { callback.onSuccess(null); return; } - final List result = new ArrayList<>(); - result.addAll(body.getNewStories()); - result.addAll(body.getOldStories()); + final List result = new ArrayList(); + final List newStories = body.getNewStories(); + if (newStories != null) result.addAll(newStories); + final List oldStories = body.getOldStories(); + if (oldStories != null) result.addAll(oldStories); callback.onSuccess(result); } From 4c800835c395a5fea8f436b13a971528fc0c2d3e Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Sat, 3 Apr 2021 12:29:56 -0400 Subject: [PATCH 17/95] new story menubar; effectively close #553 --- .../fragments/StoryViewerFragment.java | 74 +++++++++++++++---- app/src/main/res/menu/story_menu.xml | 13 +++- 2 files changed, 69 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java index 82438999..ddd84ce3 100644 --- a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java @@ -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); } }); diff --git a/app/src/main/res/menu/story_menu.xml b/app/src/main/res/menu/story_menu.xml index a7293f91..8169fd62 100644 --- a/app/src/main/res/menu/story_menu.xml +++ b/app/src/main/res/menu/story_menu.xml @@ -5,13 +5,18 @@ + android:title="@string/reply_story" + android:titleCondensed="@string/reply_story" + app:showAsAction="never" /> + + app:showAsAction="never" /> \ No newline at end of file From fcdcef4ebb87bc5092d20e64b79878230e72b938 Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Sat, 3 Apr 2021 12:50:46 -0400 Subject: [PATCH 18/95] removed "@" for consistency --- .../main/java/awais/instagrabber/utils/DownloadUtils.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java index ea0d73ac..18bf59c7 100644 --- a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java @@ -142,7 +142,7 @@ public final class DownloadUtils { final String sliderPostfix, final String displayUrl, final String username) { - final String usernamePrepend = TextUtils.isEmpty(username) ? "" : "@" + username + "_"; + final String usernamePrepend = TextUtils.isEmpty(username) ? "" : (username + "_"); final String fileName = usernamePrepend + postId + sliderPostfix + getFileExtensionFromUrl(displayUrl); return new File(finalDir, fileName); } @@ -278,7 +278,7 @@ public final class DownloadUtils { 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() + "_" : ""; + && storyModel.getUsername() != null ? storyModel.getUsername() + "_" : ""; final File saveFile = new File(downloadDir, usernamePrepend + baseFileName); download(context, url, saveFile.getAbsolutePath()); @@ -306,7 +306,7 @@ public final class DownloadUtils { final Map 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: From bcde6ac85ae0fd723e91e4a24749e1a6577e5edb Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Sat, 3 Apr 2021 19:30:00 -0400 Subject: [PATCH 19/95] login update --- .../awais/instagrabber/activities/Login.java | 24 +++---------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/activities/Login.java b/app/src/main/java/awais/instagrabber/activities/Login.java index 37083794..879c92f3 100755 --- a/app/src/main/java/awais/instagrabber/activities/Login.java +++ b/app/src/main/java/awais/instagrabber/activities/Login.java @@ -24,7 +24,7 @@ import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.TextUtils; -public final class Login extends BaseLanguageActivity implements View.OnClickListener, CompoundButton.OnCheckedChangeListener { +public final class Login extends BaseLanguageActivity implements View.OnClickListener { private final WebViewClient webViewClient = new WebViewClient() { @Override public void onPageStarted(final WebView view, final String url, final Bitmap favicon) { @@ -53,7 +53,7 @@ public final class Login extends BaseLanguageActivity implements View.OnClickLis } private final WebChromeClient webChromeClient = new WebChromeClient(); - private String webViewUrl, defaultUserAgent; + private String webViewUrl; private boolean ready = false; private ActivityLoginBinding loginBinding; @@ -65,7 +65,6 @@ public final class Login extends BaseLanguageActivity implements View.OnClickLis initWebView(); - loginBinding.desktopMode.setOnCheckedChangeListener(this); loginBinding.cookies.setOnClickListener(this); loginBinding.refresh.setOnClickListener(this); } @@ -86,23 +85,6 @@ public final class Login extends BaseLanguageActivity implements View.OnClickLis } } - @Override - public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) { - final WebSettings webSettings = loginBinding.webView.getSettings(); - - final String newUserAgent = isChecked - ? "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36" - : defaultUserAgent; - - webSettings.setUserAgentString(newUserAgent); - webSettings.setUseWideViewPort(isChecked); - webSettings.setLoadWithOverviewMode(isChecked); - webSettings.setSupportZoom(isChecked); - webSettings.setBuiltInZoomControls(isChecked); - - loginBinding.webView.loadUrl("https://instagram.com/"); - } - @SuppressLint("SetJavaScriptEnabled") private void initWebView() { if (loginBinding != null) { @@ -110,7 +92,7 @@ public final class Login extends BaseLanguageActivity implements View.OnClickLis loginBinding.webView.setWebViewClient(webViewClient); final WebSettings webSettings = loginBinding.webView.getSettings(); if (webSettings != null) { - if (defaultUserAgent == null) defaultUserAgent = webSettings.getUserAgentString(); + webSettings.setUserAgentString("Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.105 Mobile Safari/537.36"); webSettings.setJavaScriptEnabled(true); webSettings.setDomStorageEnabled(true); webSettings.setSupportZoom(true); From eafce5309a26e387e1cac4b4255f62a003b0df4c Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Sat, 3 Apr 2021 21:37:57 -0400 Subject: [PATCH 20/95] New Crowdin updates (#841) * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Vietnamese) * New translations arrays.xml (Vietnamese) * New translations strings.xml (French) * New translations strings.xml (Italian) * New translations strings.xml (Vietnamese) * New translations strings.xml (French) * New translations strings.xml (Russian) * New translations strings.xml (Hindi) * New translations strings.xml (Persian) * New translations strings.xml (Indonesian) * New translations strings.xml (Portuguese, Brazilian) * New translations strings.xml (Vietnamese) * New translations strings.xml (Chinese Traditional) * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Turkish) * New translations strings.xml (Slovak) * New translations strings.xml (Polish) * New translations strings.xml (Spanish) * New translations strings.xml (Dutch) * New translations strings.xml (Macedonian) * New translations strings.xml (Japanese) * New translations strings.xml (Italian) * New translations strings.xml (Basque) * New translations strings.xml (Greek) * New translations strings.xml (German) * New translations strings.xml (Czech) * New translations strings.xml (Catalan) * New translations strings.xml (Odia) * Update source file strings.xml * New translations strings.xml (French) * New translations strings.xml (Spanish) * New translations strings.xml (Italian) * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Spanish) * New translations strings.xml (Persian) * New translations arrays.xml (Basque) * New translations strings.xml (Basque) * New translations strings.xml (Basque) * New translations strings.xml (Polish) * New translations strings.xml (Polish) * New translations strings.xml (Portuguese, Brazilian) * New translations strings.xml (Portuguese, Brazilian) * New translations strings.xml (Catalan) * New translations strings.xml (Catalan) * New translations strings.xml (Catalan) * New translations strings.xml (Greek) * New translations strings.xml (Greek) * New translations strings.xml (Greek) * New translations strings.xml (Greek) * New translations strings.xml (Greek) * New translations arrays.xml (Greek) * New translations strings.xml (Greek) * New translations strings.xml (Greek) * New translations strings.xml (Greek) * New translations strings.xml (Greek) * New translations strings.xml (Spanish) * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Russian) * New translations strings.xml (Slovak) * New translations strings.xml (Turkish) * New translations strings.xml (Persian) * New translations strings.xml (Chinese Traditional) * New translations strings.xml (Vietnamese) * New translations strings.xml (Indonesian) * New translations strings.xml (Hindi) * New translations strings.xml (Dutch) * New translations strings.xml (Macedonian) * New translations strings.xml (French) * New translations strings.xml (Basque) * New translations strings.xml (Polish) * New translations strings.xml (Portuguese, Brazilian) * New translations strings.xml (Catalan) * New translations strings.xml (Greek) * New translations strings.xml (Czech) * New translations strings.xml (German) * New translations strings.xml (Italian) * New translations strings.xml (Japanese) * New translations strings.xml (Odia) * Update source file strings.xml * New translations strings.xml (Spanish) * New translations strings.xml (Italian) * New translations strings.xml (Portuguese, Brazilian) * New translations strings.xml (Greek) * New translations strings.xml (Russian) * New translations strings.xml (Russian) * New translations strings.xml (Persian) * New translations strings.xml (French) * New translations strings.xml (Russian) * New translations strings.xml (Polish) * New translations strings.xml (Polish) * New translations strings.xml (Polish) * New translations strings.xml (Spanish) * New translations arrays.xml (Chinese Traditional) * New translations arrays.xml (Russian) * New translations strings.xml (Russian) * New translations arrays.xml (Slovak) * New translations strings.xml (Slovak) * New translations arrays.xml (Turkish) * New translations strings.xml (Turkish) * New translations arrays.xml (Chinese Simplified) * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Chinese Traditional) * New translations strings.xml (Dutch) * New translations arrays.xml (Vietnamese) * New translations strings.xml (Vietnamese) * New translations arrays.xml (Portuguese, Brazilian) * New translations arrays.xml (Indonesian) * New translations strings.xml (Indonesian) * New translations arrays.xml (Persian) * New translations arrays.xml (Hindi) * New translations strings.xml (Hindi) * New translations arrays.xml (Odia) * New translations arrays.xml (Polish) * New translations arrays.xml (Dutch) * New translations strings.xml (Persian) * New translations arrays.xml (Spanish) * New translations arrays.xml (Basque) * New translations strings.xml (Basque) * New translations strings.xml (Polish) * New translations strings.xml (Portuguese, Brazilian) * New translations strings.xml (Catalan) * New translations strings.xml (Greek) * New translations arrays.xml (Greek) * New translations arrays.xml (French) * New translations strings.xml (French) * New translations arrays.xml (Catalan) * New translations strings.xml (Macedonian) * New translations arrays.xml (Czech) * New translations strings.xml (Czech) * New translations arrays.xml (German) * New translations strings.xml (German) * New translations arrays.xml (Italian) * New translations strings.xml (Italian) * New translations arrays.xml (Japanese) * New translations strings.xml (Japanese) * New translations arrays.xml (Macedonian) * New translations strings.xml (Odia) * Update source file arrays.xml * Update source file strings.xml * New translations strings.xml (Spanish) * New translations strings.xml (Russian) * New translations strings.xml (Slovak) * New translations strings.xml (Turkish) * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Chinese Traditional) * New translations strings.xml (Dutch) * New translations strings.xml (Vietnamese) * New translations strings.xml (Indonesian) * New translations strings.xml (Hindi) * New translations strings.xml (Persian) * New translations strings.xml (Basque) * New translations strings.xml (Polish) * New translations strings.xml (Portuguese, Brazilian) * New translations strings.xml (Catalan) * New translations strings.xml (Greek) * New translations strings.xml (French) * New translations strings.xml (Macedonian) * New translations strings.xml (Czech) * New translations strings.xml (German) * New translations strings.xml (Italian) * New translations strings.xml (Japanese) * New translations strings.xml (Odia) * Update source file strings.xml * New translations strings.xml (Spanish) * Update source file strings.xml * New translations strings.xml (Spanish) * New translations strings.xml (Russian) * New translations strings.xml (Slovak) * New translations strings.xml (Turkish) * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Chinese Traditional) * New translations strings.xml (Dutch) * New translations strings.xml (Vietnamese) * New translations strings.xml (Indonesian) * New translations strings.xml (Hindi) * New translations strings.xml (Persian) * New translations strings.xml (Basque) * New translations strings.xml (Polish) * New translations strings.xml (Portuguese, Brazilian) * New translations strings.xml (Catalan) * New translations strings.xml (Greek) * New translations strings.xml (French) * New translations strings.xml (Macedonian) * New translations strings.xml (Czech) * New translations strings.xml (German) * New translations strings.xml (Italian) * New translations strings.xml (Japanese) * New translations strings.xml (Odia) * New translations strings.xml (Italian) * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Polish) * New translations strings.xml (French) * New translations strings.xml (Japanese) * New translations strings.xml (Spanish) * New translations strings.xml (Russian) * New translations strings.xml (Slovak) * New translations strings.xml (Turkish) * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Chinese Traditional) * New translations strings.xml (Dutch) * New translations strings.xml (Vietnamese) * New translations strings.xml (Indonesian) * New translations strings.xml (Hindi) * New translations strings.xml (Persian) * New translations strings.xml (Basque) * New translations strings.xml (Polish) * New translations strings.xml (Portuguese, Brazilian) * New translations strings.xml (Catalan) * New translations strings.xml (Greek) * New translations strings.xml (French) * New translations strings.xml (Macedonian) * New translations strings.xml (Czech) * New translations strings.xml (German) * New translations strings.xml (Italian) * New translations strings.xml (Japanese) * New translations strings.xml (Odia) * Update source file strings.xml * New translations strings.xml (Spanish) * New translations strings.xml (French) * New translations strings.xml (Italian) * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Portuguese, Brazilian) * Update source file strings.xml * New translations strings.xml (Spanish) * New translations strings.xml (Russian) * New translations strings.xml (Slovak) * New translations strings.xml (Turkish) * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Chinese Traditional) * New translations strings.xml (Dutch) * New translations strings.xml (Vietnamese) * New translations strings.xml (Indonesian) * New translations strings.xml (Hindi) * New translations strings.xml (Persian) * New translations strings.xml (Basque) * New translations strings.xml (Polish) * New translations strings.xml (Portuguese, Brazilian) * New translations strings.xml (Catalan) * New translations strings.xml (Greek) * New translations strings.xml (French) * New translations strings.xml (Macedonian) * New translations strings.xml (Czech) * New translations strings.xml (German) * New translations strings.xml (Italian) * New translations strings.xml (Japanese) * New translations strings.xml (Odia) * New translations strings.xml (Portuguese, Brazilian) * New translations strings.xml (French) --- app/src/github/res/values-ca/strings.xml | 6 + app/src/github/res/values-cs/strings.xml | 6 + app/src/github/res/values-de/strings.xml | 6 + app/src/github/res/values-el/strings.xml | 6 + app/src/github/res/values-es/strings.xml | 6 + app/src/github/res/values-eu/strings.xml | 6 + app/src/github/res/values-fa/strings.xml | 6 + app/src/github/res/values-fr/strings.xml | 6 + app/src/github/res/values-hi/strings.xml | 6 + app/src/github/res/values-in/strings.xml | 6 + app/src/github/res/values-it/strings.xml | 6 + app/src/github/res/values-ja/strings.xml | 6 + app/src/github/res/values-mk/strings.xml | 6 + app/src/github/res/values-nl/strings.xml | 6 + app/src/github/res/values-or/strings.xml | 6 + app/src/github/res/values-pl/strings.xml | 6 + app/src/github/res/values-pt/strings.xml | 6 + app/src/github/res/values-ru/strings.xml | 6 + app/src/github/res/values-sk/strings.xml | 6 + app/src/github/res/values-tr/strings.xml | 6 + app/src/github/res/values-vi/strings.xml | 6 + app/src/github/res/values-zh-rCN/strings.xml | 6 + app/src/github/res/values-zh-rTW/strings.xml | 6 + app/src/main/res/values-ca/arrays.xml | 9 + app/src/main/res/values-ca/strings.xml | 49 +++- app/src/main/res/values-cs/arrays.xml | 9 + app/src/main/res/values-cs/strings.xml | 19 ++ app/src/main/res/values-de/arrays.xml | 9 + app/src/main/res/values-de/strings.xml | 19 ++ app/src/main/res/values-el/arrays.xml | 11 +- app/src/main/res/values-el/strings.xml | 279 ++++++++++--------- app/src/main/res/values-es/arrays.xml | 9 + app/src/main/res/values-es/strings.xml | 21 +- app/src/main/res/values-eu/arrays.xml | 13 +- app/src/main/res/values-eu/strings.xml | 39 ++- app/src/main/res/values-fa/arrays.xml | 9 + app/src/main/res/values-fa/strings.xml | 45 ++- app/src/main/res/values-fr/arrays.xml | 9 + app/src/main/res/values-fr/strings.xml | 21 +- app/src/main/res/values-hi/arrays.xml | 9 + app/src/main/res/values-hi/strings.xml | 19 ++ app/src/main/res/values-in/arrays.xml | 9 + app/src/main/res/values-in/strings.xml | 19 ++ app/src/main/res/values-it/arrays.xml | 9 + app/src/main/res/values-it/strings.xml | 21 +- app/src/main/res/values-ja/arrays.xml | 9 + app/src/main/res/values-ja/strings.xml | 39 ++- app/src/main/res/values-mk/arrays.xml | 9 + app/src/main/res/values-mk/strings.xml | 19 ++ app/src/main/res/values-nl/arrays.xml | 9 + app/src/main/res/values-nl/strings.xml | 19 ++ app/src/main/res/values-or/arrays.xml | 9 + app/src/main/res/values-or/strings.xml | 19 ++ app/src/main/res/values-pl/arrays.xml | 9 + app/src/main/res/values-pl/strings.xml | 29 +- app/src/main/res/values-pt/arrays.xml | 9 + app/src/main/res/values-pt/strings.xml | 39 ++- app/src/main/res/values-ru/arrays.xml | 9 + app/src/main/res/values-ru/strings.xml | 45 ++- app/src/main/res/values-sk/arrays.xml | 9 + app/src/main/res/values-sk/strings.xml | 19 ++ app/src/main/res/values-tr/arrays.xml | 9 + app/src/main/res/values-tr/strings.xml | 19 ++ app/src/main/res/values-vi/arrays.xml | 19 +- app/src/main/res/values-vi/strings.xml | 111 +++++--- app/src/main/res/values-zh-rCN/arrays.xml | 9 + app/src/main/res/values-zh-rCN/strings.xml | 31 ++- app/src/main/res/values-zh-rTW/arrays.xml | 9 + app/src/main/res/values-zh-rTW/strings.xml | 19 ++ 69 files changed, 1051 insertions(+), 269 deletions(-) create mode 100644 app/src/github/res/values-ca/strings.xml create mode 100644 app/src/github/res/values-cs/strings.xml create mode 100644 app/src/github/res/values-de/strings.xml create mode 100644 app/src/github/res/values-el/strings.xml create mode 100644 app/src/github/res/values-es/strings.xml create mode 100644 app/src/github/res/values-eu/strings.xml create mode 100644 app/src/github/res/values-fa/strings.xml create mode 100644 app/src/github/res/values-fr/strings.xml create mode 100644 app/src/github/res/values-hi/strings.xml create mode 100644 app/src/github/res/values-in/strings.xml create mode 100644 app/src/github/res/values-it/strings.xml create mode 100644 app/src/github/res/values-ja/strings.xml create mode 100644 app/src/github/res/values-mk/strings.xml create mode 100644 app/src/github/res/values-nl/strings.xml create mode 100644 app/src/github/res/values-or/strings.xml create mode 100644 app/src/github/res/values-pl/strings.xml create mode 100644 app/src/github/res/values-pt/strings.xml create mode 100644 app/src/github/res/values-ru/strings.xml create mode 100644 app/src/github/res/values-sk/strings.xml create mode 100644 app/src/github/res/values-tr/strings.xml create mode 100644 app/src/github/res/values-vi/strings.xml create mode 100644 app/src/github/res/values-zh-rCN/strings.xml create mode 100644 app/src/github/res/values-zh-rTW/strings.xml diff --git a/app/src/github/res/values-ca/strings.xml b/app/src/github/res/values-ca/strings.xml new file mode 100644 index 00000000..599c258b --- /dev/null +++ b/app/src/github/res/values-ca/strings.xml @@ -0,0 +1,6 @@ + + + Habilita el Sentry + Sentry és un oient/intèrpret d\'error que envia asíncronament l\'error/esdeveniment a Sentry.io + Sentry s\'iniciarà al pròxim llançament + diff --git a/app/src/github/res/values-cs/strings.xml b/app/src/github/res/values-cs/strings.xml new file mode 100644 index 00000000..078fa6a8 --- /dev/null +++ b/app/src/github/res/values-cs/strings.xml @@ -0,0 +1,6 @@ + + + Enable Sentry + Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io + Sentry will start on next launch + diff --git a/app/src/github/res/values-de/strings.xml b/app/src/github/res/values-de/strings.xml new file mode 100644 index 00000000..078fa6a8 --- /dev/null +++ b/app/src/github/res/values-de/strings.xml @@ -0,0 +1,6 @@ + + + Enable Sentry + Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io + Sentry will start on next launch + diff --git a/app/src/github/res/values-el/strings.xml b/app/src/github/res/values-el/strings.xml new file mode 100644 index 00000000..579b3cc5 --- /dev/null +++ b/app/src/github/res/values-el/strings.xml @@ -0,0 +1,6 @@ + + + Ενεργοποίηση Sentry + Το Sentry είναι διαχειριστής σφαλμάτων ασύγχρονης αποστολής του σφάλματος/συμβάντος στο Sentry.io + Το Sentry θα ξεκινήσει στην επόμενη εκκίνηση + diff --git a/app/src/github/res/values-es/strings.xml b/app/src/github/res/values-es/strings.xml new file mode 100644 index 00000000..59f6cc24 --- /dev/null +++ b/app/src/github/res/values-es/strings.xml @@ -0,0 +1,6 @@ + + + Activar Sentry + Sentry es un oyente/manejador de errores que asincrónicamente envía el error/evento a Sentry.io + Sentry comenzará en el próximo inicio + diff --git a/app/src/github/res/values-eu/strings.xml b/app/src/github/res/values-eu/strings.xml new file mode 100644 index 00000000..078fa6a8 --- /dev/null +++ b/app/src/github/res/values-eu/strings.xml @@ -0,0 +1,6 @@ + + + Enable Sentry + Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io + Sentry will start on next launch + diff --git a/app/src/github/res/values-fa/strings.xml b/app/src/github/res/values-fa/strings.xml new file mode 100644 index 00000000..078fa6a8 --- /dev/null +++ b/app/src/github/res/values-fa/strings.xml @@ -0,0 +1,6 @@ + + + Enable Sentry + Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io + Sentry will start on next launch + diff --git a/app/src/github/res/values-fr/strings.xml b/app/src/github/res/values-fr/strings.xml new file mode 100644 index 00000000..74186f6f --- /dev/null +++ b/app/src/github/res/values-fr/strings.xml @@ -0,0 +1,6 @@ + + + Activer Sentry + Sentry est un écouteur/gestionnaire d\'erreurs qui envoie de manière asynchrone l\'erreur/l\'événement à Sentry.io + Sentry commencera au prochain lancement + diff --git a/app/src/github/res/values-hi/strings.xml b/app/src/github/res/values-hi/strings.xml new file mode 100644 index 00000000..078fa6a8 --- /dev/null +++ b/app/src/github/res/values-hi/strings.xml @@ -0,0 +1,6 @@ + + + Enable Sentry + Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io + Sentry will start on next launch + diff --git a/app/src/github/res/values-in/strings.xml b/app/src/github/res/values-in/strings.xml new file mode 100644 index 00000000..078fa6a8 --- /dev/null +++ b/app/src/github/res/values-in/strings.xml @@ -0,0 +1,6 @@ + + + Enable Sentry + Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io + Sentry will start on next launch + diff --git a/app/src/github/res/values-it/strings.xml b/app/src/github/res/values-it/strings.xml new file mode 100644 index 00000000..99ec0ec1 --- /dev/null +++ b/app/src/github/res/values-it/strings.xml @@ -0,0 +1,6 @@ + + + Abilita Sentry + Sentry è un ascoltatore/gestore di errori che invia asincronicamente l\'errore/evento a Sentry.io + Sentry comincerà al prossimo lancio + diff --git a/app/src/github/res/values-ja/strings.xml b/app/src/github/res/values-ja/strings.xml new file mode 100644 index 00000000..078fa6a8 --- /dev/null +++ b/app/src/github/res/values-ja/strings.xml @@ -0,0 +1,6 @@ + + + Enable Sentry + Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io + Sentry will start on next launch + diff --git a/app/src/github/res/values-mk/strings.xml b/app/src/github/res/values-mk/strings.xml new file mode 100644 index 00000000..078fa6a8 --- /dev/null +++ b/app/src/github/res/values-mk/strings.xml @@ -0,0 +1,6 @@ + + + Enable Sentry + Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io + Sentry will start on next launch + diff --git a/app/src/github/res/values-nl/strings.xml b/app/src/github/res/values-nl/strings.xml new file mode 100644 index 00000000..078fa6a8 --- /dev/null +++ b/app/src/github/res/values-nl/strings.xml @@ -0,0 +1,6 @@ + + + Enable Sentry + Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io + Sentry will start on next launch + diff --git a/app/src/github/res/values-or/strings.xml b/app/src/github/res/values-or/strings.xml new file mode 100644 index 00000000..078fa6a8 --- /dev/null +++ b/app/src/github/res/values-or/strings.xml @@ -0,0 +1,6 @@ + + + Enable Sentry + Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io + Sentry will start on next launch + diff --git a/app/src/github/res/values-pl/strings.xml b/app/src/github/res/values-pl/strings.xml new file mode 100644 index 00000000..481f46c5 --- /dev/null +++ b/app/src/github/res/values-pl/strings.xml @@ -0,0 +1,6 @@ + + + Włącz Sentry + Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io + Sentry rozpocznie się przy następnym uruchomieniu + diff --git a/app/src/github/res/values-pt/strings.xml b/app/src/github/res/values-pt/strings.xml new file mode 100644 index 00000000..7a0848a7 --- /dev/null +++ b/app/src/github/res/values-pt/strings.xml @@ -0,0 +1,6 @@ + + + Ativar Sentry + Sentry é um ouvinte/gestor de erros que assincronicamente envia o erro/evento para Sentry.io + Sentry começará no próximo início + diff --git a/app/src/github/res/values-ru/strings.xml b/app/src/github/res/values-ru/strings.xml new file mode 100644 index 00000000..0de74041 --- /dev/null +++ b/app/src/github/res/values-ru/strings.xml @@ -0,0 +1,6 @@ + + + Включить режим \"часового\" + \"Часовой\" - это слушатель/обработчик ошибок, который асинхронно отправляет ошибку/событие на Sentry.io + \"Часовой\" будет запущен при следующем запуске + diff --git a/app/src/github/res/values-sk/strings.xml b/app/src/github/res/values-sk/strings.xml new file mode 100644 index 00000000..078fa6a8 --- /dev/null +++ b/app/src/github/res/values-sk/strings.xml @@ -0,0 +1,6 @@ + + + Enable Sentry + Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io + Sentry will start on next launch + diff --git a/app/src/github/res/values-tr/strings.xml b/app/src/github/res/values-tr/strings.xml new file mode 100644 index 00000000..078fa6a8 --- /dev/null +++ b/app/src/github/res/values-tr/strings.xml @@ -0,0 +1,6 @@ + + + Enable Sentry + Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io + Sentry will start on next launch + diff --git a/app/src/github/res/values-vi/strings.xml b/app/src/github/res/values-vi/strings.xml new file mode 100644 index 00000000..078fa6a8 --- /dev/null +++ b/app/src/github/res/values-vi/strings.xml @@ -0,0 +1,6 @@ + + + Enable Sentry + Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io + Sentry will start on next launch + diff --git a/app/src/github/res/values-zh-rCN/strings.xml b/app/src/github/res/values-zh-rCN/strings.xml new file mode 100644 index 00000000..8c4a510e --- /dev/null +++ b/app/src/github/res/values-zh-rCN/strings.xml @@ -0,0 +1,6 @@ + + + 启用 Sentry + Sentry 会将错误报告发送至 Sentry.io + 启用 Sentry 将在下次启动应用时生效 + diff --git a/app/src/github/res/values-zh-rTW/strings.xml b/app/src/github/res/values-zh-rTW/strings.xml new file mode 100644 index 00000000..078fa6a8 --- /dev/null +++ b/app/src/github/res/values-zh-rTW/strings.xml @@ -0,0 +1,6 @@ + + + Enable Sentry + Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io + Sentry will start on next launch + diff --git a/app/src/main/res/values-ca/arrays.xml b/app/src/main/res/values-ca/arrays.xml index 8a29e7bd..34d4e174 100644 --- a/app/src/main/res/values-ca/arrays.xml +++ b/app/src/main/res/values-ca/arrays.xml @@ -44,6 +44,15 @@ \| - + + + + + + + + + segons minuts diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 02092f94..93a5ef0d 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -21,11 +21,12 @@ Comentaris Activitat Cerca actualitzacions a l\'inici - Block screenshots & app preview + Bloca les captures de pantalla & previsualització de l\'aplicació Descarrega les publicacions a carpetes de nom d\'usuari Prepend Username to Filename Marca les històries com a vistes després de visualitzar-es L\'autor de la història sabrà que l\'has vista + Hide muted stories from feed Marca els missatges com a vists després de visualitzar-los Altres membres sabran que l\'has vist Habilitar les notificacions d\'activitat @@ -180,7 +181,7 @@ Suggerit Captura de pantalla feta No es pot lliurar - Unseen count response is null! + La resposta de recompte no vista és nul·la! Missatge... Premeu i mantingueu premut per enregistrar l\'àudio S\'està actualitzant... @@ -188,9 +189,9 @@ Vols sortir d\'aquest xat? Expulsar Usuaris que han marxat - Invalid user - Instagram does not allow uploading videos longer than 60 secs for DM. - Instagram does not allow uploading audio longer than 60 secs. + Usuari/a invàlid + Instagram no permet pujar vídeos més de 60 segons per DM. + Instagram no permet pujar àudio de més de 60 segons. Baixar directament Descarregar publicacions directament al telèfon! S\'estan recuperant les noves publicacions @@ -450,14 +451,32 @@ segons minuts Cercar GIPHY - Response is null! - Response status is not ok! - Request failed! - Keyword - Enable keyword filter - Edit keyword filters - Added keyword: %s to filter list - Removed keyword: %s from filter list - Marked as seen - Delete unsuccessful + La resposta és nul·la! + L\'estat de la resposta no és correcte! + La petició ha fallat! + Paraula clau + Activar el filtre de paraules clau + Editar els filtres de paraules clau + S\'ha afegit la paraula clau: %s a la llista de filtres + S\'ha eliminat la paraula clau: %s de la llista de filtres + Marcat com a vist + Suprimeix sense èxit + Throttled by Instagram because of too many API requests. Wait for some time before retrying. + Error + This account has been logged out. + Login required! + Sentry block. + User is inactive! + Informe d\'error de Barinsta + Selecciona una aplicació de correu electrònic per enviar registres d\'error + Not found! + Your IP has been rate limited by Instagram. Wait for an hour and try again. + Skip this update + You\'re already on the latest version + Screen order + Other tabs + The tab order will be reflected on next launch + If saved, all DM related features will be disabled on next launch + Copy caption + Copy reply diff --git a/app/src/main/res/values-cs/arrays.xml b/app/src/main/res/values-cs/arrays.xml index 8d3ac1f8..0cd91529 100644 --- a/app/src/main/res/values-cs/arrays.xml +++ b/app/src/main/res/values-cs/arrays.xml @@ -44,6 +44,15 @@ \| - + + + + + + + + + secs mins diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index c5bc0f11..9d444cf3 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -26,6 +26,7 @@ Prepend Username to Filename Označit příběhy po zhlédnutí jako zobrazené Autor příběhu bude vědět, že jsi si ho zobrazili + Hide muted stories from feed Označovat přímou zprávu po zobrazení jako zobrazenou Ostatní členové uvidí, že jste si ji ji zobrazili Povolit oznámení o aktivitě @@ -476,4 +477,22 @@ Removed keyword: %s from filter list Marked as seen Delete unsuccessful + Throttled by Instagram because of too many API requests. Wait for some time before retrying. + Error + This account has been logged out. + Login required! + Sentry block. + User is inactive! + Barinsta Crash Report + Select an email app to send crash logs + Not found! + Your IP has been rate limited by Instagram. Wait for an hour and try again. + Skip this update + You\'re already on the latest version + Screen order + Other tabs + The tab order will be reflected on next launch + If saved, all DM related features will be disabled on next launch + Copy caption + Copy reply diff --git a/app/src/main/res/values-de/arrays.xml b/app/src/main/res/values-de/arrays.xml index c36bdbcb..11014161 100644 --- a/app/src/main/res/values-de/arrays.xml +++ b/app/src/main/res/values-de/arrays.xml @@ -44,6 +44,15 @@ \| - + + + + + + + + + secs mins diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index e023c6b9..50539525 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -26,6 +26,7 @@ Prepend Username to Filename Stories nach dem Ansehen als gesehen markieren Die Person wird wissen, dass du dir die Story angesehen hast + Hide muted stories from feed Direktnachrichten nach dem Ansehen als gesehen markieren Andere Mitglieder werden wissen, dass du sie gesehen hast Aktivitätsbenachrichtigungen aktivieren @@ -460,4 +461,22 @@ Removed keyword: %s from filter list Marked as seen Delete unsuccessful + Throttled by Instagram because of too many API requests. Wait for some time before retrying. + Error + This account has been logged out. + Login required! + Sentry block. + User is inactive! + Barinsta Crash Report + Select an email app to send crash logs + Not found! + Your IP has been rate limited by Instagram. Wait for an hour and try again. + Skip this update + You\'re already on the latest version + Screen order + Other tabs + The tab order will be reflected on next launch + If saved, all DM related features will be disabled on next launch + Copy caption + Copy reply diff --git a/app/src/main/res/values-el/arrays.xml b/app/src/main/res/values-el/arrays.xml index 0dd3a31e..77f71e18 100644 --- a/app/src/main/res/values-el/arrays.xml +++ b/app/src/main/res/values-el/arrays.xml @@ -32,7 +32,7 @@ Φωτεινό - Προεπιλεγμένο του Instagram(Αδιάβαστο και μετά διαβασμένο) + Προεπιλογή του Instagram (Μη αναγνωσμένα και μετά αναγνωσμένα) Από το νεότερο στο παλαιότερο Από το παλαιότερο στο νεότερο @@ -44,6 +44,15 @@ \| - + + + + + + + + + δεύτερα λεπτά diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index b8c7df56..3cc70379 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -1,13 +1,13 @@ Σχετικά με - Απευθείας Μηνύματα + Μηνύματα Ρυθμίσεις Λήψη Αναζήτηση ονόματος χρήστη... Σύγκριση Σφάλμα κατά την αντιγραφή κειμένου - Αντιγράφηκε στο clipboard! + Αντιγράφηκε στο πρόχειρο! Αναφορά Προστασία αρχείου με κωδικό πρόσβασης Κωδικός Πρόσβασης @@ -20,26 +20,27 @@ Ανακαλύψτε Σχόλια Δραστηριότητα - Έλεγχος για ενημερώσεις στο ξεκίνημα - Block screenshots & app preview - Λήψη δημοσίευσης στους φακέλους με ονόματα χρηστών + Έλεγχος για ενημερώσεις κατά την εκκίνηση + Παρεμπόδιση στιγμιοτύπων οθόνης & προεπισκόπησης εφαρμογής + Λήψη δημοσιεύσεων σε φακέλους ονομάτων χρηστών Prepend Username to Filename Επισήμανση ιστοριών ως προβληθέντων μετά την προβολή - Ο συντάκτης της ιστορίας θα ξέρει ότι την προβάλατε - Σήμανση ΠΜ ως αναγνωσμένου μετά την προβολή - Άλλα μέλη θα γνωρίζουν πως το προβάλατε + Ο δημιουργός της ιστορίας θα γνωρίζει ότι προβλήθηκε + Hide muted stories from feed + Επισήμανση μηνυμάτων ως αναγνωσμένων μετά την προβολή + Τα υπόλοιπα μέλη θα γνωρίζουν ότι προβλήθηκε Ενεργοποίηση ειδοποιήσεων δραστηριότητας Ταξινόμηση ροής ιστορίων - Σφάλμα κατά τη φόρτωση προφίλ! Είναι έγκυρο το όνομα χρήστη; Αν ναι, μπορεί να είστε περιορισμένοι. - Σφάλμα κατά την φόρτωση προφίλ! Είναι έγκυρο το όνομα χρήστη; Ή σας μπλοκάρισε; + Σφάλμα κατά τη φόρτωση προφίλ! Είναι το όνομα χρήστη έγκυρο; Αν ναι, μπορεί να είστε περιορισμένος. + Σφάλμα κατά τη φόρτωση λογαριασμού! Είναι το όνομα χρήστη έγκυρο; Μήπως σας έχει μπλοκάρει; Σφάλμα κατά τη φόρτωση hashtag! Είναι το όνομα έγκυρο; - Σφάλμα κατά την φόρτωση τοποθεσίας! Είναι το URL έγκυρο; - Σφάλμα κατά τη δημιουργία φακέλου/ων λήψης. + Σφάλμα κατά την φόρτωση τοποθεσίας! Είναι η διεύθυνση έγκυρη; + Σφάλμα κατά τη δημιουργία φακέλου/-ων λήψης. Αποθήκευση σε προσαρμοσμένο φάκελο - Επιλογή Φακέλου + Επιλογή φακέλου Θέμα Επηρεάζει μόνο τους συνδεδεμένους χρήστες: - Επηρεάζει μόνο ανώνυμους χρήστες: + Επηρεάζει μόνο τους ανώνυμους χρήστες: Γλώσσα %s\nΔημοσίευση @@ -53,23 +54,23 @@ %s\nΑκόλουθος %s\nΑκόλουθοι - %s\nΑκολουθείται - Παίζε αυτόματα τα βίντεο - Πάντα βάζε τα βίντεο σε σιγή - Να εμφανίζονται πάντα οι λεζάντες δημοσιέυσεων - Επιλογή τι να κατεβεί - Συγκεκριμένο + %s\nΑκολουθείτε + Αυτόματη αναπαραγωγή των βίντεο + Μόνιμη σίγαση των βίντεο + Μόνιμη εμφάνιση των λεζαντών των δημοσιεύσεων + Επιλογή δημοσιεύσεων για λήψη + Τρέχων Ολόκληρο Άλμπουμ - Εμφάνιση Ιστοριών + Εμφάνιση ιστοριών Δεν υπάρχουν άλλες ιστορίες! Κάντε υπομονή! - Προβολη Δημοσίευσης + Προβολή Δημοσίευσης Προβολή Δημοσίευσης Spotify Ψηφίστε Η ψήφος ήταν επιτυχής! Έχετε ήδη ψηφίσει! - Ανταπόκριση + Απάντηση Απάντηση… Η απάντηση ήταν επιτυχής! @@ -90,9 +91,9 @@ Δεν Υπάρχουν Τέτοιες Δημοσιεύσεις! Σύνδεση Αποσύνδεση - Περιήγηση στο Instagram ανώνυμα + Ανώνυμη περιήγηση στο Instagram Αφαίρεση όλων των λογαριασμών - Αυτό θα καταργήσει όλους τους λογαριασμούς που έχουν προστεθέι στην εφαρμογή!\nΓια να καταργήσετε μόνο έναν λογαριασμό, πατήστε παρατεταμένα το λογαριασμό από το διάλογο εναλλαγής λογαριασμού.\nΘέλετε να συνεχίσετε; + Αυτό θα αφαιρέσει όλους τους λογαριασμούς που έχουν προστεθεί στην εφαρμογή!\nΓια να αφαιρέσετε μόνο έναν λογαριασμό, πατήστε παρατεταμένα τον λογαριασμό από τον διάλογο εναλλαγής λογαριασμών.\nΘέλετε να συνεχίσετε; Μορφή ημερομηνίας Δημιουργία νέας συλλογής Επεξεργασία ονόματος συλλογής @@ -100,26 +101,26 @@ Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτή τη συλλογή; Όλα τα πολυμέσα που περιέχονται θα παραμείνουν σε άλλες συλλογές. Προσθήκη στη συλλογή... - Κατάργηση από τη συλλογή + Αφαίρεση από τη συλλογή Επισημασμένο ως \"Μου αρέσει\" - Αποθηκεύτηκε - Επισημάνθηκε η ετικέτα + Αποθηκευμένα + Σε αυτήν τη φωτογραφία Μήνυμα Μου αρέσει - Δεν μου αρέσει + Δε μου αρέσει Αποθήκευση σαν σελιδοδείκτης Διαγραφή σελιδοδείκτη Ακολουθήστε - Κατάργηση ακολούθησης - Προσθήκη στα αγαπημένα + Να μην ακολουθώ + Αγαπημένα Αποκλεισμός Άρση αποκλεισμού Περιορισμός Άρση Περιορισμού Σίγαση ιστοριών Σίγαση δημοσιεύσεων - Κατάργηση σίγασης ιστοριών - Κατάργηση σίγασης δημοσιεύσεων + Άρση σίγασης ιστοριών + Άρση σίγασης δημοσιεύσεων Αντιγραφή βιογραφικού Μετάφραση βιογραφικού Κοινά @@ -130,10 +131,10 @@ Ρυθμίσεις Αγαπημένα Η εισαγωγή ήταν επιτυχής! - Η εισαγωγή ήταν ανεπιτυχής! + Η εισαγωγή απέτυχε! Η εξαγωγή ήταν επιτυχής! Η εξαγωγή απέτυχε! - Επαναφόρτωση + Ανανέωση Λήψη cookies Λειτουργία Επιφάνειας Εργασίας Χρήση προσαρμοσμένης μορφής @@ -143,7 +144,7 @@ Προεπισκόπηση Εναλλαγή θέσεων ώρας και ημερομηνίας Αδυναμία διαγραφής λογαριασμού που χρησιμοποιείται αυτή την στιγμή - Είστε βέβαιοι ότι θέλετε να διαγράψετε το \'%s\'; + Είστε βέβαιος ότι θέλετε να διαγράψετε το «%s»; Άνοιγμα προφίλ Προβολή ιστορίας Προβολή εικόνας προφίλ @@ -155,87 +156,87 @@ %s κοινοποίησε ένα βίντεο %s έστειλε ένα μήνυμα %s κοινοποίησε ένα gif - %s shared a sticker + %s κοινοποίησε ένα αυτοκόλλητο %s κοινοποίησε ένα προφίλ: @%s - %s μοιράστηκε μια τοποθεσία: %s - %s κοινοποίησε μια ιστορία highlight από @%s - %s κοινοποίησε μια δημοσίευση από @%s + %s κοινοποίησε μία τοποθεσία: %s + %s κοινοποίησε ένα highlight ιστορίας από @%s + %s κοινοποίησε μία ιστορία από @%s %s έστειλε ένα ηχητικό μήνυμα %s κοινοποίησε ένα κλιπ από @%s %s κοινοποίησε ένα IGTV βίντεο από @%s - Απαντήσατε στην ιστορία τους: %s + Απαντήσατε στην ιστορία του: %s %s απάντησε στην ιστορία σας: %s - Αντιδράσατε στην ιστορία τους: %s + Αντιδράσατε στην ιστορία του: %s %s αντέδρασε στην ιστορία σας: %s Αναφέρατε τον χρήστη @%s στην ιστορία σας %s σας ανέφερε στην ιστορία τους Άγνωστος τύπος πολυμέσου Το πολυμέσο έχει λήξει! Παραδόθηκε - Έχει σταλεί + Απεστάλη Ανοίχτηκε - Ξαναπαίχτηκε + Αναπαράχθηκε ξανά Γίνεται Αποστολή… Αποκλείστηκε Προτεινόμενη - Τραβήχθηκε στιγμιότυπο οθόνης + Λήφθηκε στιγμιότυπο οθόνης Αδυναμία παράδοσης - Unseen count response is null! + Η απόκριση καταμέτρησης μη προβληθέντων είναι άκυρη! Μήνυμα... Πατήστε παρατεταμένα για εγγραφή ήχου Ενημέρωση... - Αποχώρηση από την συνομιλία + Αποχώρηση από τη συνομιλία Αποχώρηση από αυτήν τη συνομιλία; Διώξιμο Χρήστες που αποχώρησαν Μη έγκυρος χρήστης - Instagram does not allow uploading videos longer than 60 secs for DM. - Instagram does not allow uploading audio longer than 60 secs. + Το Instagram δεν επιτρέπει τη αναφόρτωση βίντεο άνω των 60 δευτ. ως μηνύματα. + Το Instagram δεν επιτρέπει τη αναφόρτωση αρχείων ήχου άνω των 60 δευτ. Απευθείας λήψη - Λήψεις δημοσιεύσεων απευθείας στο τηλέφωνο! - Ανάκτηση δημοσιεύσης/εων + Γίνεται λήψη των δημοσιεύσεων απευθείας στο τηλέφωνο! + Ανάκτηση δημοσιεύσης/-εων Η λήψη ολοκληρώθηκε Γίνεται λήψη της δημοσίευσης… Γίνεται λήψη πολυμέσων Γίνεται λήψη εικόνας προφίλ Προέκυψε ένα άγνωστο σφάλμα!!! - Σφάλμα κατά την δημιουργία φακέλου! + Σφάλμα κατά τη δημιουργία φακέλου! Σφάλμα λήψης αρχείου - Μπορείτε να κατεβάσετε μόνο 100 δημοσιεύσεις κάθε φορά. Μην είστε τόσο άπληστοι! + Μπορείτε να καταφορτώσετε μόνο 100 δημοσιεύσεις τη φορά. Μην είστε τόσο άπληστος! Αντιγραφή σχολίου - Προβολή αυτών που μάρκαραν το σχόλιο ως \'Μου Αρέσει\' + Προβολή όσων δήλωσαν ότι τους αρέσει το σχόλιο Απάντηση στο σχόλιο - Επισήμανση σχολίου ως \'Μου Αρέσει\' - Αφαίρεση \'Μου Αρέσει\' από το σχόλιο + Επισήμανση ως «Μου Αρέσει» + Κατάργηση του «Μου Αρέσει» Μετάφραση σχολίου Διαγραφή σχολίου - Δεν υπάρχουν κενά σχόλια! + Όχι κενά σχόλια! Θέλετε να αναζητήσετε το όνομα χρήστη; Θέλετε να αναζητήσετε το hashtag; Ακόλουθοι Ακολουθείτε - Σύγκριση ακολούθων & ακολούθείται - Και οι δύο ακολουθιέστε μεταξύ σας + Σύγκριση ακολούθων + Ακολουθείτε ο ένας τον άλλον δεν ακολουθεί %s %s δεν ακολουθεί Σφάλμα κατά την φόρτωση των cookies - Γράψτε νεο σχόλιο… + Σύνταξη νέου σχολίου… Σύνταξη νέου μηνύματος… - Του άρεσε η δημοσίευση σας + Δήλωσε ότι του αρέσει η δημοσίευσή σας Σχολιάστηκε στην δημοσίευση σας: Ξεκίνησε να σας ακολουθεί - Πρόσθεσε την ετικέτα σας σε μία δημοσίευση - Έκανε αίτημα να σας ακολουθήσει + Σας πρόσθεσε με ετικέτα σε μια δημοσίευση + Ζήτησε να σας ακολουθήσει Έγκριση αιτήματος Απόρριψη αιτήματος Μοιραστείτε αυτή τη δημόσια δημοσίευση στο… - Αυτή είναι μια ιδιωτική ανάρτηση! Μοιραστείτε την με αυτούς που μπορούν να την δουν! - Αυτή η κατηγορία είναι κάπως κενή… + Αυτή είναι ιδιωτική ανάρτηση! Μοιραστείτε τη με εκείνους που μπορούν να τη δουν! + Αυτή η κατηγορία είναι κενή, με κάποιον τρόπο… Υπάρχει διαθέσιμη ενημέρωση! (%s) - Υπενθύμιση: Εάν το έχετε κατεβάσει από το F-Droid, πρέπει να το ενημερώσετε από αυτό! Το ίδιο ισχύει και για το GitHub. + Υπενθύμιση: Εάν η εγκατάσταση έγινε από το F-Droid, από εκεί θα χρειαστεί να γίνουν κι οι ενημερώσεις! Το ίδιο ισχύει και για το GitHub. Ευχαριστούμε που ενημερώσατε το Barinsta! - Η εφαρμογή συνετρίβη - Ουπς.. Η εφαρμογή συνετρίβη, αλλά μην ανησυχείτε, μπορείτε να αναφέρετε το σφάλμα στον προγραμματιστή για να τον βοηθήσετε να διορθώσει το πρόβλημα. (: + Η εφαρμογή κατέρρευσε + Ώπα.. Η εφαρμογή κατέρρευσε, αλλά μην ανησυχείτε. Μπορείτε να αναφέρετε το σφάλμα στον προγραμματιστή, για να τον βοηθήσετε να διορθώσει το πρόβλημα. (: Δραστηριότητα Αρχειοθήκη ιστοριών Προτεινόμενοι χρήστες @@ -244,16 +245,16 @@ Έχετε: %d ακόλουθοι %d σχόλια - %d επισημάνσεις \'Μου Αρέσει\' σε σχόλια + Το σχόλιο αρέσει σε %d %d ετικέτες χρήστη - %d επισημάνσεις \"Μου αρέσει\" + Αρέσει σε %d %d φωτογραφίες σας %d αιτήματα ακολούθησης - Αποσυνδεθήκατε πριν κάνετε κλικ σε αυτή την ειδοποίηση;! + Αποσυνδεθήκατε προτού πατήσετε αυτήν την ειδοποίηση;! Ροή Προφίλ - Περισσότερα - ΠΜ + Λοιπά + Μηνύματα %d επιλέχθηκε Η αποσύνδεση ήταν επιτυχής! Πληροφορίες @@ -266,37 +267,37 @@ Λήψεις Τοποθεσία Λογαριασμός - Η συγκεκριμένη σύνδεση δεν λειτουργεί; Απλά ξαναπροσθέστε τον λογαριασμό. + Δε λειτουργεί η τρέχουσα σύνδεση; Απλά προσθέστε ξανά τον λογαριασμό. Προσθήκη λογαριασμού Άδεια (μόνο στα Αγγλικά) - Επισκεφθείτε την ιστοσελίδα μας - Λάβετε υποστήριξη, συζητήστε, συναντήστε άλλους, και διασκεδάστε! - Δείτε τον πηγαίο κώδικα μας στο GitHub - Ελέγχου, μαρκάρισμα με αστέρια, αναφορά σφαλμάτων, συνεισφορές, και διασκέδαση (ξανά)! + Επισκεφθείτε τον ιστότοπό μας + Λάβετε υποστήριξη, συζητήστε, συναντήστε άλλους και διασκεδάστε! + Δείτε τον πηγαίο κώδικά μας στο GitHub + Έλεγχος, αναφορά σφαλμάτων, συνεισφορά και διασκέδαση (ξανά)! Αποστολή σχολίων μέσω ηλ. ταχυδρομείου Αποδόσεις Τρίτων Υπενθύμιση Παρακαλούμε χρησιμοποιήστε αυτήν την εφαρμογή υπεύθυνα. Οι ληφθείσες εικόνες πρέπει να χρησιμοποιούνται μόνο για σκοπούς που επιτρέπονται από τους ισχύοντες νόμους. - Άσπρο + Λευκό Μαύρο - Φωτείνο θέμα - Σκοτεινό θέμα - Barista - Σκοτεινό Material + Φωτεινό θέμα + Σκούρο θέμα + Κόκκοι καφέ + Ουσιώδες Σκούρο Προστέθηκε στα Αγαπημένα! - Προσθήκη στα αγαπημένα + Στα αγαπημένα Λογαριασμοί - Hashtags + Hashtag Τοποθεσίες Άγνωστο Αφαίρεση από τα αγαπημένα! Αντίγραφα ασφαλείας & Επαναφορά - Αντίγραφο ασφαλείας ρυθμίσεων εφαρμογής Barinsta, δεδομένα σύνδεσης λογαριασμού και/ή αγαπημένα σε ένα απλό κείμενο ή κρυπτογραφημένο αρχείο αντιγράφου ασφαλείας για μεταγενέστερη επαναφορά. - Αν δημιουργείτε αντίγραφα ασφαλείας των δεδομένων σύνδεσης λογαριασμού, αντιμετωπίζετε το αρχείο ως απόρρητο και κρατήστε το κάπου που είναι ασφαλές! + Δημιουργία αντιγράφου ασφαλείας των ρυθμίσεων της εφαρμογής, των δεδομένων σύνδεσης λογαριασμού και/ή αγαπημένα σε απλό κείμενο ή κρυπτογραφημένο αρχείο για μεταγενέστερη επαναφορά. + Αν δημιουργείτε αντίγραφα ασφαλείας των δεδομένων σύνδεσης λογαριασμού, αντιμετωπίστε το αρχείο ως απόρρητο και κρατήστε το σε ασφαλές μέρος! Δημιουργία νέου αρχείου αντιγράφου ασφαλείας Επαναφορά από υπάρχον αρχείο αντιγράφου ασφαλείας Αρχείο: - Εισαγωγή κωδικού πρόσβασης + Καταχώριση κωδικού πρόσβασης Επιλέξτε ένα αρχείο αντιγράφου ασφαλείας (.zaai/.backup) Εφαρμογή Αποθήκευση @@ -304,16 +305,16 @@ Επεξεργασία λεζάντας Μετάφραση λεζάντας Χρονοδιάγραμμα αναπαραγωγού βίντεο - Μαρκάρισμα ως \'Μου Αρέσει\'… - Το μαρκάρισμα ως \'Μου Αρέσει\' ήταν ανεπιτυχές - Η κατάργηση \'Μου Αρέσει\' ήταν ανεπιτυχής - Κατάργηση \'Μου Αρέσει\'… + «Μου αρέσει»… + Ανεπιτυχής επισήμανση ως «Μου Αρέσει» + Ανεπιτυχής αναίρεση του «Μου Αρέσει» + Αναίρεση «Μου Αρέσει»… Στοιχεία ελέγχου Αποθήκευση… Αφαίρεση… - Η αποθήκευση ΔΕΝ ήταν επιτυχής - Η αφαίρεση ΔΕΝ ήταν επιτυχής - Γίνεται λήψη… + Η αποθήκευση ήταν ανεπιτυχής + Η αφαίρεση ήταν ανεπιτυχής + Λήψη… Λήψη στοιχείου %1$d από %2$d Διαγραφή Σχόλιο @@ -321,20 +322,20 @@ Ροή ιστοριών Άνοιγμα δημοσίευσης... Κοινοποίηση - Στυλ διάταξης + Τρόπος διάταξης Πλήθος στηλών 2 3 Εμφάνιση ονομάτων - Προβολή άβαταρ + Εμφάνιση άβαταρ Μέγεθος άβαταρ Γωνίες - Εμφάνιση διάταξης πλέγματος - Απενεργοποίηση των εφέ + Εμφάνιση κενού πλέγματος + Απενεργοποίηση κινουμένων σχεδίων Παρακαλούμε περιμένετε να ολοκληρωθεί πρώτα η τρέχουσα εργασία! - Ανάλογα με τις μετρήσεις του χρήστη, αυτό μπορεί να πάρει λίγο χρόνο για να φορτωθεί. Μείνετε υπομονετικοί. - Η δημοσίευση δεν βρέθηκε! - Δεν βρέθηκε εφαρμογή που να ανοίγει urls + Αναλόγως τις μετρήσεις χρηστών, ενδέχεται να καθυστερήσει η φόρτωση. Κάντε υπομονή. + Η δημοσίευση δε βρέθηκε! + Δε βρέθηκε εφαρμογή ανοίγματος url Συλλογή Κάμερα Όλες οι φωτογραφίες @@ -342,7 +343,7 @@ Όλα τα βίντεο Φωτεινότητα Αντίθεση - Δόνηση + Ζωντάνια Κορεσμός Όξυνση Έκθεση @@ -361,8 +362,8 @@ Περικοπή Κανονικό - %d επισήμανση \"Μου αρέσει\" - %d επισημάνσεις \"Μου αρέσει\" + Αρέσει σε %d + Αρέσει σε %d %d σχόλιο @@ -381,20 +382,20 @@ Τίτλος Μέλη Διαχειριστής - Προσκεκλημμένος + Προσκαλών Σίγαση μηνυμάτων Σίγαση αναφορών Προσθήκη μελών Αναζήτηση Έγινε - Ορισμός ως Διαχειριστής - Κατάργηση σαν Διαχειριστής + Θέστε ως διαχειριστή + Κατάργηση ως Διαχειριστής Η επεξεργασία απέτυχε Μήνυμα Απάντηση Πατήστε για αφαίρεση Προώθηση - Έχετε προωθήσει ένα μήνυμα + Προωθήσατε ένα μήνυμα Προωθήθηκε ένα μήνυμα Προσθήκη Αποστολή @@ -406,20 +407,20 @@ Απαντήθηκε στον %s Σας απάντησε Απάντησε στον εαυτό του - Αντιδράσατε στην ιστορία τους + Αντιδράσατε στην ιστορία του Αντέδρασε στην ιστορία σας - Τους αναφέρατε στην ιστορία σας - Σας ανέφερε στην ιστορία τους - Απαντήσατε στην ιστορία τους + Τον αναφέρατε στην ιστορία σας + Σας ανέφερε στην ιστορία του + Απαντήσατε στην ιστορία του Απάντησε στην ιστορία σας - Η εικόνα έχει λήξει + Η εικόνα έληξε Η εικόνα θα λήξει όταν προβληθεί Το βίντεο έληξε Το βίντεο θα λήξει όταν προβληθεί - Το μήνυμα έχει λήξει + Το μήνυμα έληξε Το μήνυμα θα λήξει όταν προβληθεί Ιστορία του @%s - Ιστορία highlight του @%s + Highlight ιστορίας του @%s Φωτογραφία Βίντεο Ηχητικό μήνυμα @@ -438,26 +439,44 @@ Απόρριψη Αποδοχή Εσείς - No pending requests - Γίνεται έλεγχος για νέες μηνύματα + Δεν υπάρχουν εκκρεμή αιτήματα + Γίνεται έλεγχος για νέα μηνύματα Ιστορίες - DM + Μηνύματα Ειδοποιήσεις Δημοσίευση - Enable DM notifications - Auto refresh messages + Ενεργοποίηση ειδοποιήσεων μηνυμάτων + Αυτόματη ανανέωση μηνυμάτων Αυτόματη ανανέωση κάθε δεύτερα λεπτά - Αναζήτηση Στο GIPHY - Response is null! - Response status is not ok! - Request failed! + Αναζήτηση στο GIPHY + Άκυρη απόκριση! + Η κατάσταση απόκρισης δεν είναι εντάξει! + Αποτυχία αιτήματος! Λέξη-κλειδί - Enable keyword filter - Edit keyword filters - Added keyword: %s to filter list - Removed keyword: %s from filter list - Σήμανθηκε ως αναγνωσμένο + Ενεργοποίηση φίλτρου λέξεων-κλειδιών + Επεξεργασία φίλτρων λέξεων-κλειδιών + Προστέθηκε λέξη-κλειδί: %s στον κατάλογο φιλτραρίσματος + Αφαιρέθηκε λέξη-κλειδί: %s στον κατάλογο φιλτραρίσματος + Επισήμανθηκε ως αναγνωσμένο Η διαγραφή απέτυχε + Throttled by Instagram because of too many API requests. Wait for some time before retrying. + Error + This account has been logged out. + Login required! + Sentry block. + User is inactive! + Αναφορά Κατάρρευσης Barinsta + Επιλέξτε μια εφαρμογή ηλ. ταχυδρομείου για αποστολή αρχείων καταγραφής κατάρρευσης + Not found! + Your IP has been rate limited by Instagram. Wait for an hour and try again. + Skip this update + You\'re already on the latest version + Screen order + Other tabs + The tab order will be reflected on next launch + If saved, all DM related features will be disabled on next launch + Αντιγραφή λεζάντας + Αντιγραφή απάντησης diff --git a/app/src/main/res/values-es/arrays.xml b/app/src/main/res/values-es/arrays.xml index 3c89597f..4d5bed14 100755 --- a/app/src/main/res/values-es/arrays.xml +++ b/app/src/main/res/values-es/arrays.xml @@ -44,6 +44,15 @@ \| - + + + + + + + + + segs mins diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 9ee7d1d0..188dff0c 100755 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -23,9 +23,10 @@ Buscar actualizaciones al inicio Bloquea capturas de pantalla & vista previa de aplicaciones Usar subcarpetas con el nombre de usuario - Prepend Username to Filename + Añadir nombre de usuario al inicio del nombre del archivo Marcar historias como vistas después de verlas El autor de la historia sabrá que lo has visto + Ocultar historias silenciadas del feed Marcar Mensaje Directo como visto después de verlo Otros miembros sabrán que lo has visto Activar notificaciones de actividad @@ -460,4 +461,22 @@ Se eliminó la palabra clave: %s de la lista de filtros Marcado como visto Eliminación fallida + Throttled by Instagram because of too many API requests. Wait for some time before retrying. + Error + This account has been logged out. + Login required! + Sentry block. + User is inactive! + Informe de fallos de Barinsta + Seleccione una aplicación de correo electrónico para enviar registros de errores + Not found! + Your IP has been rate limited by Instagram. Wait for an hour and try again. + Omitir esta actualización + Ya tienes la última versión + Orden de pantalla + Otras pestañas + El orden de la pestaña se reflejará en el próximo lanzamiento + Si se guarda, todas las funcionalidades relacionadas con MDs se desactivarán en el próximo lanzamiento + Copiar título + Copiar respuesta diff --git a/app/src/main/res/values-eu/arrays.xml b/app/src/main/res/values-eu/arrays.xml index 70f105f7..9e98429a 100644 --- a/app/src/main/res/values-eu/arrays.xml +++ b/app/src/main/res/values-eu/arrays.xml @@ -44,8 +44,17 @@ \| - + + + + + + + + + - secs - mins + s + m diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 8f217f17..1d26e03d 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -26,6 +26,7 @@ Prepend Username to Filename Markatu istorioak ikusita gisa ikusi ondoren Istorioaren egileak ikusi duzula jakingo du + Hide muted stories from feed Markatu MZ ikusita gisa ikusi ondoren Beste kideek ikusi duzula jakingo dute Gaitu jarduera-jakinarazpenak @@ -149,7 +150,7 @@ Ikusi profil-irudia Unsupported message type Ezabatu mezua - View on GIPHY + Ikusi GIPHYn %s shared a post by @%s %s shared an image %s shared a video @@ -183,7 +184,7 @@ Unseen count response is null! Mezua... Press and hold to record audio - Updating... + Eguneratzen... Utzi txata Utzi txat hau? Bota @@ -384,20 +385,20 @@ Inviter Mute messages Mute mentions - Add members - Search - Done - Make Admin + Gehitu kideak + Bilatu + Eginda + Ezarri Admin Remove as Admin Edit was unsuccessful - Message + Mezua Reply Tap to remove - Forward + Birbidali You forwarded a message Forwarded a message - Add - Send + Gehitu + Bidali Replying to yourself Replying to %s You replied to yourself @@ -460,4 +461,22 @@ Removed keyword: %s from filter list Marked as seen Delete unsuccessful + Throttled by Instagram because of too many API requests. Wait for some time before retrying. + Error + This account has been logged out. + Login required! + Sentry block. + User is inactive! + Barinsta Crash Report + Select an email app to send crash logs + Not found! + Your IP has been rate limited by Instagram. Wait for an hour and try again. + Skip this update + You\'re already on the latest version + Screen order + Other tabs + The tab order will be reflected on next launch + If saved, all DM related features will be disabled on next launch + Copy caption + Copy reply diff --git a/app/src/main/res/values-fa/arrays.xml b/app/src/main/res/values-fa/arrays.xml index 053ad43e..b52e999e 100644 --- a/app/src/main/res/values-fa/arrays.xml +++ b/app/src/main/res/values-fa/arrays.xml @@ -44,6 +44,15 @@ \| - + + + + + + + + + secs mins diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 75371835..38d8f503 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -26,6 +26,7 @@ Prepend Username to Filename نشان کرد استوری ها به عنوان دیده شده بعد از دیدن نویسنده استوری می داند که شما آن را دیده اید + Hide muted stories from feed نشان کردن پیام خصوصی بعنوان دیده شده بعد از دیدن کاربران دیگر خواهند فهمید شما این را دیده اید روشن کردن آگهداد فعالیت ها @@ -46,8 +47,8 @@ %s\nPosts - %s Post - %s Posts + %s فرسته + %s فرسته %s\nFollower @@ -76,7 +77,7 @@ %d response averaging %s %d responses averaging %s - Your answer: %s + پاسخ شما: %s پاسخ به استوری پاسخ… نظرسنجی @@ -182,7 +183,7 @@ اسکرین شات گرفته شده نمیتواند ارسال شود Unseen count response is null! - Message... + پیام... Press and hold to record audio Updating... Leave chat @@ -208,7 +209,7 @@ پاسخ به دیدگاه پسندیدن دیدگاه نپسندیدن دیدگاه - Translate comment + گرداندن دیدگاه هذف دیدگاه بدون دیدگاه تهی! ایا میخواهید نام کاربری جستوجو کنید ؟ @@ -338,8 +339,8 @@ No app found which opens urls Gallery فرتورانداز - All Photos - All Media + همۀ فرتورها + همۀ رسانه‌ها All Videos Brightness Contrast @@ -356,7 +357,7 @@ Box blur Sepia Clarendon - 1977 + ۱۹۹۷ Aden Reset Crop @@ -391,7 +392,7 @@ Make Admin Remove as Admin Edit was unsuccessful - Message + پیام Reply Tap to remove Forward @@ -421,7 +422,7 @@ Message will expire when seen \@%s\'s story \@%s\'s story highlight - Photo + فرتور Video Voice message فرسته @@ -442,9 +443,9 @@ No pending requests Checking for new messages Stories - DM - Notifications - Post + پیام سرراست + آگهداد + فرسته Enable DM notifications Auto refresh messages Auto refresh every @@ -461,4 +462,22 @@ Removed keyword: %s from filter list Marked as seen Delete unsuccessful + Throttled by Instagram because of too many API requests. Wait for some time before retrying. + Error + This account has been logged out. + Login required! + Sentry block. + User is inactive! + Barinsta Crash Report + Select an email app to send crash logs + Not found! + Your IP has been rate limited by Instagram. Wait for an hour and try again. + Skip this update + You\'re already on the latest version + Screen order + Other tabs + The tab order will be reflected on next launch + If saved, all DM related features will be disabled on next launch + Copy caption + Copy reply diff --git a/app/src/main/res/values-fr/arrays.xml b/app/src/main/res/values-fr/arrays.xml index 30e34e69..b2c5109e 100755 --- a/app/src/main/res/values-fr/arrays.xml +++ b/app/src/main/res/values-fr/arrays.xml @@ -44,6 +44,15 @@ \| - + + + + + + + + + secondes minutes diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index d2fd93dd..0f61d784 100755 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -23,9 +23,10 @@ Rechercher les mises à jours au démarrage Bloquer les captures d\'écran & l\'aperçu de l\'application Télécharger les messages dans les dossiers des noms d\'utilisateurs - Prepend Username to Filename + Préfixer le nom d\'utilisateur au nom de fichier Marquer les stories comme vues après consultation L\'auteur de la story saura que vous l\'avez vue + Masquer les stories en sourdine du flux Marquer les messages privés comme vus après consultation Les autres utilisateurs saurons que vous l\'avez vu Activer les notifications d\'activités @@ -460,4 +461,22 @@ Mot-clé supprimé : %s de la liste de filtres Marqué comme vu Suppression non réussie + Propulsé par Instagram à cause d\'un trop grand nombre de requêtes API. Attendez un certain temps avant de réessayer. + Erreur + Ce compte a été déconnecté. + Connexion requise! + Bloc du Guetteur. + L\'utilisateur est inactif! + Rapport d\'erreur de Barinsta + Sélectionnez une application de messagerie pour envoyer les journaux d\'erreurs + Aucun résultat! + Votre adresse IP a été limitée par Instagram. Attendez une heure et essayez à nouveau. + Ignorer cette mise à jour + Vous avez déjà la dernière version + Ordre d\'écran + Autres onglets + L\'ordre des onglets sera reflété au prochain lancement + Si enregistré, toutes les fonctionnalités liées aux DM seront désactivées au prochain lancement + Copie de la légende + Copie de la réponse diff --git a/app/src/main/res/values-hi/arrays.xml b/app/src/main/res/values-hi/arrays.xml index 9b8f6139..cf9cde1c 100644 --- a/app/src/main/res/values-hi/arrays.xml +++ b/app/src/main/res/values-hi/arrays.xml @@ -44,6 +44,15 @@ \| - + + + + + + + + + secs mins diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 623bc2ce..a7cc2d40 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -26,6 +26,7 @@ Prepend Username to Filename स्टोरि को दिखने के बाद \"दिखा गया\" दिखादें सटोरि के लेखक जानेगा कि तुम देखे हो इसको + Hide muted stories from feed तुम देखने के बाद सीधा संदेश को \"दिखागया\" लिखा जाएगा अलग सदस्य जानेंगे कि तुम देखे हो क्रियाकलाप सूचनाएँ दिखायें @@ -461,4 +462,22 @@ Removed keyword: %s from filter list Marked as seen Delete unsuccessful + Throttled by Instagram because of too many API requests. Wait for some time before retrying. + Error + This account has been logged out. + Login required! + Sentry block. + User is inactive! + Barinsta Crash Report + Select an email app to send crash logs + Not found! + Your IP has been rate limited by Instagram. Wait for an hour and try again. + Skip this update + You\'re already on the latest version + Screen order + Other tabs + The tab order will be reflected on next launch + If saved, all DM related features will be disabled on next launch + Copy caption + Copy reply diff --git a/app/src/main/res/values-in/arrays.xml b/app/src/main/res/values-in/arrays.xml index a691d248..1ce3efc0 100644 --- a/app/src/main/res/values-in/arrays.xml +++ b/app/src/main/res/values-in/arrays.xml @@ -44,6 +44,15 @@ tanggal - + + + + + + + + + secs mins diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 6801759b..3d9aeecb 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -26,6 +26,7 @@ Prepend Username to Filename Tandai cerita dibaca setelah melihat Pembuat cerita akan tahu Anda melihatnya + Hide muted stories from feed Tandai DM dibaca setelah melihat Peserta lain akan tahu Anda melihatnya Nyalakan pemberitahuan aktivitas @@ -452,4 +453,22 @@ Removed keyword: %s from filter list Marked as seen Delete unsuccessful + Throttled by Instagram because of too many API requests. Wait for some time before retrying. + Error + This account has been logged out. + Login required! + Sentry block. + User is inactive! + Barinsta Crash Report + Select an email app to send crash logs + Not found! + Your IP has been rate limited by Instagram. Wait for an hour and try again. + Skip this update + You\'re already on the latest version + Screen order + Other tabs + The tab order will be reflected on next launch + If saved, all DM related features will be disabled on next launch + Copy caption + Copy reply diff --git a/app/src/main/res/values-it/arrays.xml b/app/src/main/res/values-it/arrays.xml index 6bc18600..b95a3a1a 100755 --- a/app/src/main/res/values-it/arrays.xml +++ b/app/src/main/res/values-it/arrays.xml @@ -44,6 +44,15 @@ \| - + + + + + + + + + secondi minuti diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index eefead25..0aeb0239 100755 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -23,9 +23,10 @@ Verifica per aggiornamenti all\'avvio Blocca screenshot & anteprima app Scarica i post nelle cartelle del nome utente - Prepend Username to Filename + Anteponi Nome Utente al Nome del File Segna le storie come viste dopo la visualizzazione L\'autore della storia saprà che l\'hai visualizzata + Nascondi storie silenziate dal feed Segna il DM come visto dopo la visualizzazione Altri membri sapranno che lo hai visualizzato Abilita notifiche attività @@ -460,4 +461,22 @@ Parola chiave rimossa: %s dalla lista filtri Segnato come visto Eliminazione non riuscita + Throttled by Instagram because of too many API requests. Wait for some time before retrying. + Error + This account has been logged out. + Login required! + Sentry block. + User is inactive! + Rapporto sugli errori di Barinsta + Selezionare un\'applicazione di posta elettronica per inviare i registri di errori + Not found! + Your IP has been rate limited by Instagram. Wait for an hour and try again. + Salta questo aggiornamento + Hai già l\'ultima versione + Ordine schermata + Altre schede + L\'ordine delle schede sarà rifletto al prossimo avvio + Se salvate, tutte le funzionalità correlate a DM saranno disabilitate al prossimo avvio + Copia didascalia + Copia risposta diff --git a/app/src/main/res/values-ja/arrays.xml b/app/src/main/res/values-ja/arrays.xml index 468a763a..f47c31b0 100644 --- a/app/src/main/res/values-ja/arrays.xml +++ b/app/src/main/res/values-ja/arrays.xml @@ -44,6 +44,15 @@ \| - + + + + + + + + + diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 178f3726..6818078e 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -26,6 +26,7 @@ Prepend Username to Filename ストーリーズを表示後に既読にする ストーリーの作成者は、あなたが閲覧したことを知ることができます。 + Hide muted stories from feed DMを表示後に既読にする ほかのメンバーは、あなたが閲覧したことを知ることができます。 アクティビティの通知を有効化 @@ -145,7 +146,7 @@ プロフィール画像を表示 Unsupported message type メッセージの送信を取り消す - View on GIPHY + GIPHYで見る %s shared a post by @%s %s shared an image %s shared a video @@ -339,11 +340,11 @@ Brightness Contrast Vibrance - Saturation - Sharpen - Exposure - Center - Color + 彩度 + シャープ + 露出 + 中央 + Start End Bilateral Blur @@ -352,10 +353,10 @@ セピア Clarendon 1977 - Aden + アデン リセット トリミング - Normal + ノーマル %d いいね! @@ -363,7 +364,7 @@ %d コメント - %d views + %d ビュー %s ストーリー @@ -426,7 +427,7 @@ End chat? All members will be removed from the group. They will still be able to view the chat history. Pending Requests - Accept request from %1s (%2s)? + %1s (%2s) からのリクエストを承認しますか? Decline Accept You @@ -452,4 +453,22 @@ Removed keyword: %s from filter list Marked as seen Delete unsuccessful + Throttled by Instagram because of too many API requests. Wait for some time before retrying. + Error + This account has been logged out. + Login required! + Sentry block. + User is inactive! + Barinsta Crash Report + Select an email app to send crash logs + Not found! + Your IP has been rate limited by Instagram. Wait for an hour and try again. + Skip this update + You\'re already on the latest version + Screen order + Other tabs + The tab order will be reflected on next launch + If saved, all DM related features will be disabled on next launch + Copy caption + Copy reply diff --git a/app/src/main/res/values-mk/arrays.xml b/app/src/main/res/values-mk/arrays.xml index 974c0995..98c7373a 100644 --- a/app/src/main/res/values-mk/arrays.xml +++ b/app/src/main/res/values-mk/arrays.xml @@ -44,6 +44,15 @@ \| - + + + + + + + + + секунди минути diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index 118e07f4..c234ef9d 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -26,6 +26,7 @@ Prepend Username to Filename Означи ги приказните како видени Авторот на приказната ќе знае дека сте ја погледнале приказната + Hide muted stories from feed Означи порака како видена Другите членови ќе знаат дека сте ја виделе содржината на пораката Овозможи нотификации @@ -460,4 +461,22 @@ Removed keyword: %s from filter list Marked as seen Delete unsuccessful + Throttled by Instagram because of too many API requests. Wait for some time before retrying. + Error + This account has been logged out. + Login required! + Sentry block. + User is inactive! + Barinsta Crash Report + Select an email app to send crash logs + Not found! + Your IP has been rate limited by Instagram. Wait for an hour and try again. + Skip this update + You\'re already on the latest version + Screen order + Other tabs + The tab order will be reflected on next launch + If saved, all DM related features will be disabled on next launch + Copy caption + Copy reply diff --git a/app/src/main/res/values-nl/arrays.xml b/app/src/main/res/values-nl/arrays.xml index a851e9bb..4fa7ccb6 100644 --- a/app/src/main/res/values-nl/arrays.xml +++ b/app/src/main/res/values-nl/arrays.xml @@ -44,6 +44,15 @@ \| - + + + + + + + + + secs mins diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index b904bed6..1a5b56bb 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -26,6 +26,7 @@ Prepend Username to Filename Markeer verhalen als gelezen na bekijken Verhaalmaker zal het weten als je het bekeken hebt + Hide muted stories from feed Markeer privéberichten als gelezen na bekijken Andere gebruikers zullen het weten als je het hebt bekeken Activiteitmeldingen inschakelen @@ -460,4 +461,22 @@ Removed keyword: %s from filter list Marked as seen Delete unsuccessful + Throttled by Instagram because of too many API requests. Wait for some time before retrying. + Error + This account has been logged out. + Login required! + Sentry block. + User is inactive! + Barinsta Crash Report + Select an email app to send crash logs + Not found! + Your IP has been rate limited by Instagram. Wait for an hour and try again. + Skip this update + You\'re already on the latest version + Screen order + Other tabs + The tab order will be reflected on next launch + If saved, all DM related features will be disabled on next launch + Copy caption + Copy reply diff --git a/app/src/main/res/values-or/arrays.xml b/app/src/main/res/values-or/arrays.xml index 26010439..05187416 100644 --- a/app/src/main/res/values-or/arrays.xml +++ b/app/src/main/res/values-or/arrays.xml @@ -44,6 +44,15 @@ \| - + + + + + + + + + secs mins diff --git a/app/src/main/res/values-or/strings.xml b/app/src/main/res/values-or/strings.xml index 12b95d7b..2ad01552 100644 --- a/app/src/main/res/values-or/strings.xml +++ b/app/src/main/res/values-or/strings.xml @@ -26,6 +26,7 @@ Prepend Username to Filename କାହାଣୀଗୁଡିକ ଦେଖିବା ପରେ \'ଦେଖାଗଲା\' ଚିହ୍ନିତ କରନ୍ତୁ | କାହାଣୀ ପ୍ରେରକ ଜାଣିବେ ତୁମେ ଏହାକୁ ଦେଖିଛ + Hide muted stories from feed ବାର୍ତା ଦେଖିବା ପରେ \'ଦେଖାଗଲା\' ଚିହ୍ନିତ କରନ୍ତୁ | ଅନ୍ୟ ସଦସ୍ୟମାନେ ଜାଣିବେ ତୁମେ ଏହାକୁ ଦେଖିଛ। କାର୍ଯ୍ୟକଳାପ ସୂଚନା ଦେଖାନ୍ତୁ @@ -460,4 +461,22 @@ Removed keyword: %s from filter list Marked as seen Delete unsuccessful + Throttled by Instagram because of too many API requests. Wait for some time before retrying. + Error + This account has been logged out. + Login required! + Sentry block. + User is inactive! + Barinsta Crash Report + Select an email app to send crash logs + Not found! + Your IP has been rate limited by Instagram. Wait for an hour and try again. + Skip this update + You\'re already on the latest version + Screen order + Other tabs + The tab order will be reflected on next launch + If saved, all DM related features will be disabled on next launch + Copy caption + Copy reply diff --git a/app/src/main/res/values-pl/arrays.xml b/app/src/main/res/values-pl/arrays.xml index 1147bc83..a7e40411 100644 --- a/app/src/main/res/values-pl/arrays.xml +++ b/app/src/main/res/values-pl/arrays.xml @@ -44,6 +44,15 @@ \| - + + + + + + + + + s. min. diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 2c449634..b3962453 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -26,6 +26,7 @@ Prepend Username to Filename Oznacz relacje jako widoczne po wyświetleniu Autor relacji będzie widział, że to wyświetliłeś + Ukryj wyciszone relacje z kanału Oznacz wiadomość jako przeczytaną Inni użytkownicy będą wiedzieli, że to wyświetliłeś Włącz powiadomienia o aktywności @@ -469,11 +470,29 @@ Odpowiedź jest pusta! Status odpowiedzi nie jest OK! Żądanie nie powiodło się! - Keyword - Enable keyword filter - Edit keyword filters - Added keyword: %s to filter list - Removed keyword: %s from filter list + Słowo kluczowe + Włącz filtr słów kluczowych + Edytuj filtry słów kluczowych + Dodano słowo kluczowe: %s do listy filtrów + Usunięto słowo kluczowe: %s z listy filtrów Oznacz jako przeczytane Usuwanie nie powiodło się + Throttled by Instagram because of too many API requests. Wait for some time before retrying. + Error + This account has been logged out. + Login required! + Sentry block. + User is inactive! + Raport awarii Barinsta + Wybierz aplikację e-mail do wysyłania dzienników awarii + Not found! + Your IP has been rate limited by Instagram. Wait for an hour and try again. + Pomiń tę aktualizację + Posiadasz aktualną wersję + Kolejność na ekranie + Inne karty + Kolejność zakładek zostanie odzwierciedlona przy następnym uruchomieniu + Po zapisaniu wszystkie funkcje związane z PW zostaną wyłączone przy następnym uruchomieniu + Kopiuj podpis + Kopiuj odpowiedź diff --git a/app/src/main/res/values-pt/arrays.xml b/app/src/main/res/values-pt/arrays.xml index f7104787..fb832f13 100644 --- a/app/src/main/res/values-pt/arrays.xml +++ b/app/src/main/res/values-pt/arrays.xml @@ -44,6 +44,15 @@ \| - + + + + + + + + + segs mins diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index f8c3f401..57164840 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -21,11 +21,12 @@ Comentários Atividade Verificar se há atualizações ao iniciar - Block screenshots & app preview + Bloquear capturas de tela e a pré-visualização do aplicativo Baixar publicações para pastas com o nome de usuário - Prepend Username to Filename + Adicionar Nome de usuário ao Nome do arquivo Marcar stories como vistos após a visualização O autor do story saberá que você viu + Esconder stories silenciados do feed Marcar DM como vista após a visualização Outros membros saberão que você viu Ativar notificações de atividade @@ -214,7 +215,7 @@ Você quer buscar a hashtag? Seguidores Seguindo - Comparar seguidores + Comparar seguidores e seguindo Ambos se seguem não segue %s %s não segue @@ -453,11 +454,29 @@ A resposta é nula! O estado da resposta não está correto! A solicitação falhou! - Keyword - Enable keyword filter - Edit keyword filters - Added keyword: %s to filter list - Removed keyword: %s from filter list - Marked as seen - Delete unsuccessful + Palavra-chave + Ativar filtro de palavras-chave + Editar filtros de palavras-chave + Palavra-chave adicionada: %s para a lista de filtros + Palavra-chave removida: %s da lista de filtros + Marcado como visto + Falha ao excluir + Impedido pelo Instagram por causa de muitos pedidos da API. Espere algum tempo antes de tentar novamente. + Erro + Esta conta foi desconectada. + Login necessário! + Sentry bloqueado. + O usuário está inativo! + Relatório de Travamento do Barinsta + Selecione um aplicativo de e-mail para enviar os registros de travamento + Não encontrado! + Seu IP foi limitado pelo Instagram. Espere uma hora e tente novamente. + Pular esta atualização + Você já está na versão mais recente + Ordem da tela + Outras abas + A ordem das abas será refletida no próximo início + Se salvo, todos os recursos relacionados a DM serão desativados no próximo início + Copiar legenda + Copiar resposta diff --git a/app/src/main/res/values-ru/arrays.xml b/app/src/main/res/values-ru/arrays.xml index 8fb04bad..8a7d9564 100644 --- a/app/src/main/res/values-ru/arrays.xml +++ b/app/src/main/res/values-ru/arrays.xml @@ -44,6 +44,15 @@ \| - + + + + + + + + + секунды минуты diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index ca3155ef..6f76baed 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -21,11 +21,12 @@ Комментарии Активность Проверять наличие обновлений при запуске - Block screenshots & app preview + Блокировать скриншоты & превью приложения Скачать публикации в папки с именем пользователя Prepend Username to Filename Отметить истории как увиденные после просмотра Автор истории узнает, что вы просмотрели её + Hide muted stories from feed Отметить ЛС как увиденные после просмотра Другие участники узнают, что вы просмотрели его Включить уведомления об активности @@ -188,7 +189,7 @@ Предложено Сделан снимок экрана Не удаётся доставить - Unseen count response is null! + Количество непрочитанных сообщений неизвестно! Сообщение... Нажмите и удерживайте, чтобы записать звук Обновление... @@ -271,7 +272,7 @@ Начальный экран Общее Тема - Скачивания + Загрузки Локализация Учётная запись Текущий вход не работает? Просто добавьте учётную запись снова. @@ -313,8 +314,8 @@ Перевести подпись Лента времени видеопроигрывателя Нравится… - Нравится неудачно - Не нравится неудачно + Не удалось поставить лайк + Не удалось снять лайк Отклонение… Управление Сохранение… @@ -405,7 +406,7 @@ Готово Сделать администратором Удалить администратора - Ошибка при изменении + Не удалось изменить Сообщение Ответить Нажмите для удаления @@ -469,11 +470,29 @@ Ответ пуст! Ответный статус не в порядке! Запрос не удался! - Keyword - Enable keyword filter - Edit keyword filters - Added keyword: %s to filter list - Removed keyword: %s from filter list - Marked as seen - Delete unsuccessful + Ключевое слово + Включить фильтр по ключевым словам + Редактировать фильтры ключевых слов + Добавлено ключевое слово: %s в список фильтров + Удалено ключевое слово: %s из списка фильтров + Отмечено как просмотренное + Не удалось удалить + Throttled by Instagram because of too many API requests. Wait for some time before retrying. + Error + This account has been logged out. + Login required! + Sentry block. + User is inactive! + Barinsta Crash Report + Выберите приложение для отправки логов ошибки + Not found! + Your IP has been rate limited by Instagram. Wait for an hour and try again. + Skip this update + You\'re already on the latest version + Screen order + Other tabs + The tab order will be reflected on next launch + If saved, all DM related features will be disabled on next launch + Копировать подпись + Копировать ответ diff --git a/app/src/main/res/values-sk/arrays.xml b/app/src/main/res/values-sk/arrays.xml index 9e9535a2..2e5b71d2 100644 --- a/app/src/main/res/values-sk/arrays.xml +++ b/app/src/main/res/values-sk/arrays.xml @@ -44,6 +44,15 @@ \| - + + + + + + + + + sekúnd minút diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 7b1c175a..3b91ad3a 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -26,6 +26,7 @@ Prepend Username to Filename Označiť príbehy po videní ako videné Autor príbehu bude vedieť že ste ho videli + Hide muted stories from feed Po prečítaní, označiť správu ako prečítanú Ostatní budú vedieť že ste ho videli Zapnúť notifikácie o aktivitách @@ -476,4 +477,22 @@ Removed keyword: %s from filter list Marked as seen Delete unsuccessful + Throttled by Instagram because of too many API requests. Wait for some time before retrying. + Error + This account has been logged out. + Login required! + Sentry block. + User is inactive! + Barinsta Crash Report + Select an email app to send crash logs + Not found! + Your IP has been rate limited by Instagram. Wait for an hour and try again. + Skip this update + You\'re already on the latest version + Screen order + Other tabs + The tab order will be reflected on next launch + If saved, all DM related features will be disabled on next launch + Copy caption + Copy reply diff --git a/app/src/main/res/values-tr/arrays.xml b/app/src/main/res/values-tr/arrays.xml index e87f4cd1..d1cc9560 100644 --- a/app/src/main/res/values-tr/arrays.xml +++ b/app/src/main/res/values-tr/arrays.xml @@ -44,6 +44,15 @@ \| - + + + + + + + + + saniye dakika diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index fea3a299..02bbdef6 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -26,6 +26,7 @@ Prepend Username to Filename Hikayeleri gördükten sonra görüldü olarak işaretle Hikayeyi paylaşan gördüğünüzü bilecek + Hide muted stories from feed DM\'leri gördükten sonra görüldü olarak işaretle Diğerleri gördüğünüzü bilecek Hareketler için bildirimleri aktifleştir @@ -460,4 +461,22 @@ Removed keyword: %s from filter list Marked as seen Delete unsuccessful + Throttled by Instagram because of too many API requests. Wait for some time before retrying. + Error + This account has been logged out. + Login required! + Sentry block. + User is inactive! + Barinsta Crash Report + Select an email app to send crash logs + Not found! + Your IP has been rate limited by Instagram. Wait for an hour and try again. + Skip this update + You\'re already on the latest version + Screen order + Other tabs + The tab order will be reflected on next launch + If saved, all DM related features will be disabled on next launch + Copy caption + Copy reply diff --git a/app/src/main/res/values-vi/arrays.xml b/app/src/main/res/values-vi/arrays.xml index c45da5ac..97d23d29 100644 --- a/app/src/main/res/values-vi/arrays.xml +++ b/app/src/main/res/values-vi/arrays.xml @@ -32,9 +32,9 @@ Sáng - Instagram default (Unread then read) - From newest to oldest - From oldest to newest + Mặc định của Instagram (chưa đọc rồi đọc) + Mới nhất đến cũ nhất + Cũ nhất đến mới nhất Không @@ -44,8 +44,17 @@ \| - + + + + + + + + + - secs - mins + giây + phút diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 858193f6..c8d69477 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -9,7 +9,7 @@ Lỗi khi sao chép Đã sao chép vào clipboard! Báo cáo - Protect file with password + Bảo vệ file với mật khẩu Mật khẩu OK @@ -21,19 +21,20 @@ Bình luận Hoạt động Kiểm tra cập nhật khi khởi động - Block screenshots & app preview + Chặn chụp ảnh màn hình & xem trước ứng dụng Tải bài viết xuống theo thư mục tên người dùng trong Downloads Prepend Username to Filename Đánh dấu story là đã xem sau khi xem Người đăng story sẽ biết bạn đã xem nó + Hide muted stories from feed Đánh dấu DM là đã xem sau khi xem Những thành viên khác sẽ biết bạn đã xem nó Kích hoạt thông báo hoạt động - Feed stories sort - Error loading profile! Is the username valid? If so, you may be ratelimited. - Error loading profile! Is the username valid? Or did they block you? - Error loading hashtag! Is the name valid? - Error loading location! Is the URL valid? + Sắp xếp câu chuyện trên bảng tin + Tải hồ sơ thất bại! Tên người dùng có đúng chưa? Nếu đúng, có thể bạn đã bị giới hạn. + Họ có chặn bạn không? + Tải hashtag thất bại! Tên đã đúng chứ? + Tải vị trí thất bại! URL đã đúng chưa? Lỗi khi tạo thư mục Download. Lưu vào thư mục tùy chọn Chọn thư mục @@ -53,8 +54,8 @@ %s\nĐang theo dõi Tự động phát video Luôn luôn tắt âm thanh video - Always show post captions - Chọn cái để tải xuống + Luôn hiển thị tiêu đề của bài viết + Chọn mục tải xuống Hiện tại Cả album Hiện story @@ -90,13 +91,13 @@ Xóa tất cả tài khoản Điều này sẽ xóa tất cả tài khoản đã thêm vào ứng dụng!\nĐể xóa một tài khoản duy nhất, nhấn giữ vào tài khoản đó trong mục đổi tài khoản.\nBạn có muốn tiếp tục không? Định dạng ngày tháng - Create new collection - Edit collection name - Delete collection - Are you sure you want to delete this collection? - All contained media will remain in other collections. - Add to collection... - Remove from collection + Tạo bộ sưu tập mới + Chỉnh sửa tên bộ sưu tập + Xoá bộ sưu tập + Bạn có chắc muốn xoá bộ sưu tập này chứ? + Tất cả những phương tiện đã lưu sẽ được giữ lại trong bộ sưu tập khác. + Thêm vào bộ sưu tập... + Xoá khỏi bộ sưu tập Đã thích Đã lưu Đã gắn thẻ @@ -112,15 +113,15 @@ Bỏ chặn Giới hạn Bỏ giới hạn - Mute stories - Mute posts - Unmute stories - Unmute posts - Copy bio - Translate bio - Mutual - Following - Follower + Chặn story + Chặn bài đăng + Bỏ chặn story + Bỏ chặn bài đăng + Sao chép thông tin + Dịch thông tin + Chung + Đang theo dõi + Người theo dõi Bản đồ Tài khoản Cài đặt @@ -141,29 +142,29 @@ Không thể xóa tài khoản đang được sử dụng Bạn có chắc bạn muốn xóa \'%s\'? Mở hồ sơ - View story + Xem story Xem Ảnh Đại Diện - Unsupported message type + Dạng tin nhắn không được hỗ trợ Gỡ tin nhắn - View on GIPHY - %s shared a post by @%s - %s shared an image - %s shared a video - %s sent a message - %s shared a gif - %s shared a sticker - %s shared a profile: @%s - %s shared a location: %s - %s shared a story highlight by @%s - %s shared a story by @%s - %s sent a voice message - %s shared a clip by @%s - %s shared an IGTV video by @%s - You replied to their story: %s - %s replied to your story: %s - You reacted to their story: %s - %s reacted to your story: %s - You mentioned @%s in your story + Xem trên GIPHY + %s đã chia sẻ bài viết của @%s + %s đã chia sẻ một bức ảnh + %s đã chia sẻ một video + %s đã gửi một tin nhắn + %s đã chia sẻ một ảnh động + %s đã chia sẻ một sticker + %s đã chia sẻ một hồ sơ: @%s + %s% đã chia sẻ vị trí: %s + %s đã chia sẻ một story highlight của @%s + %s đã chia sẻ một story của @%s + %s đã gửi một tin nhắn thoại + %s đã chia sẻ một đoạn clip của @%s + %s đã chia sẻ một đoạn video IGTV của @%s + Bạn đã phản hồi story của họ: %s + %s đã phản hồi story của bạn: %s + Bạn đã bày tỏ cảm xúc story của họ: %s + %s đã bày tỏ cảm xúc story của bạn: %s + Bạn đã gắn thẻ @%s trong story của mình %s mentioned you in their story Phương tiện không xác định Phương tiện đã hết hạn! @@ -452,4 +453,22 @@ Removed keyword: %s from filter list Marked as seen Delete unsuccessful + Throttled by Instagram because of too many API requests. Wait for some time before retrying. + Error + This account has been logged out. + Login required! + Sentry block. + User is inactive! + Barinsta Crash Report + Select an email app to send crash logs + Not found! + Your IP has been rate limited by Instagram. Wait for an hour and try again. + Skip this update + You\'re already on the latest version + Screen order + Other tabs + The tab order will be reflected on next launch + If saved, all DM related features will be disabled on next launch + Copy caption + Copy reply diff --git a/app/src/main/res/values-zh-rCN/arrays.xml b/app/src/main/res/values-zh-rCN/arrays.xml index 667cd83a..9a8d9fd0 100644 --- a/app/src/main/res/values-zh-rCN/arrays.xml +++ b/app/src/main/res/values-zh-rCN/arrays.xml @@ -44,6 +44,15 @@ \| - + + + + + + + + + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 0fb2193f..e7987f20 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -23,9 +23,10 @@ 启动时检查更新 屏蔽截图及应用预览 下载帖子到用户名文件夹 - Prepend Username to Filename + 在文件名前添加用户名 查看快拍后将其标记为已读 快拍作者会知道您已看过 + 动态隐藏静音快拍 查看私信后将其标记为已读 其他成员会知道你看过了 启用活动通知 @@ -445,11 +446,29 @@ 回应为空! 回应状态错误! 请求失败 - Keyword - Enable keyword filter - Edit keyword filters - Added keyword: %s to filter list - Removed keyword: %s from filter list + 关键词 + 启用关键词过滤器 + 编辑关键词过滤器 + 已添加关键词: %s 至过滤列表 + 已移除关键词: %s 至过滤列表 已标记为已读 删除失败 + Throttled by Instagram because of too many API requests. Wait for some time before retrying. + Error + This account has been logged out. + Login required! + Sentry block. + User is inactive! + Barinsta 崩溃报告 + 选择一个电子邮件应用来发送崩溃日志 + Not found! + Your IP has been rate limited by Instagram. Wait for an hour and try again. + 跳过本次更新 + 您现在安装的是最新版本 + 页面顺序 + 其它页面 + 页面顺序将在下次启动时生效 + 若您如此保存,所有私信功能将会在下次启动时被禁用 + 复制说明 + 复制回复 diff --git a/app/src/main/res/values-zh-rTW/arrays.xml b/app/src/main/res/values-zh-rTW/arrays.xml index 2b82c66e..ad809f5e 100644 --- a/app/src/main/res/values-zh-rTW/arrays.xml +++ b/app/src/main/res/values-zh-rTW/arrays.xml @@ -44,6 +44,15 @@ \| - + + + + + + + + + secs mins diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 3ce706fa..e7a4ea57 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -26,6 +26,7 @@ Prepend Username to Filename 檢視完限時動態後標記為已讀 限時動態的作者會知道您已查看了此限時動態 + Hide muted stories from feed 檢視完訊息後標記為已讀 其他成員會知道您查看了此訊息 啟用活動通知 @@ -452,4 +453,22 @@ Removed keyword: %s from filter list Marked as seen Delete unsuccessful + Throttled by Instagram because of too many API requests. Wait for some time before retrying. + Error + This account has been logged out. + Login required! + Sentry block. + User is inactive! + Barinsta Crash Report + Select an email app to send crash logs + Not found! + Your IP has been rate limited by Instagram. Wait for an hour and try again. + Skip this update + You\'re already on the latest version + Screen order + Other tabs + The tab order will be reflected on next launch + If saved, all DM related features will be disabled on next launch + Copy caption + Copy reply From 0560f082cc709c80fa7981f6d76ff210be5c727f Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Sat, 3 Apr 2021 22:06:35 -0400 Subject: [PATCH 21/95] update contributors --- .all-contributorsrc | 46 ++++++++++++++++++++++++++++++++++++--------- README.md | 29 +++++++++++++++------------- 2 files changed, 53 insertions(+), 22 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index aa7840b9..cd865d08 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -32,6 +32,24 @@ "question" ] }, + { + "login": "zerrium", + "name": "Zerrium", + "avatar_url": "https://avatars.githubusercontent.com/u/58355441?v=4", + "profile": "https://github.com/zerrium", + "contributions": [ + "code" + ] + }, + { + "login": "junhuicoding", + "name": "Chua Jun Hui", + "avatar_url": "https://avatars.githubusercontent.com/u/54289027?v=4", + "profile": "https://github.com/junhuicoding", + "contributions": [ + "code" + ] + }, { "login": "andersonvom", "name": "Anderson Mesquita", @@ -42,6 +60,16 @@ "bug" ] }, + { + "login": "vojta-horanek", + "name": "Vojtěch Hořánek", + "avatar_url": "https://avatars.githubusercontent.com/u/12630566?v=4", + "profile": "https://vojtechh.eu/", + "contributions": [ + "code", + "translation" + ] + }, { "login": "MeLlamoPablo", "name": "Pablo Rodríguez", @@ -255,15 +283,15 @@ "translation" ] }, - { - "login": "Pyrobauve", - "name": "Pyrobauve", - "avatar_url": "https://avatars.githubusercontent.com/u/48654473?v=4", - "profile": "https://github.com/Pyrobauve", - "contributions": [ - "translation" - ] - }, + { + "login": "Pyrobauve", + "name": "Pyrobauve", + "avatar_url": "https://avatars.githubusercontent.com/u/48654473?v=4", + "profile": "https://github.com/Pyrobauve", + "contributions": [ + "translation" + ] + }, { "login": "RAMAR-RAR", "name": "RAMAR-RAR", diff --git a/README.md b/README.md index 16258554..d1301419 100755 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com) [![GPLv3 license](https://img.shields.io/badge/License-GPLv3-blue.svg)](./LICENSE) [![GitHub stars](https://img.shields.io/github/stars/austinhuang0131/instagrabber.svg?style=social&label=Star)](https://GitHub.com/austinhuang0131/barinsta/stargazers/) -[![All Contributors](https://img.shields.io/badge/all_contributors-39-orange.svg)](#contributors) +[![All Contributors](https://img.shields.io/badge/all_contributors-42-orange.svg)](#contributors) Instagram client; previously known as InstaGrabber. @@ -56,52 +56,55 @@ Prominent contributors are listed here in the [all-contributors](https://allcont
Austin Huang

💻 📖 💬 🌍 🤔
Ammar Githam

💻 🎨 🤔 🚧 💬 +
Zerrium

💻 +
Chua Jun Hui

💻
Anderson Mesquita

💻 🐛 +
Vojtěch Hořánek

💻 🌍 + +
Pablo Rodríguez

💻
Alexandre Macabies

💻
Stefan Najdovski

🎨 🌍 - -
CrazyMarvin

💵
Kevin Thomas

💵
Shadowspear123

📝 🐛 🤔 💬 + +
Ricardo

🐛 🌍
Akrai

🤔 🌍
avtkal

🌍 - -
Cézar Augusto

🌍
Dimitris T

🌍
farzadx

🌍 + +
Fatih Aydın

🌍
fouze555

🌍
Galang23

🌍 - -
Initdebugs

🌍
Jakub Janek

🌍
GenosseFlosse

🌍 + +
kernoeb

🌍
MoaufmKlo

🌍
nalinalini

🌍 - -
peterge1998

🌍
PierreM0

🌍
Pyrobauve

🌍 + +
RAMAR-RAR

🌍
rohang02

🌍
retiolus

🌍 - -
rikishi0071

🌍
Alexey Peschany

🌍
Sitavi

🌍 + +
Still Hsu

🌍
Ten_Lego

🌍
wagnim

🌍 - -
wokija

🌍
ysakamoto

🌍
ZDVokoun

🌍 From 50429a0926b33288f2d80257fb7b006e1a4e731f Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Sat, 3 Apr 2021 22:16:50 -0400 Subject: [PATCH 22/95] #955 --- .../instagrabber/fragments/settings/AboutFragment.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/AboutFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/AboutFragment.java index 98afce8d..5a48c04b 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/AboutFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/AboutFragment.java @@ -75,6 +75,12 @@ public class AboutFragment extends BasePreferencesFragment { "Copyright (C) 2014 Austin Andrews & Google LLC. Apache 2.0.", "https://materialdesignicons.com" )); + thirdPartyCategory.addPreference(get3ptPreference( + context, + "Process Phoenix", + "Copyright (C) 2015 Jake Wharton. Apache 2.0.", + "https://github.com/JakeWharton/ProcessPhoenix" + )); thirdPartyCategory.addPreference(get3ptPreference( context, "Retrofit", From 51f76a6c3c2aa1ce4a1d48a8f58b109a36ef33a7 Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Sat, 3 Apr 2021 22:22:55 -0400 Subject: [PATCH 23/95] release prep --- .project | 4 ++-- app/build.gradle | 4 ++-- fastlane/metadata/android/en-US/changelogs/61.txt | 3 +++ 3 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/61.txt diff --git a/.project b/.project index 800b8ec3..b3746935 100644 --- a/.project +++ b/.project @@ -1,6 +1,6 @@ - InstaGrabber + Barinsta Project instagrabber created by Buildship. @@ -16,7 +16,7 @@ - 1600117114918 + 0 30 diff --git a/app/build.gradle b/app/build.gradle index 8b9632e6..871f8cc5 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -20,8 +20,8 @@ android { minSdkVersion 21 targetSdkVersion 29 - versionCode 60 - versionName '19.1.0' + versionCode 61 + versionName '19.2.0' multiDexEnabled true diff --git a/fastlane/metadata/android/en-US/changelogs/61.txt b/fastlane/metadata/android/en-US/changelogs/61.txt new file mode 100644 index 00000000..327c4802 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/61.txt @@ -0,0 +1,3 @@ +New customizable bottom navigation bar, keyword filter, add username to file name, FLAG_SECURE, and more! + +For details see https://github.com/austinhuang0131/barinsta/releases/tag/v19.2.0 \ No newline at end of file From f584b51f64f7161f79fba7df279dd0205efd393f Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Sun, 4 Apr 2021 09:47:57 -0400 Subject: [PATCH 24/95] fix navigation actions for fdroid --- .../directmessages/DirectMessageSettingsFragment.java | 6 ++---- .../directmessages/DirectMessageThreadFragment.java | 3 +-- .../main/res/navigation/notification_viewer_nav_graph.xml | 2 +- app/src/main/res/navigation/profile_nav_graph.xml | 4 +++- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageSettingsFragment.java b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageSettingsFragment.java index f98e4094..ea895eba 100644 --- a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageSettingsFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageSettingsFragment.java @@ -354,8 +354,7 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi } if (TextUtils.isEmpty(user.getUsername())) return; final ProfileNavGraphDirections.ActionGlobalProfileFragment directions = ProfileNavGraphDirections - .actionGlobalProfileFragment() - .setUsername("@" + user.getUsername()); + .actionGlobalProfileFragment("@" + user.getUsername()); NavHostFragment.findNavController(this).navigate(directions); }, (position, user) -> { @@ -393,8 +392,7 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi @Override public void onClick(final int position, final PendingUser pendingUser) { final ProfileNavGraphDirections.ActionGlobalProfileFragment directions = ProfileNavGraphDirections - .actionGlobalProfileFragment() - .setUsername("@" + pendingUser.getUser().getUsername()); + .actionGlobalProfileFragment("@" + pendingUser.getUser().getUsername()); NavHostFragment.findNavController(DirectMessageSettingsFragment.this).navigate(directions); } diff --git a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java index a6b1cb4c..57f17cf2 100644 --- a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java @@ -1447,8 +1447,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact private void navigateToUser(@NonNull final String username) { final ProfileNavGraphDirections.ActionGlobalProfileFragment direction = ProfileNavGraphDirections - .actionGlobalProfileFragment() - .setUsername("@" + username); + .actionGlobalProfileFragment("@" + username); NavHostFragment.findNavController(DirectMessageThreadFragment.this).navigate(direction); } diff --git a/app/src/main/res/navigation/notification_viewer_nav_graph.xml b/app/src/main/res/navigation/notification_viewer_nav_graph.xml index 50e2fbdf..139795c1 100644 --- a/app/src/main/res/navigation/notification_viewer_nav_graph.xml +++ b/app/src/main/res/navigation/notification_viewer_nav_graph.xml @@ -13,7 +13,7 @@ + app:nullable="false" /> @@ -82,6 +81,9 @@ android:name="targetId" android:defaultValue="0L" app:argType="long" /> + Date: Sun, 4 Apr 2021 10:43:51 -0400 Subject: [PATCH 25/95] fix navigation actions for fdroid, part 2 --- .../fragments/NotificationsViewerFragment.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java index 15d5306d..880db291 100644 --- a/app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java @@ -93,12 +93,12 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe final NotificationImage notificationImage = model.getArgs().getMedia().get(0); final long mediaId = Long.parseLong(notificationImage.getId().split("_")[0]); if (model.getType() == NotificationType.RESPONDED_STORY) { - final NavDirections action = NotificationsViewerFragmentDirections - .actionNotificationsToStory( - StoryViewerOptions.forStory( + final StoryViewerOptions options = StoryViewerOptions.forStory( mediaId, - model.getArgs().getUsername())); - NavHostFragment.findNavController(NotificationsViewerFragment.this).navigate(action); + model.getArgs().getUsername()); + final Bundle bundle = new Bundle(); + bundle.putSerializable("options", options); + NavHostFragment.findNavController(NotificationsViewerFragment.this).navigate(R.id.action_notifications_to_story, bundle); } else { final AlertDialog alertDialog = new AlertDialog.Builder(context) .setCancelable(false) From 917e4f3fbd68dfb73eb719dd213aa8fcda3e4e98 Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Sun, 4 Apr 2021 10:59:24 -0400 Subject: [PATCH 26/95] close #971 --- .../main/java/awais/instagrabber/utils/ResponseBodyUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java b/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java index 0d7acc16..aefc94f3 100644 --- a/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java @@ -1094,7 +1094,6 @@ public final class ResponseBodyUtils { final List sortedCandidates = candidates.stream() .sorted((c1, c2) -> Integer.compare(c2.getWidth(), c1.getWidth())) .collect(Collectors.toList()); - if (sortedCandidates.size() == 1) return sortedCandidates.get(0).getUrl(); final List filteredCandidates = sortedCandidates.stream() .filter(c -> c.getWidth() <= media.getOriginalWidth() @@ -1102,6 +1101,7 @@ public final class ResponseBodyUtils { && (isSquare || Integer.compare(c.getWidth(), c.getHeight()) != 0) ) .collect(Collectors.toList()); + if (filteredCandidates.size() == 0) return sortedCandidates.get(0).getUrl(); final MediaCandidate candidate = filteredCandidates.get(0); if (candidate == null) return null; return candidate.getUrl(); From a2f73eeb2db523c3d3e95dbc31f910763cdbf964 Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Wed, 7 Apr 2021 07:36:21 -0400 Subject: [PATCH 27/95] actually add getHideMutedReelsPreference --- .../fragments/settings/StoriesPreferencesFragment.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/StoriesPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/StoriesPreferencesFragment.java index 99eee7c7..0f3850ab 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/StoriesPreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/StoriesPreferencesFragment.java @@ -17,6 +17,7 @@ public class StoriesPreferencesFragment extends BasePreferencesFragment { final Context context = getContext(); if (context == null) return; screen.addPreference(getStorySortPreference(context)); + screen.addPreference(getHideMutedReelsPreference(context)); screen.addPreference(getMarkStoriesSeenPreference(context)); } From abd6fa1c289d8375de61639a8e9d29f8bf5e156a Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Fri, 9 Apr 2021 19:13:59 +0900 Subject: [PATCH 28/95] Null check. Fixes https://github.com/austinhuang0131/barinsta/issues/1021 --- .../viewholder/directmessages/DirectItemViewHolder.java | 5 +++-- app/src/main/java/awais/instagrabber/utils/DMUtils.java | 9 ++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemViewHolder.java index 96476b10..17a0956c 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemViewHolder.java @@ -112,6 +112,7 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple } public void bind(final int position, final DirectItem item) { + if (item == null) return; this.item = item; messageDirection = isSelf(item) ? MessageDirection.OUTGOING : MessageDirection.INCOMING; // Asynchronous binding causes some weird behaviour @@ -123,7 +124,7 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple setupLongClickListener(position, messageDirection); } - private void bindBase(final DirectItem item, final MessageDirection messageDirection, final int position) { + private void bindBase(@NonNull final DirectItem item, final MessageDirection messageDirection, final int position) { final FrameLayout.LayoutParams containerLayoutParams = (FrameLayout.LayoutParams) binding.container.getLayoutParams(); final DirectItemType itemType = item.getItemType(); setMessageDirectionGravity(messageDirection, containerLayoutParams); @@ -188,7 +189,7 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple containerLayoutParams.gravity = Gravity.CENTER; } - private void setMessageInfo(final DirectItem item, final MessageDirection messageDirection) { + private void setMessageInfo(@NonNull final DirectItem item, final MessageDirection messageDirection) { if (showMessageInfo()) { binding.messageInfo.setVisibility(View.VISIBLE); binding.deliveryStatus.setVisibility(messageDirection == MessageDirection.OUTGOING ? View.VISIBLE : View.GONE); diff --git a/app/src/main/java/awais/instagrabber/utils/DMUtils.java b/app/src/main/java/awais/instagrabber/utils/DMUtils.java index 12aa2e07..904a7055 100644 --- a/app/src/main/java/awais/instagrabber/utils/DMUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/DMUtils.java @@ -23,18 +23,21 @@ import awais.instagrabber.repositories.responses.directmessages.DirectThreadLast import awais.instagrabber.repositories.responses.directmessages.RavenExpiringMediaActionSummary; public final class DMUtils { - public static boolean isRead(final DirectItem item, + public static boolean isRead(@NonNull final DirectItem item, @NonNull final Map lastSeenAt, - final List userIdsToCheck) { + @NonNull final List userIdsToCheck) { // Further check if directStory exists // if (read && directStory != null) { // read = false; // } + if (item == null) return false; return lastSeenAt.entrySet() .stream() .filter(entry -> userIdsToCheck.contains(entry.getKey())) .anyMatch(entry -> { - final String userLastSeenTsString = entry.getValue().getTimestamp(); + final DirectThreadLastSeenAt threadLastSeenAt = entry.getValue(); + if (threadLastSeenAt == null) return false; + final String userLastSeenTsString = threadLastSeenAt.getTimestamp(); if (userLastSeenTsString == null) return false; final long userTs = Long.parseLong(userLastSeenTsString); final long itemTs = item.getTimestamp(); From bb29e847f53bd56424cd6e91486ada7a977d07ab Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Fri, 9 Apr 2021 19:29:38 +0900 Subject: [PATCH 29/95] Check for all dm related values before initializing. Fixes https://github.com/austinhuang0131/barinsta/issues/1019 --- .../instagrabber/activities/MainActivity.java | 35 ++++++++++++++----- .../instagrabber/managers/InboxManager.java | 8 +++-- .../instagrabber/managers/ThreadManager.java | 6 ++-- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/activities/MainActivity.java b/app/src/main/java/awais/instagrabber/activities/MainActivity.java index 268b9ca5..8882bed9 100644 --- a/app/src/main/java/awais/instagrabber/activities/MainActivity.java +++ b/app/src/main/java/awais/instagrabber/activities/MainActivity.java @@ -57,6 +57,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Deque; import java.util.List; +import java.util.UUID; import java.util.stream.Collectors; import awais.instagrabber.BuildConfig; @@ -85,8 +86,8 @@ import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.emoji.EmojiParser; import awais.instagrabber.viewmodels.AppStateViewModel; -import awais.instagrabber.webservices.RetrofitFactory; import awais.instagrabber.viewmodels.DirectInboxViewModel; +import awais.instagrabber.webservices.RetrofitFactory; import awais.instagrabber.webservices.SearchService; import retrofit2.Call; import retrofit2.Callback; @@ -137,9 +138,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage RetrofitFactory.setup(this); super.onCreate(savedInstanceState); binding = ActivityMainBinding.inflate(getLayoutInflater()); - final String cookie = settingsHelper.getString(Constants.COOKIE); - CookieUtils.setupCookies(cookie); - isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != 0; + setupCookie(); if (settingsHelper.getBoolean(Constants.FLAG_SECURE)) getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); setContentView(binding.getRoot()); @@ -165,7 +164,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage new ViewModelProvider(this).get(AppStateViewModel.class); // Just initiate the App state here final Intent intent = getIntent(); handleIntent(intent); - if (!TextUtils.isEmpty(cookie) && settingsHelper.getBoolean(Constants.CHECK_ACTIVITY)) { + if (isLoggedIn && settingsHelper.getBoolean(Constants.CHECK_ACTIVITY)) { bindActivityCheckerService(); } getSupportFragmentManager().addOnBackStackChangedListener(this); @@ -180,6 +179,26 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage initDmUnreadCount(); } + private void setupCookie() { + final String cookie = settingsHelper.getString(Constants.COOKIE); + long userId = 0; + String csrfToken = null; + if (!TextUtils.isEmpty(cookie)) { + userId = CookieUtils.getUserIdFromCookie(cookie); + csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie); + } + if (TextUtils.isEmpty(cookie) || userId == 0 || TextUtils.isEmpty(csrfToken)) { + isLoggedIn = false; + return; + } + final String deviceUuid = settingsHelper.getString(Constants.DEVICE_UUID); + if (TextUtils.isEmpty(deviceUuid)) { + settingsHelper.putString(Constants.DEVICE_UUID, UUID.randomUUID().toString()); + } + CookieUtils.setupCookies(cookie); + isLoggedIn = true; + } + private void initDmService() { if (!isLoggedIn) return; final boolean enabled = settingsHelper.getBoolean(PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH); @@ -916,9 +935,9 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage return currentTabs; } -// public boolean isNavRootInCurrentTabs(@IdRes final int navRootId) { -// return showBottomViewDestinations.stream().anyMatch(id -> id == navRootId); -// } + // public boolean isNavRootInCurrentTabs(@IdRes final int navRootId) { + // return showBottomViewDestinations.stream().anyMatch(id -> id == navRootId); + // } private void setNavBarDMUnreadCountBadge(final int unseenCount) { final BadgeDrawable badge = binding.bottomNavView.getOrCreateBadge(R.id.direct_messages_nav_graph); diff --git a/app/src/main/java/awais/instagrabber/managers/InboxManager.java b/app/src/main/java/awais/instagrabber/managers/InboxManager.java index 98e12b46..efca55f5 100644 --- a/app/src/main/java/awais/instagrabber/managers/InboxManager.java +++ b/app/src/main/java/awais/instagrabber/managers/InboxManager.java @@ -82,8 +82,12 @@ public final class InboxManager { final long userId = CookieUtils.getUserIdFromCookie(cookie); final String deviceUuid = settingsHelper.getString(Constants.DEVICE_UUID); final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie); - if (TextUtils.isEmpty(csrfToken) || userId <= 0 || TextUtils.isEmpty(deviceUuid)) { - throw new IllegalArgumentException("User is not logged in!"); + if (TextUtils.isEmpty(csrfToken)) { + throw new IllegalArgumentException("csrfToken is empty!"); + } else if (userId == 0) { + throw new IllegalArgumentException("user id invalid"); + } else if (TextUtils.isEmpty(deviceUuid)) { + throw new IllegalArgumentException("device uuid is empty!"); } service = DirectMessagesService.getInstance(csrfToken, userId, deviceUuid); diff --git a/app/src/main/java/awais/instagrabber/managers/ThreadManager.java b/app/src/main/java/awais/instagrabber/managers/ThreadManager.java index 8efff504..c76728e3 100644 --- a/app/src/main/java/awais/instagrabber/managers/ThreadManager.java +++ b/app/src/main/java/awais/instagrabber/managers/ThreadManager.java @@ -156,9 +156,9 @@ public final class ThreadManager { viewerId = CookieUtils.getUserIdFromCookie(cookie); final String deviceUuid = settingsHelper.getString(Constants.DEVICE_UUID); final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie); - if (TextUtils.isEmpty(csrfToken) || viewerId <= 0 || TextUtils.isEmpty(deviceUuid)) { - throw new IllegalArgumentException("User is not logged in!"); - } + // if (TextUtils.isEmpty(csrfToken) || viewerId <= 0 || TextUtils.isEmpty(deviceUuid)) { + // throw new IllegalArgumentException("User is not logged in!"); + // } service = DirectMessagesService.getInstance(csrfToken, viewerId, deviceUuid); mediaService = MediaService.getInstance(deviceUuid, csrfToken, viewerId); friendshipService = FriendshipService.getInstance(deviceUuid, csrfToken, viewerId); From 1fdd9077d5b776a121d78ea498ca000801345db6 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Fri, 9 Apr 2021 19:36:11 +0900 Subject: [PATCH 30/95] Remove invalid gifs before submitting to adapter. Fixes https://github.com/austinhuang0131/barinsta/issues/1013 --- .../viewmodels/GifPickerViewModel.java | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/viewmodels/GifPickerViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/GifPickerViewModel.java index dd014204..69450281 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/GifPickerViewModel.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/GifPickerViewModel.java @@ -13,12 +13,17 @@ import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Objects; +import java.util.stream.Collectors; import awais.instagrabber.R; import awais.instagrabber.models.Resource; +import awais.instagrabber.repositories.responses.AnimatedMediaFixedHeight; import awais.instagrabber.repositories.responses.giphy.GiphyGif; +import awais.instagrabber.repositories.responses.giphy.GiphyGifImages; import awais.instagrabber.repositories.responses.giphy.GiphyGifResponse; import awais.instagrabber.repositories.responses.giphy.GiphyGifResults; +import awais.instagrabber.utils.TextUtils; import awais.instagrabber.webservices.GifService; import retrofit2.Call; import retrofit2.Callback; @@ -92,12 +97,25 @@ public class GifPickerViewModel extends ViewModel { final GiphyGifResults results = giphyGifResponse.getResults(); images.postValue(Resource.success( ImmutableList.builder() - .addAll(results.getGiphy() == null ? Collections.emptyList() : results.getGiphy()) - .addAll(results.getGiphyGifs() == null ? Collections.emptyList() : results.getGiphyGifs()) + .addAll(results.getGiphy() == null ? Collections.emptyList() : filterInvalid(results.getGiphy())) + .addAll(results.getGiphyGifs() == null ? Collections.emptyList() : filterInvalid(results.getGiphyGifs())) .build() )); } + private List filterInvalid(@NonNull final List giphyGifs) { + return giphyGifs.stream() + .filter(Objects::nonNull) + .filter(giphyGif -> { + final GiphyGifImages images = giphyGif.getImages(); + if (images == null) return false; + final AnimatedMediaFixedHeight fixedHeight = images.getFixedHeight(); + if (fixedHeight == null) return false; + return !TextUtils.isEmpty(fixedHeight.getWebp()); + }) + .collect(Collectors.toList()); + } + // @NonNull // private List getGiphyGifImages(@NonNull final List giphy) { // return giphy.stream() From f570ea6146401a41b32f74c79a8afd58ecf9e794 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Fri, 9 Apr 2021 19:51:37 +0900 Subject: [PATCH 31/95] Null checks. Fixes https://github.com/austinhuang0131/barinsta/issues/992 --- .../fragments/settings/MorePreferencesFragment.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java index 8348123b..a07c9221 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java @@ -88,13 +88,17 @@ public class MorePreferencesFragment extends BasePreferencesFragment { public void onSuccess(@NonNull final List accounts) { if (!isLoggedIn) { if (accounts.size() > 0) { - accountCategory.addPreference(getAccountSwitcherPreference(null, context)); + final AccountSwitcherPreference preference = getAccountSwitcherPreference(null, context); + if (preference == null) return; + accountCategory.addPreference(preference); } // Need to show something to trigger login activity - accountCategory.addPreference(getPreference(R.string.add_account, R.drawable.ic_add, preference -> { + final Preference preference1 = getPreference(R.string.add_account, R.drawable.ic_add, preference -> { startActivityForResult(new Intent(getContext(), Login.class), Constants.LOGIN_RESULT_CODE); return true; - })); + }); + if (preference1 == null) return; + accountCategory.addPreference(preference1); } if (accounts.size() > 0) { accountCategory From 1c1e5f0654304a8bcb7361f3092e822d9f953c66 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Fri, 9 Apr 2021 19:54:38 +0900 Subject: [PATCH 32/95] Null check. Fixes https://github.com/austinhuang0131/barinsta/issues/991 --- .../fragments/StoryListViewerFragment.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/fragments/StoryListViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/StoryListViewerFragment.java index ee997c86..73efea84 100644 --- a/app/src/main/java/awais/instagrabber/fragments/StoryListViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/StoryListViewerFragment.java @@ -24,6 +24,7 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import awais.instagrabber.R; @@ -98,8 +99,7 @@ public final class StoryListViewerFragment extends Fragment implements SwipeRefr final Context context = getContext(); Toast.makeText(context, R.string.empty_list, Toast.LENGTH_SHORT).show(); } catch (Exception ignored) {} - } - else { + } else { endCursor = result.getNextCursor(); final List models = archivesViewModel.getList().getValue(); final List modelsCopy = models == null ? new ArrayList<>() : new ArrayList<>(models); @@ -198,7 +198,13 @@ public final class StoryListViewerFragment extends Fragment implements SwipeRefr adapter = new FeedStoriesListAdapter(clickListener); binding.rvStories.setLayoutManager(layoutManager); binding.rvStories.setAdapter(adapter); - feedStoriesViewModel.getList().observe(getViewLifecycleOwner(), adapter::submitList); + feedStoriesViewModel.getList().observe(getViewLifecycleOwner(), list -> { + if (list == null) { + adapter.submitList(Collections.emptyList()); + return; + } + adapter.submitList(list); + }); } else { if (actionBar != null) actionBar.setTitle(R.string.action_archive); final RecyclerLazyLoader lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> { From 977ccce22c04693599ea45a06b94ff3a6950a677 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Fri, 9 Apr 2021 19:57:56 +0900 Subject: [PATCH 33/95] Null checks. Fixes https://github.com/austinhuang0131/barinsta/issues/990 --- .../directmessages/DirectMessageThreadFragment.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java index 57f17cf2..73bf0244 100644 --- a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java @@ -414,10 +414,14 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact case SUCCESS: Toast.makeText(context, R.string.marked_as_seen, Toast.LENGTH_SHORT).show(); case LOADING: - item.setEnabled(false); + if (item != null) { + item.setEnabled(false); + } break; case ERROR: - item.setEnabled(true); + if (item != null) { + item.setEnabled(true); + } if (resource.message != null) { Snackbar.make(context, binding.getRoot(), resource.message, Snackbar.LENGTH_LONG).show(); return; @@ -956,7 +960,9 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact } final DirectThread thread = threadLiveData.getValue(); if (thread == null) return; - markAsSeenMenuItem.setEnabled(!DMUtils.isRead(thread)); + if (markAsSeenMenuItem != null) { + markAsSeenMenuItem.setEnabled(!DMUtils.isRead(thread)); + } }); if (itemsAdapter == null) return; itemsAdapter.submitList(items, () -> { From 99431fd077776b1a8ac3d0ed9b6a37e298d9abe4 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Fri, 9 Apr 2021 20:05:18 +0900 Subject: [PATCH 34/95] Null checks. Fixes https://github.com/austinhuang0131/barinsta/issues/981 --- .../instagrabber/managers/ThreadManager.java | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/awais/instagrabber/managers/ThreadManager.java b/app/src/main/java/awais/instagrabber/managers/ThreadManager.java index c76728e3..5b84c97c 100644 --- a/app/src/main/java/awais/instagrabber/managers/ThreadManager.java +++ b/app/src/main/java/awais/instagrabber/managers/ThreadManager.java @@ -27,6 +27,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -564,6 +565,7 @@ public final class ThreadManager { private List addEmoji(final List reactionList, final String emoji, final boolean shouldReplaceIfAlreadyReacted) { + if (currentUser == null) return reactionList; final List temp = reactionList == null ? new ArrayList<>() : new ArrayList<>(reactionList); int index = -1; for (int i = 0; i < temp.size(); i++) { @@ -1311,6 +1313,7 @@ public final class ThreadManager { final MutableLiveData> data = new MutableLiveData<>(); final Call addUsersRequest = service.addUsers(threadId, users.stream() + .filter(Objects::nonNull) .map(User::getPk) .collect(Collectors.toList())); handleDetailsChangeRequest(data, addUsersRequest); @@ -1319,6 +1322,10 @@ public final class ThreadManager { public LiveData> removeMember(final User user) { final MutableLiveData> data = new MutableLiveData<>(); + if (user == null) { + data.postValue(Resource.error("user is null!", null)); + return data; + } final Call request = service.removeUsers(threadId, Collections.singleton(user.getPk())); request.enqueue(new Callback() { @Override @@ -1337,6 +1344,7 @@ public final class ThreadManager { leftUsersValue = Collections.emptyList(); } final List updatedActiveUsers = activeUsers.stream() + .filter(Objects::nonNull) .filter(u -> u.getPk() != user.getPk()) .collect(Collectors.toList()); final ImmutableList.Builder updatedLeftUsersBuilder = ImmutableList.builder().addAll(leftUsersValue); @@ -1357,12 +1365,14 @@ public final class ThreadManager { } public boolean isAdmin(final User user) { + if (user == null) return false; final List adminUserIdsValue = adminUserIds.getValue(); return adminUserIdsValue != null && adminUserIdsValue.contains(user.getPk()); } public LiveData> makeAdmin(final User user) { final MutableLiveData> data = new MutableLiveData<>(); + if (user == null) return data; if (isAdmin(user)) return data; final Call request = service.addAdmins(threadId, Collections.singleton(user.getPk())); request.enqueue(new Callback() { @@ -1399,6 +1409,7 @@ public final class ThreadManager { public LiveData> removeAdmin(final User user) { final MutableLiveData> data = new MutableLiveData<>(); + if (user == null) return data; if (!isAdmin(user)) return data; final Call request = service.removeAdmins(threadId, Collections.singleton(user.getPk())); request.enqueue(new Callback() { @@ -1411,6 +1422,7 @@ public final class ThreadManager { final List currentAdmins = adminUserIds.getValue(); if (currentAdmins == null) return; final List updatedAdminUserIds = currentAdmins.stream() + .filter(Objects::nonNull) .filter(userId1 -> userId1 != user.getPk()) .collect(Collectors.toList()); final DirectThread currentThread = ThreadManager.this.thread.getValue(); @@ -1583,6 +1595,7 @@ public final class ThreadManager { public LiveData> blockUser(final User user) { final MutableLiveData> data = new MutableLiveData<>(); + if (user == null) return data; friendshipService.block(user.getPk(), new ServiceCallback() { @Override public void onSuccess(final FriendshipChangeResponse result) { @@ -1600,6 +1613,7 @@ public final class ThreadManager { public LiveData> unblockUser(final User user) { final MutableLiveData> data = new MutableLiveData<>(); + if (user == null) return data; friendshipService.unblock(user.getPk(), new ServiceCallback() { @Override public void onSuccess(final FriendshipChangeResponse result) { @@ -1617,6 +1631,7 @@ public final class ThreadManager { public LiveData> restrictUser(final User user) { final MutableLiveData> data = new MutableLiveData<>(); + if (user == null) return data; friendshipService.toggleRestrict(user.getPk(), true, new ServiceCallback() { @Override public void onSuccess(final FriendshipRestrictResponse result) { @@ -1634,6 +1649,7 @@ public final class ThreadManager { public LiveData> unRestrictUser(final User user) { final MutableLiveData> data = new MutableLiveData<>(); + if (user == null) return data; friendshipService.toggleRestrict(user.getPk(), false, new ServiceCallback() { @Override public void onSuccess(final FriendshipRestrictResponse result) { @@ -1654,7 +1670,10 @@ public final class ThreadManager { data.postValue(Resource.loading(null)); final Call approveUsersRequest = service .approveParticipantRequests(threadId, - users.stream().map(User::getPk).collect(Collectors.toList())); + users.stream() + .filter(Objects::nonNull) + .map(User::getPk) + .collect(Collectors.toList())); handleDetailsChangeRequest(data, approveUsersRequest, () -> pendingUserApproveDenySuccessAction(users)); return data; } @@ -1811,6 +1830,7 @@ public final class ThreadManager { @Override public void onResponse(@NonNull final Call call, @NonNull final Response response) { + if (currentUser == null) return; if (!response.isSuccessful()) { handleErrorBody(call, response, data); return; From 23a5ae84055348f0cc4cf3ba4e066201ffbb5d70 Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Fri, 9 Apr 2021 09:06:53 -0400 Subject: [PATCH 35/95] fix picture zooming issue Co-Authored-By: Ammar Githam --- .../java/awais/instagrabber/fragments/PostViewV2Fragment.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java b/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java index 0d0c7865..58caad5a 100644 --- a/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java @@ -1022,6 +1022,8 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment im // binding.postImage.setOnClickListener(v -> toggleDetails()); final AnimatedZoomableController zoomableController = AnimatedZoomableController.newInstance(); zoomableController.setMaxScaleFactor(3f); + zoomableController.setGestureZoomEnabled(true); + zoomableController.setEnabled(true); binding.postImage.setZoomableController(zoomableController); binding.postImage.setTapListener(new GestureDetector.SimpleOnGestureListener() { @Override From 84d82e92d395881db0d70497f6f303159ae4e81e Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Sun, 11 Apr 2021 09:45:38 -0400 Subject: [PATCH 36/95] made play in background an option per https://redd.it/mmpeg9 --- .../fragments/PostViewV2Fragment.java | 1 + .../settings/PostPreferencesFragment.java | 16 +++++++++++++--- .../responses/directmessages/DirectItem.java | 1 - .../java/awais/instagrabber/utils/Constants.java | 1 + .../awais/instagrabber/utils/SettingsHelper.java | 3 ++- app/src/main/res/values/strings.xml | 2 ++ 6 files changed, 19 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java b/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java index 58caad5a..2232a927 100644 --- a/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java @@ -334,6 +334,7 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment im if (bottomSheetBehavior != null) { captionState = bottomSheetBehavior.getState(); } + if (settingsHelper.getBoolean(Constants.PLAY_IN_BACKGROUND)) return; final Media media = viewModel.getMedia(); if (media == null) return; switch (media.getMediaType()) { diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/PostPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/PostPreferencesFragment.java index 14daec7f..492f574c 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/PostPreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/PostPreferencesFragment.java @@ -17,16 +17,26 @@ public class PostPreferencesFragment extends BasePreferencesFragment { final Context context = getContext(); if (context == null) return; // generalCategory.addPreference(getAutoPlayVideosPreference(context)); + screen.addPreference(getBackgroundPlayPreference(context)); screen.addPreference(getAlwaysMuteVideosPreference(context)); screen.addPreference(getShowCaptionPreference(context)); screen.addPreference(getToggleKeywordFilterPreference(context)); screen.addPreference(getEditKeywordFilterPreference(context)); } - private Preference getAutoPlayVideosPreference(@NonNull final Context context) { +// private Preference getAutoPlayVideosPreference(@NonNull final Context context) { +// final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context); +// preference.setKey(Constants.AUTOPLAY_VIDEOS); +// preference.setTitle(R.string.post_viewer_autoplay_video); +// preference.setIconSpaceReserved(false); +// return preference; +// } + + private Preference getBackgroundPlayPreference(@NonNull final Context context) { final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context); - preference.setKey(Constants.AUTOPLAY_VIDEOS); - preference.setTitle(R.string.post_viewer_autoplay_video); + preference.setKey(Constants.PLAY_IN_BACKGROUND); + preference.setTitle(R.string.post_viewer_background_play); + preference.setTitle(R.string.post_viewer_background_play_summary); preference.setIconSpaceReserved(false); return preference; } diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItem.java b/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItem.java index 52ad8818..221afd79 100644 --- a/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItem.java +++ b/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItem.java @@ -229,7 +229,6 @@ public class DirectItem implements Cloneable, Serializable { public LocalDateTime getLocalDateTime() { if (localDateTime == null) { localDateTime = Instant.ofEpochMilli(timestamp / 1000).atZone(ZoneId.systemDefault()).toLocalDateTime(); - ; } return localDateTime; } diff --git a/app/src/main/java/awais/instagrabber/utils/Constants.java b/app/src/main/java/awais/instagrabber/utils/Constants.java index a2a5fd13..9f79641e 100644 --- a/app/src/main/java/awais/instagrabber/utils/Constants.java +++ b/app/src/main/java/awais/instagrabber/utils/Constants.java @@ -21,6 +21,7 @@ public final class Constants { 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"; + public static final String PLAY_IN_BACKGROUND = "play_in_background"; // 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"; diff --git a/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java b/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java index fc568d9e..659070a9 100755 --- a/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java +++ b/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java @@ -43,6 +43,7 @@ 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; +import static awais.instagrabber.utils.Constants.PLAY_IN_BACKGROUND; import static awais.instagrabber.utils.Constants.PREF_DARK_THEME; import static awais.instagrabber.utils.Constants.PREF_EMOJI_VARIANTS; import static awais.instagrabber.utils.Constants.PREF_HASHTAG_POSTS_LAYOUT; @@ -164,7 +165,7 @@ public final class SettingsHelper { @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, HIDE_MUTED_REELS}) + FLAG_SECURE, TOGGLE_KEYWORD_FILTER, PREF_ENABLE_SENTRY, HIDE_MUTED_REELS, PLAY_IN_BACKGROUND}) public @interface BooleanSettings {} @StringDef({PREV_INSTALL_VERSION, BROWSER_UA_CODE, APP_UA_CODE, PREF_ENABLE_DM_AUTO_REFRESH_FREQ_NUMBER}) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9cf27407..9e67c16d 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -59,6 +59,8 @@ %s\nFollowing Autoplay videos + Continue to play videos in background + When app is not in focus Always mute videos Always show post captions Select what to download From d326d9e2d79d42c3152de88e9340d0634a7b20c0 Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Sun, 11 Apr 2021 09:54:25 -0400 Subject: [PATCH 37/95] visual optimizations for the previous commit --- .../fragments/settings/PostPreferencesFragment.java | 2 +- app/src/main/res/values/strings.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/PostPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/PostPreferencesFragment.java index 492f574c..c79d009b 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/PostPreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/PostPreferencesFragment.java @@ -36,7 +36,7 @@ public class PostPreferencesFragment extends BasePreferencesFragment { final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context); preference.setKey(Constants.PLAY_IN_BACKGROUND); preference.setTitle(R.string.post_viewer_background_play); - preference.setTitle(R.string.post_viewer_background_play_summary); + preference.setSummary(R.string.post_viewer_background_play_summary); preference.setIconSpaceReserved(false); return preference; } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9e67c16d..9f3010d6 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -59,8 +59,8 @@ %s\nFollowing Autoplay videos - Continue to play videos in background - When app is not in focus + Continue videos in background + Do not pause videos when the app is out of focus Always mute videos Always show post captions Select what to download From d8d47befab55893545ddcf8071a88922ce306ec0 Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Sun, 11 Apr 2021 10:07:28 -0400 Subject: [PATCH 38/95] favorite tab for anons --- .../instagrabber/activities/MainActivity.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/activities/MainActivity.java b/app/src/main/java/awais/instagrabber/activities/MainActivity.java index 8882bed9..bf36e002 100644 --- a/app/src/main/java/awais/instagrabber/activities/MainActivity.java +++ b/app/src/main/java/awais/instagrabber/activities/MainActivity.java @@ -564,6 +564,13 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage private List setupAnonBottomNav() { final int selectedItemId = binding.bottomNavView.getSelectedItemId(); + final Tab favoriteTab = new Tab(R.drawable.ic_star_24, + getString(R.string.title_favorites), + false, + "favorites_nav_graph", + R.navigation.favorites_nav_graph, + R.id.favorites_nav_graph, + R.id.favoritesFragment); final Tab profileTab = new Tab(R.drawable.ic_person_24, getString(R.string.profile), false, @@ -580,12 +587,15 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage R.id.morePreferencesFragment); final Menu menu = binding.bottomNavView.getMenu(); menu.clear(); + menu.add(0, favoriteTab.getNavigationRootId(), 0, favoriteTab.getTitle()).setIcon(favoriteTab.getIconResId()); menu.add(0, profileTab.getNavigationRootId(), 0, profileTab.getTitle()).setIcon(profileTab.getIconResId()); menu.add(0, moreTab.getNavigationRootId(), 0, moreTab.getTitle()).setIcon(moreTab.getIconResId()); - if (selectedItemId != R.id.profile_nav_graph && selectedItemId != R.id.more_nav_graph) { + if (selectedItemId != R.id.profile_nav_graph + && selectedItemId != R.id.more_nav_graph + && selectedItemId != R.id.favorites_nav_graph) { setBottomNavSelectedTab(profileTab); } - return ImmutableList.of(profileTab, moreTab); + return ImmutableList.of(favoriteTab, profileTab, moreTab); } private List setupMainBottomNav() { From 14ed5e936429cf64c94bb791cd925c746b4f7b91 Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Sun, 11 Apr 2021 10:11:40 -0400 Subject: [PATCH 39/95] release prep --- app/build.gradle | 4 ++-- fastlane/metadata/android/en-US/changelogs/62.txt | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/62.txt diff --git a/app/build.gradle b/app/build.gradle index 871f8cc5..51300156 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -20,8 +20,8 @@ android { minSdkVersion 21 targetSdkVersion 29 - versionCode 61 - versionName '19.2.0' + versionCode 62 + versionName '19.2.1' multiDexEnabled true diff --git a/fastlane/metadata/android/en-US/changelogs/62.txt b/fastlane/metadata/android/en-US/changelogs/62.txt new file mode 100644 index 00000000..f227ef0f --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/62.txt @@ -0,0 +1,3 @@ +Option to play video in background, favorites tab for anonymous users, and bug fixes. + +For details see https://github.com/austinhuang0131/barinsta/releases/tag/v19.2.1 \ No newline at end of file From 6328a66fe4e472b8963fc903e6e87cfd7b11078b Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Sun, 11 Apr 2021 11:28:49 -0400 Subject: [PATCH 40/95] New Crowdin updates (#962) * New translations strings.xml (Japanese) * New translations strings.xml (Spanish) * New translations strings.xml (Italian) * New translations strings.xml (French) * New translations strings.xml (Italian) * New translations strings.xml (Polish) * New translations strings.xml (Polish) * New translations strings.xml (Russian) * New translations strings.xml (Russian) * New translations strings.xml (Greek) * New translations strings.xml (Greek) * New translations strings.xml (Greek) * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Greek) * New translations strings.xml (Greek) * New translations arrays.xml (Greek) * New translations strings.xml (Greek) * New translations strings.xml (Greek) * New translations strings.xml (Spanish) * New translations strings.xml (Russian) * New translations strings.xml (Slovak) * New translations strings.xml (Turkish) * New translations strings.xml (Chinese Simplified) * New translations strings.xml (Chinese Traditional) * New translations strings.xml (Dutch) * New translations strings.xml (Vietnamese) * New translations strings.xml (Indonesian) * New translations strings.xml (Hindi) * New translations strings.xml (Persian) * New translations strings.xml (Basque) * New translations strings.xml (Polish) * New translations strings.xml (Portuguese, Brazilian) * New translations strings.xml (Catalan) * New translations strings.xml (Greek) * New translations strings.xml (French) * New translations strings.xml (Macedonian) * New translations strings.xml (Czech) * New translations strings.xml (German) * New translations strings.xml (Italian) * New translations strings.xml (Japanese) * New translations strings.xml (Odia) * Update source file strings.xml * New translations strings.xml (Chinese Simplified) --- app/src/github/res/values-pl/strings.xml | 2 +- app/src/main/res/values-ca/strings.xml | 2 + app/src/main/res/values-cs/strings.xml | 2 + app/src/main/res/values-de/strings.xml | 2 + app/src/main/res/values-el/arrays.xml | 4 +- app/src/main/res/values-el/strings.xml | 54 +++++++++++----------- app/src/main/res/values-es/strings.xml | 16 ++++--- app/src/main/res/values-eu/strings.xml | 2 + app/src/main/res/values-fa/strings.xml | 2 + app/src/main/res/values-fr/strings.xml | 4 +- app/src/main/res/values-hi/strings.xml | 2 + app/src/main/res/values-in/strings.xml | 2 + app/src/main/res/values-it/strings.xml | 18 ++++---- app/src/main/res/values-ja/strings.xml | 4 +- app/src/main/res/values-mk/strings.xml | 2 + app/src/main/res/values-nl/strings.xml | 2 + app/src/main/res/values-or/strings.xml | 2 + app/src/main/res/values-pl/strings.xml | 16 ++++--- app/src/main/res/values-pt/strings.xml | 2 + app/src/main/res/values-ru/strings.xml | 34 +++++++------- app/src/main/res/values-sk/strings.xml | 2 + app/src/main/res/values-tr/strings.xml | 2 + app/src/main/res/values-vi/strings.xml | 2 + app/src/main/res/values-zh-rCN/strings.xml | 20 ++++---- app/src/main/res/values-zh-rTW/strings.xml | 2 + 25 files changed, 124 insertions(+), 78 deletions(-) diff --git a/app/src/github/res/values-pl/strings.xml b/app/src/github/res/values-pl/strings.xml index 481f46c5..08b8c67e 100644 --- a/app/src/github/res/values-pl/strings.xml +++ b/app/src/github/res/values-pl/strings.xml @@ -1,6 +1,6 @@ Włącz Sentry - Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io + Sentry jest słuchaczem/obsługą błędów, które asynchronicznie wysyłają błąd/zdarzenie do Sentry.io Sentry rozpocznie się przy następnym uruchomieniu diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 93a5ef0d..454747ed 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -56,6 +56,8 @@ %s\n Seguint Reproduir vídeos automàticament + Continue videos in background + Do not pause videos when the app is out of focus Silenciar sempre els vídeos Mostrar sempre els subtítols Seleccionar el que s\'ha de descarregar diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 9d444cf3..e82b94f7 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -62,6 +62,8 @@ %s\nSleduje Videa spouštět automaticky + Continue videos in background + Do not pause videos when the app is out of focus Vždy ztlumit videa Vždy zobrazovat titulek příspěvku Vyberte, co stáhnout diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 50539525..1957b57e 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -56,6 +56,8 @@ %s\nAbonniert Videos automatisch abspielen + Continue videos in background + Do not pause videos when the app is out of focus Videos immer stummschalten Bildtext immer anzeigen Datei zum Download auswählen diff --git a/app/src/main/res/values-el/arrays.xml b/app/src/main/res/values-el/arrays.xml index 77f71e18..b9f46308 100644 --- a/app/src/main/res/values-el/arrays.xml +++ b/app/src/main/res/values-el/arrays.xml @@ -39,8 +39,8 @@ Κανένα \@ - στο - πάνω + στις + στις \| - diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 3cc70379..9597947f 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -23,16 +23,16 @@ Έλεγχος για ενημερώσεις κατά την εκκίνηση Παρεμπόδιση στιγμιοτύπων οθόνης & προεπισκόπησης εφαρμογής Λήψη δημοσιεύσεων σε φακέλους ονομάτων χρηστών - Prepend Username to Filename + Προσθέστε το όνομα χρήστη πριν από το όνομα του αρχείου Επισήμανση ιστοριών ως προβληθέντων μετά την προβολή Ο δημιουργός της ιστορίας θα γνωρίζει ότι προβλήθηκε - Hide muted stories from feed + Απόκρυψη ιστοριών που βρίσκονται σε σίγαση από τη ροή Επισήμανση μηνυμάτων ως αναγνωσμένων μετά την προβολή Τα υπόλοιπα μέλη θα γνωρίζουν ότι προβλήθηκε Ενεργοποίηση ειδοποιήσεων δραστηριότητας Ταξινόμηση ροής ιστορίων Σφάλμα κατά τη φόρτωση προφίλ! Είναι το όνομα χρήστη έγκυρο; Αν ναι, μπορεί να είστε περιορισμένος. - Σφάλμα κατά τη φόρτωση λογαριασμού! Είναι το όνομα χρήστη έγκυρο; Μήπως σας έχει μπλοκάρει; + Σφάλμα κατά τη φόρτωση λογαριασμού! Είναι το όνομα χρήστη έγκυρο; Μήπως ο χρήστης σας έχει μπλοκάρει; Σφάλμα κατά τη φόρτωση hashtag! Είναι το όνομα έγκυρο; Σφάλμα κατά την φόρτωση τοποθεσίας! Είναι η διεύθυνση έγκυρη; Σφάλμα κατά τη δημιουργία φακέλου/-ων λήψης. @@ -56,6 +56,8 @@ %s\nΑκολουθείτε Αυτόματη αναπαραγωγή των βίντεο + Continue videos in background + Do not pause videos when the app is out of focus Μόνιμη σίγαση των βίντεο Μόνιμη εμφάνιση των λεζαντών των δημοσιεύσεων Επιλογή δημοσιεύσεων για λήψη @@ -93,7 +95,7 @@ Αποσύνδεση Ανώνυμη περιήγηση στο Instagram Αφαίρεση όλων των λογαριασμών - Αυτό θα αφαιρέσει όλους τους λογαριασμούς που έχουν προστεθεί στην εφαρμογή!\nΓια να αφαιρέσετε μόνο έναν λογαριασμό, πατήστε παρατεταμένα τον λογαριασμό από τον διάλογο εναλλαγής λογαριασμών.\nΘέλετε να συνεχίσετε; + Έτσι, θα αφαιρεθούν όλοι οι λογαριασμοί που έχουν προστεθεί στην εφαρμογή!\nΓια να αφαιρέσετε μόνο έναν λογαριασμό, πατήστε τον παρατεταμένα από τον διάλογο εναλλαγής λογαριασμών.\nΘέλετε να συνεχίσετε; Μορφή ημερομηνίας Δημιουργία νέας συλλογής Επεξεργασία ονόματος συλλογής @@ -102,9 +104,9 @@ Όλα τα πολυμέσα που περιέχονται θα παραμείνουν σε άλλες συλλογές. Προσθήκη στη συλλογή... Αφαίρεση από τη συλλογή - Επισημασμένο ως \"Μου αρέσει\" + Μ\'αρέσουν Αποθηκευμένα - Σε αυτήν τη φωτογραφία + Ετικέτες Μήνυμα Μου αρέσει Δε μου αρέσει @@ -240,7 +242,7 @@ Δραστηριότητα Αρχειοθήκη ιστοριών Προτεινόμενοι χρήστες - Επιλέξτε Εικόνα + Επιλογή εικόνας Μεταφόρτωση… Έχετε: %d ακόλουθοι @@ -255,7 +257,7 @@ Προφίλ Λοιπά Μηνύματα - %d επιλέχθηκε + Επιλέχθηκαν %d Η αποσύνδεση ήταν επιτυχής! Πληροφορίες Σήμανση ως αναγνωσμένο @@ -283,7 +285,7 @@ Φωτεινό θέμα Σκούρο θέμα Κόκκοι καφέ - Ουσιώδες Σκούρο + Σκουρόχρωμο Προστέθηκε στα Αγαπημένα! Στα αγαπημένα Λογαριασμοί @@ -292,7 +294,7 @@ Άγνωστο Αφαίρεση από τα αγαπημένα! Αντίγραφα ασφαλείας & Επαναφορά - Δημιουργία αντιγράφου ασφαλείας των ρυθμίσεων της εφαρμογής, των δεδομένων σύνδεσης λογαριασμού και/ή αγαπημένα σε απλό κείμενο ή κρυπτογραφημένο αρχείο για μεταγενέστερη επαναφορά. + Δημιουργία αντιγράφου ασφαλείας των ρυθμίσεων της εφαρμογής, των δεδομένων σύνδεσης του λογαριασμού και/ή των αγαπημένων, σε ακρυπτογράφητο ή κρυπτογραφημένο αρχείο για μεταγενέστερη επαναφορά. Αν δημιουργείτε αντίγραφα ασφαλείας των δεδομένων σύνδεσης λογαριασμού, αντιμετωπίστε το αρχείο ως απόρρητο και κρατήστε το σε ασφαλές μέρος! Δημιουργία νέου αρχείου αντιγράφου ασφαλείας Επαναφορά από υπάρχον αρχείο αντιγράφου ασφαλείας @@ -327,8 +329,8 @@ 2 3 Εμφάνιση ονομάτων - Εμφάνιση άβαταρ - Μέγεθος άβαταρ + Εμφάνιση εικόνας προφίλ + Μέγεθος εικόνας προφίλ Γωνίες Εμφάνιση κενού πλέγματος Απενεργοποίηση κινουμένων σχεδίων @@ -461,22 +463,22 @@ Αφαιρέθηκε λέξη-κλειδί: %s στον κατάλογο φιλτραρίσματος Επισήμανθηκε ως αναγνωσμένο Η διαγραφή απέτυχε - Throttled by Instagram because of too many API requests. Wait for some time before retrying. - Error - This account has been logged out. - Login required! - Sentry block. - User is inactive! + Περιορίστηκατε από το Instagram λόγω υπερβολικών αιτήσεων API. Περιμένετε ορισμένη ώρα προτού προσπαθήσετε ξανά. + Σφάλμα + Αυτός ο λογαριασμός έχει αποσυνδεθεί. + Απαιτείται σύνδεση! + Φραγή Sentry. + Ο χρήστης είναι ανενεργός! Αναφορά Κατάρρευσης Barinsta Επιλέξτε μια εφαρμογή ηλ. ταχυδρομείου για αποστολή αρχείων καταγραφής κατάρρευσης - Not found! - Your IP has been rate limited by Instagram. Wait for an hour and try again. - Skip this update - You\'re already on the latest version - Screen order - Other tabs - The tab order will be reflected on next launch - If saved, all DM related features will be disabled on next launch + Δε βρέθηκε! + Η διεύθυνση IP σας έχει περιοριστεί από το Instagram. Περιμένετε για μία ώρα και μετά προσπαθήστε ξανά. + Παράλειψη της ενημέρωσης + Η εφαρμογή είναι ήδη στην τελευταία έκδοση + Σειρά της οθόνης + Λοιπές καρτέλες + Η σειρά των καρτελών θα ισχύσει από την επόμενη εκκίνηση + Εάν αποθηκευτεί, όλες οι λειτουργίες που είναι σχετικές με τα Μηνύματα, θα είναι απενεργοποιημένες στην επόμενη εκκίνηση Αντιγραφή λεζάντας Αντιγραφή απάντησης diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 188dff0c..46f07e79 100755 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -56,6 +56,8 @@ %s\nSiguiendo Autorreproducir vídeos + Continue videos in background + Do not pause videos when the app is out of focus Siempre silenciar vídeos Mostrar siempre subtítulos del post Seleccionar qué descargar @@ -461,16 +463,16 @@ Se eliminó la palabra clave: %s de la lista de filtros Marcado como visto Eliminación fallida - Throttled by Instagram because of too many API requests. Wait for some time before retrying. + Restringido por Instagram por hacer demasiadas solicitudes de API. Espera un tiempo antes de reintentar. Error - This account has been logged out. - Login required! - Sentry block. - User is inactive! + Esta cuenta ha sido desconectada. + ¡Inicio de sesión requerido! + Bloqueo de Sentry. + ¡Usuario inactivo! Informe de fallos de Barinsta Seleccione una aplicación de correo electrónico para enviar registros de errores - Not found! - Your IP has been rate limited by Instagram. Wait for an hour and try again. + ¡No encontrado! + Tu IP ha sido limitada por Instagram. Espera una hora e inténtalo de nuevo. Omitir esta actualización Ya tienes la última versión Orden de pantalla diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 1d26e03d..bf364629 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -56,6 +56,8 @@ %s\nJarraituak Erreproduzitu bideoak automatikoki + Continue videos in background + Do not pause videos when the app is out of focus Mututu bideoak beti Erakutsi argazki-oina beti Hautatu zer deskargatu diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 38d8f503..e9778af9 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -56,6 +56,8 @@ %s\nدنبال کننده ها پخش خودکار فیلم ها + Continue videos in background + Do not pause videos when the app is out of focus همیشه فیلم هارو بی صدا کن Always show post captions انتخاب کن چی دانلود کنی diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 0f61d784..cd7e6a88 100755 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -56,6 +56,8 @@ %s\nAbonnements Lecture automatique des vidéos + Continue videos in background + Do not pause videos when the app is out of focus Toujours couper le son des vidéos Toujours afficher les sous-titres de publication Sélectionnez ce que vous souhaitez télécharger @@ -461,7 +463,7 @@ Mot-clé supprimé : %s de la liste de filtres Marqué comme vu Suppression non réussie - Propulsé par Instagram à cause d\'un trop grand nombre de requêtes API. Attendez un certain temps avant de réessayer. + Limité par Instagram en raison d\'un trop grand nombre de requêtes API. Attendez un certain temps avant de réessayer. Erreur Ce compte a été déconnecté. Connexion requise! diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index a7cc2d40..4d12088d 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -56,6 +56,8 @@ %s\nFollowing वीडियो ऑटोप्ले करें + Continue videos in background + Do not pause videos when the app is out of focus सर्बदा वीडियो को शब्दहिन रखें Always show post captions डाउनलोड करने के लिए चयन करें diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 3d9aeecb..ff81bfd5 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -53,6 +53,8 @@ %s\ndiikuti Otomatis putar video + Continue videos in background + Do not pause videos when the app is out of focus Selalu bisukan video Selalu tampilkan keterangan kiriman Pilih apa yang akan diunduh diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 0aeb0239..f2eb3241 100755 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -56,6 +56,8 @@ %s\nSeguiti Riproduzione automatica video + Continue videos in background + Do not pause videos when the app is out of focus Silenzia sempre i video Mostra sempre le didascalie dei post Seleziona cosa scaricare @@ -461,16 +463,16 @@ Parola chiave rimossa: %s dalla lista filtri Segnato come visto Eliminazione non riuscita - Throttled by Instagram because of too many API requests. Wait for some time before retrying. - Error - This account has been logged out. - Login required! - Sentry block. - User is inactive! + Limitato da Instagram a causa delle troppe richieste API. Aspetta un po\' prima di riprovare. + Errore + Questo account è stato disconnesso. + Login richiesto! + Blocco sentinella. + Utente è inattivo! Rapporto sugli errori di Barinsta Selezionare un\'applicazione di posta elettronica per inviare i registri di errori - Not found! - Your IP has been rate limited by Instagram. Wait for an hour and try again. + Non trovato! + Il tuo IP è stato limitato da Instagram. Aspetta un\'ora e riprova. Salta questo aggiornamento Hai già l\'ultima versione Ordine schermata diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 6818078e..ee26cad5 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -53,6 +53,8 @@ %s\nフォロー中 動画を自動再生する + Continue videos in background + Do not pause videos when the app is out of focus 動画を常にミュートする キャプションを常に表示 ダウンロード対象を選択 @@ -299,7 +301,7 @@ 保存 キャプション Edit caption - Translate caption + 翻訳を見る ビデオプレーヤーのタイムライン Liking… いいね!に失敗しました diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index c234ef9d..6d7c8e57 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -56,6 +56,8 @@ %s\nСледбеници Autoplay на видеа + Continue videos in background + Do not pause videos when the app is out of focus Секогаш гледај видеа без звук Секогаш прикажувај наслов Одбери што сакаш да превземеш diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 1a5b56bb..b2084841 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -56,6 +56,8 @@ %s\nVolgend Video\'s automatisch afspelen + Continue videos in background + Do not pause videos when the app is out of focus Video\'s altijd dempen Always show post captions Selecteer wat je wil downloaden diff --git a/app/src/main/res/values-or/strings.xml b/app/src/main/res/values-or/strings.xml index 2ad01552..36644df4 100644 --- a/app/src/main/res/values-or/strings.xml +++ b/app/src/main/res/values-or/strings.xml @@ -56,6 +56,8 @@ %s\nଅନୁସରଣ କରୁଛନ୍ତି ଭିଡ଼ିଓ ସ୍ୱତଃ ଚାଲୁ କର + Continue videos in background + Do not pause videos when the app is out of focus ସର୍ବଦା ଭିଡ଼ିଓକୁ ଶବ୍ଦହୀନ ରଖ Always show post captions ଡାଉନଲୋଡ଼ କରିବା ପାଇଁ ଚୟନ କରନ୍ତୁ diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index b3962453..9fd78fc2 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -23,7 +23,7 @@ Sprawdź aktualizacje przy starcie Blokuj zrzuty ekranu & podgląd aplikacji Pobierz posty do folderów o nazwie użytkownika - Prepend Username to Filename + Dodaj nazwę użytkownika do nazwy pliku Oznacz relacje jako widoczne po wyświetleniu Autor relacji będzie widział, że to wyświetliłeś Ukryj wyciszone relacje z kanału @@ -62,6 +62,8 @@ %s\nobserwowanych Automatyczne odtwarzanie filmów + Continue videos in background + Do not pause videos when the app is out of focus Zawsze wyciszaj filmy Zawsze pokazuj napisy postów Wybierz, co chcesz pobrać @@ -478,15 +480,15 @@ Oznacz jako przeczytane Usuwanie nie powiodło się Throttled by Instagram because of too many API requests. Wait for some time before retrying. - Error - This account has been logged out. - Login required! + Błąd + To konto zostało wylogowane. + Wymagane logowanie! Sentry block. - User is inactive! + Użytkownik jest nieaktywny! Raport awarii Barinsta Wybierz aplikację e-mail do wysyłania dzienników awarii - Not found! - Your IP has been rate limited by Instagram. Wait for an hour and try again. + Nie znaleziono! + Twój adres IP został ograniczony przez Instagram. Poczekaj godzinę i spróbuj ponownie. Pomiń tę aktualizację Posiadasz aktualną wersję Kolejność na ekranie diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 57164840..f58f643a 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -56,6 +56,8 @@ %s\nSeguindo Reprodução automática de vídeos + Continue videos in background + Do not pause videos when the app is out of focus Sempre silenciar vídeos Sempre mostrar as legendas das publicações Selecionar o que baixar diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 6f76baed..61deddbb 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -23,10 +23,10 @@ Проверять наличие обновлений при запуске Блокировать скриншоты & превью приложения Скачать публикации в папки с именем пользователя - Prepend Username to Filename + Добавить имя пользователя к имени файла Отметить истории как увиденные после просмотра Автор истории узнает, что вы просмотрели её - Hide muted stories from feed + Скрыть заглушённые истории из ленты новостей Отметить ЛС как увиденные после просмотра Другие участники узнают, что вы просмотрели его Включить уведомления об активности @@ -62,6 +62,8 @@ %s\nПоследователей Автовоспроизведение видео + Continue videos in background + Do not pause videos when the app is out of focus Всегда заглушать видео Всегда отображать подписи к постам Выберите, что скачивать @@ -477,22 +479,22 @@ Удалено ключевое слово: %s из списка фильтров Отмечено как просмотренное Не удалось удалить - Throttled by Instagram because of too many API requests. Wait for some time before retrying. - Error - This account has been logged out. - Login required! - Sentry block. - User is inactive! + Замято Instagram\'ом из-за слишком большого количества запросов API. Подождите некоторое время перед повторной попыткой. + Ошибка + Эта учётная запись вышла из системы. + Требуется вход в систему! + Блокировка \"часового\". + Пользователь неактивен! Barinsta Crash Report Выберите приложение для отправки логов ошибки - Not found! - Your IP has been rate limited by Instagram. Wait for an hour and try again. - Skip this update - You\'re already on the latest version - Screen order - Other tabs - The tab order will be reflected on next launch - If saved, all DM related features will be disabled on next launch + Не найдено! + Ваш IP-адрес был ограничен Instagram. Подождите час и повторите попытку. + Пропустить это обновление + Вы уже используете последнюю версию + Порядок экрана + Другие вкладки + Порядок вкладок будет отражён при следующем запуске + При сохранении, все функции, связанные с ЛС, будут отключены при следующем запуске Копировать подпись Копировать ответ diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 3b91ad3a..f3e3ecc6 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -62,6 +62,8 @@ %s\nSleduje Automaticky prehrávať videá + Continue videos in background + Do not pause videos when the app is out of focus Vždy stíšiť videá Vždy zobraziť popis príspevku Vybrať čo stiahnuť diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 02bbdef6..3094c6f6 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -56,6 +56,8 @@ %s\nTakip Videoları otomatik oynat + Continue videos in background + Do not pause videos when the app is out of focus Videoları her zaman sustur Gönderi başlıklarını her zaman göster İndirmek için seçin diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index c8d69477..c35577d3 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -53,6 +53,8 @@ %s\nĐang theo dõi Tự động phát video + Continue videos in background + Do not pause videos when the app is out of focus Luôn luôn tắt âm thanh video Luôn hiển thị tiêu đề của bài viết Chọn mục tải xuống diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index e7987f20..8218ac05 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -53,9 +53,11 @@ %s\n已关注 自动播放视频 + 在后台继续播放视频 + 隐藏应用画面(锁屏、切换应用…)时不暂停视频 视频默认静音 总是显示帖子标题 - 选择要下载的 + 选择要下载的内容 当前照片 整个图集 显示快拍 @@ -453,16 +455,16 @@ 已移除关键词: %s 至过滤列表 已标记为已读 删除失败 - Throttled by Instagram because of too many API requests. Wait for some time before retrying. - Error - This account has been logged out. - Login required! - Sentry block. - User is inactive! + 您发送的API请求过多。请等待一段时间后重试。 + 错误 + 您已经退出。 + 需要登录! + Sentry 故障。 + 账户已冻结! Barinsta 崩溃报告 选择一个电子邮件应用来发送崩溃日志 - Not found! - Your IP has been rate limited by Instagram. Wait for an hour and try again. + 未找到所要的内容! + 您的IP地址已被 Instagram 设限。请等待一个小时后重试。 跳过本次更新 您现在安装的是最新版本 页面顺序 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index e7a4ea57..e5774194 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -53,6 +53,8 @@ %s\n追蹤中 自動播放影片 + Continue videos in background + Do not pause videos when the app is out of focus 永遠自動靜音影片 Always show post captions 選擇要下載的內容 From c859669ac1c585f6e40f49010bbb97f4b3fef5b7 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Wed, 14 Apr 2021 00:17:23 +0900 Subject: [PATCH 41/95] Add search fragment with recent searches --- app/build.gradle | 20 + .../awais.instagrabber.db.AppDatabase/6.json | 227 +++++++++++ .../awais/instagrabber/db/MigrationTest.java | 51 +++ .../db/dao/RecentSearchDaoTest.java | 82 ++++ .../instagrabber/activities/MainActivity.java | 310 +++++---------- .../adapters/FavoritesAdapter.java | 4 +- .../adapters/SearchCategoryAdapter.java | 33 ++ .../adapters/SearchItemsAdapter.java | 215 +++++++++++ .../adapters/SuggestionsAdapter.java | 77 ---- .../viewholder/FavoriteViewHolder.java | 20 +- .../viewholder/SearchItemViewHolder.java | 80 ++++ .../awais/instagrabber/db/AppDatabase.java | 25 +- .../instagrabber/db/dao/RecentSearchDao.java | 37 ++ .../datasources/RecentSearchDataSource.java | 57 +++ .../db/entities/RecentSearch.java | 185 +++++++++ .../repositories/RecentSearchRepository.java | 124 ++++++ .../fragments/FavoritesFragment.java | 4 +- .../search/SearchCategoryFragment.java | 195 ++++++++++ .../fragments/search/SearchFragment.java | 245 ++++++++++++ .../models/enums/FavoriteType.java | 3 +- .../repositories/responses/Hashtag.java | 18 + .../repositories/responses/Place.java | 19 + .../responses/search/SearchItem.java | 236 ++++++++++++ .../viewmodels/AppStateViewModel.java | 9 +- .../viewmodels/SearchFragmentViewModel.java | 352 ++++++++++++++++++ app/src/main/res/layout/activity_main.xml | 24 +- .../main/res/layout/fragment_favorites.xml | 2 +- app/src/main/res/layout/fragment_search.xml | 18 + ..._suggestion.xml => item_search_result.xml} | 41 +- app/src/main/res/menu/main_menu.xml | 19 +- .../navigation/direct_messages_nav_graph.xml | 10 + .../res/navigation/discover_nav_graph.xml | 9 + .../res/navigation/favorites_nav_graph.xml | 13 +- .../main/res/navigation/feed_nav_graph.xml | 10 +- .../main/res/navigation/hashtag_nav_graph.xml | 9 + .../res/navigation/location_nav_graph.xml | 9 + .../main/res/navigation/profile_nav_graph.xml | 37 +- app/src/main/res/values/strings.xml | 3 + app/src/main/res/values/styles.xml | 9 + 39 files changed, 2455 insertions(+), 386 deletions(-) create mode 100644 app/schemas/awais.instagrabber.db.AppDatabase/6.json create mode 100644 app/src/androidTest/java/awais/instagrabber/db/MigrationTest.java create mode 100644 app/src/androidTest/java/awais/instagrabber/db/dao/RecentSearchDaoTest.java create mode 100644 app/src/main/java/awais/instagrabber/adapters/SearchCategoryAdapter.java create mode 100644 app/src/main/java/awais/instagrabber/adapters/SearchItemsAdapter.java delete mode 100755 app/src/main/java/awais/instagrabber/adapters/SuggestionsAdapter.java create mode 100644 app/src/main/java/awais/instagrabber/adapters/viewholder/SearchItemViewHolder.java create mode 100644 app/src/main/java/awais/instagrabber/db/dao/RecentSearchDao.java create mode 100644 app/src/main/java/awais/instagrabber/db/datasources/RecentSearchDataSource.java create mode 100644 app/src/main/java/awais/instagrabber/db/entities/RecentSearch.java create mode 100644 app/src/main/java/awais/instagrabber/db/repositories/RecentSearchRepository.java create mode 100644 app/src/main/java/awais/instagrabber/fragments/search/SearchCategoryFragment.java create mode 100644 app/src/main/java/awais/instagrabber/fragments/search/SearchFragment.java create mode 100644 app/src/main/java/awais/instagrabber/viewmodels/SearchFragmentViewModel.java create mode 100644 app/src/main/res/layout/fragment_search.xml rename app/src/main/res/layout/{item_suggestion.xml => item_search_result.xml} (63%) mode change 100755 => 100644 diff --git a/app/build.gradle b/app/build.gradle index 871f8cc5..044c8f41 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -33,6 +33,12 @@ android { arguments = ["room.schemaLocation": "$projectDir/schemas".toString()] } } + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + sourceSets { + androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) } compileOptions { @@ -95,6 +101,13 @@ android { outputFileName = "barinsta_${suffix}.apk" } } + + packagingOptions { + // Exclude file to avoid + // Error: Duplicate files during packaging of APK + exclude 'META-INF/LICENSE.md' + exclude 'META-INF/LICENSE-notice.md' + } } configurations.all { @@ -165,4 +178,11 @@ dependencies { githubImplementation 'io.sentry:sentry-android:4.3.0' testImplementation 'org.junit.jupiter:junit-jupiter:5.7.1' + + androidTestImplementation 'org.junit.jupiter:junit-jupiter:5.7.1' + androidTestImplementation 'androidx.test:core:1.3.0' + androidTestImplementation 'com.android.support:support-annotations:28.0.0' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation "androidx.room:room-testing:2.2.6" + } diff --git a/app/schemas/awais.instagrabber.db.AppDatabase/6.json b/app/schemas/awais.instagrabber.db.AppDatabase/6.json new file mode 100644 index 00000000..4a2f5199 --- /dev/null +++ b/app/schemas/awais.instagrabber.db.AppDatabase/6.json @@ -0,0 +1,227 @@ +{ + "formatVersion": 1, + "database": { + "version": 6, + "identityHash": "232e618b3bfcb4661336b359d036c455", + "entities": [ + { + "tableName": "accounts", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uid` TEXT, `username` TEXT, `cookie` TEXT, `full_name` TEXT, `profile_pic` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "uid", + "columnName": "uid", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "cookie", + "columnName": "cookie", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "fullName", + "columnName": "full_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "profilePic", + "columnName": "profile_pic", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "favorites", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `query_text` TEXT, `type` TEXT, `display_name` TEXT, `pic_url` TEXT, `date_added` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "query", + "columnName": "query_text", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "displayName", + "columnName": "display_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "picUrl", + "columnName": "pic_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "dateAdded", + "columnName": "date_added", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "dm_last_notified", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `thread_id` TEXT, `last_notified_msg_ts` INTEGER, `last_notified_at` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "threadId", + "columnName": "thread_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastNotifiedMsgTs", + "columnName": "last_notified_msg_ts", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "lastNotifiedAt", + "columnName": "last_notified_at", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_dm_last_notified_thread_id", + "unique": true, + "columnNames": [ + "thread_id" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_dm_last_notified_thread_id` ON `${TABLE_NAME}` (`thread_id`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "recent_searches", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `ig_id` TEXT NOT NULL, `name` TEXT NOT NULL, `username` TEXT, `pic_url` TEXT, `type` TEXT NOT NULL, `last_searched_on` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "igId", + "columnName": "ig_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "picUrl", + "columnName": "pic_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastSearchedOn", + "columnName": "last_searched_on", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_recent_searches_ig_id_type", + "unique": true, + "columnNames": [ + "ig_id", + "type" + ], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_recent_searches_ig_id_type` ON `${TABLE_NAME}` (`ig_id`, `type`)" + } + ], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '232e618b3bfcb4661336b359d036c455')" + ] + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/awais/instagrabber/db/MigrationTest.java b/app/src/androidTest/java/awais/instagrabber/db/MigrationTest.java new file mode 100644 index 00000000..5156c5ff --- /dev/null +++ b/app/src/androidTest/java/awais/instagrabber/db/MigrationTest.java @@ -0,0 +1,51 @@ +package awais.instagrabber.db; + +import androidx.room.Room; +import androidx.room.migration.Migration; +import androidx.room.testing.MigrationTestHelper; +import androidx.sqlite.db.SupportSQLiteDatabase; +import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; + +import static awais.instagrabber.db.AppDatabase.MIGRATION_4_5; +import static awais.instagrabber.db.AppDatabase.MIGRATION_5_6; + +@RunWith(AndroidJUnit4.class) +public class MigrationTest { + private static final String TEST_DB = "migration-test"; + private static final Migration[] ALL_MIGRATIONS = new Migration[]{MIGRATION_4_5, MIGRATION_5_6}; + + @Rule + public MigrationTestHelper helper; + + public MigrationTest() { + final String canonicalName = AppDatabase.class.getCanonicalName(); + assert canonicalName != null; + helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(), + canonicalName, + new FrameworkSQLiteOpenHelperFactory()); + } + + @Test + public void migrateAll() throws IOException { + // Create earliest version of the database. Have to start with 4 since that is the version we migrated to Room. + SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 4); + db.close(); + + // Open latest version of the database. Room will validate the schema + // once all migrations execute. + AppDatabase appDb = Room.databaseBuilder(InstrumentationRegistry.getInstrumentation().getTargetContext(), + AppDatabase.class, + TEST_DB) + .addMigrations(ALL_MIGRATIONS).build(); + appDb.getOpenHelper().getWritableDatabase(); + appDb.close(); + } +} diff --git a/app/src/androidTest/java/awais/instagrabber/db/dao/RecentSearchDaoTest.java b/app/src/androidTest/java/awais/instagrabber/db/dao/RecentSearchDaoTest.java new file mode 100644 index 00000000..c8a48775 --- /dev/null +++ b/app/src/androidTest/java/awais/instagrabber/db/dao/RecentSearchDaoTest.java @@ -0,0 +1,82 @@ +package awais.instagrabber.db.dao; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.room.Room; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.runner.AndroidJUnit4; + +import com.google.common.collect.ImmutableList; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.runner.RunWith; + +import java.time.LocalDateTime; +import java.util.List; + +import awais.instagrabber.db.AppDatabase; +import awais.instagrabber.db.entities.RecentSearch; +import awais.instagrabber.models.enums.FavoriteType; + +@RunWith(AndroidJUnit4.class) +public class RecentSearchDaoTest { + private static final String TAG = RecentSearchDaoTest.class.getSimpleName(); + + private RecentSearchDao dao; + private AppDatabase db; + + @Before + public void createDb() { + final Context context = ApplicationProvider.getApplicationContext(); + db = Room.inMemoryDatabaseBuilder(context, AppDatabase.class).build(); + dao = db.recentSearchDao(); + } + + @After + public void closeDb() { + db.close(); + } + + @Test + public void writeQueryDelete() { + final RecentSearch recentSearch = insertRecentSearch("1", "test1", FavoriteType.HASHTAG); + final RecentSearch byIgIdAndType = dao.getRecentSearchByIgIdAndType("1", FavoriteType.HASHTAG); + Assertions.assertEquals(recentSearch, byIgIdAndType); + dao.deleteRecentSearch(byIgIdAndType); + final RecentSearch deleted = dao.getRecentSearchByIgIdAndType("1", FavoriteType.HASHTAG); + Assertions.assertNull(deleted); + } + + @Test + public void queryAllOrdered() { + final List insertListReversed = ImmutableList + .builder() + .add(insertRecentSearch("1", "test1", FavoriteType.HASHTAG)) + .add(insertRecentSearch("2", "test2", FavoriteType.LOCATION)) + .add(insertRecentSearch("3", "test3", FavoriteType.USER)) + .add(insertRecentSearch("4", "test4", FavoriteType.USER)) + .add(insertRecentSearch("5", "test5", FavoriteType.USER)) + .build() + .reverse(); // important + final List fromDb = dao.getAllRecentSearches(); + Assertions.assertIterableEquals(insertListReversed, fromDb); + } + + @NonNull + private RecentSearch insertRecentSearch(final String igId, final String name, final FavoriteType type) { + final RecentSearch recentSearch = new RecentSearch( + igId, + name, + null, + null, + type, + LocalDateTime.now() + ); + dao.insertRecentSearch(recentSearch); + return recentSearch; + } +} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/activities/MainActivity.java b/app/src/main/java/awais/instagrabber/activities/MainActivity.java index 8882bed9..cc833e97 100644 --- a/app/src/main/java/awais/instagrabber/activities/MainActivity.java +++ b/app/src/main/java/awais/instagrabber/activities/MainActivity.java @@ -8,19 +8,18 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; -import android.database.MatrixCursor; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; -import android.provider.BaseColumns; +import android.text.Editable; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.WindowManager; -import android.widget.AutoCompleteTextView; +import android.widget.EditText; import android.widget.Toast; import androidx.annotation.IdRes; @@ -28,7 +27,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.Toolbar; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.app.NotificationManagerCompat; @@ -50,10 +48,10 @@ import com.google.android.material.appbar.CollapsingToolbarLayout; import com.google.android.material.badge.BadgeDrawable; import com.google.android.material.behavior.HideBottomViewOnScrollBehavior; import com.google.android.material.bottomnavigation.BottomNavigationView; +import com.google.android.material.textfield.TextInputLayout; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterators; -import java.util.ArrayList; import java.util.Collections; import java.util.Deque; import java.util.List; @@ -62,9 +60,9 @@ import java.util.stream.Collectors; import awais.instagrabber.BuildConfig; import awais.instagrabber.R; -import awais.instagrabber.adapters.SuggestionsAdapter; import awais.instagrabber.asyncs.PostFetcher; import awais.instagrabber.customviews.emoji.EmojiVariantManager; +import awais.instagrabber.customviews.helpers.TextWatcherAdapter; import awais.instagrabber.databinding.ActivityMainBinding; import awais.instagrabber.fragments.PostViewV2Fragment; import awais.instagrabber.fragments.directmessages.DirectMessageInboxFragmentDirections; @@ -72,9 +70,6 @@ import awais.instagrabber.fragments.main.FeedFragment; import awais.instagrabber.fragments.settings.PreferenceKeys; import awais.instagrabber.models.IntentModel; import awais.instagrabber.models.Tab; -import awais.instagrabber.models.enums.SuggestionType; -import awais.instagrabber.repositories.responses.search.SearchItem; -import awais.instagrabber.repositories.responses.search.SearchResponse; import awais.instagrabber.services.ActivityCheckerService; import awais.instagrabber.services.DMSyncAlarmReceiver; import awais.instagrabber.utils.AppExecutors; @@ -88,10 +83,6 @@ import awais.instagrabber.utils.emoji.EmojiParser; import awais.instagrabber.viewmodels.AppStateViewModel; import awais.instagrabber.viewmodels.DirectInboxViewModel; import awais.instagrabber.webservices.RetrofitFactory; -import awais.instagrabber.webservices.SearchService; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; import static awais.instagrabber.utils.NavigationExtensions.setupWithNavController; import static awais.instagrabber.utils.Utils.settingsHelper; @@ -100,16 +91,19 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage private static final String TAG = "MainActivity"; private static final String FIRST_FRAGMENT_GRAPH_INDEX_KEY = "firstFragmentGraphIndex"; private static final String LAST_SELECT_NAV_MENU_ID = "lastSelectedNavMenuId"; + private static final List SEARCH_VISIBLE_DESTINATIONS = ImmutableList.of( + R.id.feedFragment, + R.id.profileFragment, + R.id.directMessagesInboxFragment, + R.id.discoverFragment, + R.id.favoritesFragment, + R.id.hashTagFragment, + R.id.locationFragment + ); private ActivityMainBinding binding; private LiveData currentNavControllerLiveData; private MenuItem searchMenuItem; - private SuggestionsAdapter suggestionAdapter; - private AutoCompleteTextView searchAutoComplete; - private SearchView searchView; - private SearchService searchService; - private boolean showSearch = true; - private Handler suggestionsFetchHandler; private int firstFragmentGraphIndex; private int lastSelectedNavMenuId; private boolean isActivityCheckerServiceBound = false; @@ -155,7 +149,6 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage if (savedInstanceState == null) { setupBottomNavigationBar(true); } - setupSuggestions(); if (!BuildConfig.isPre) { final boolean checkUpdates = settingsHelper.getBoolean(Constants.CHECK_UPDATES); if (checkUpdates) FlavorTown.updateCheck(this); @@ -174,9 +167,9 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage EmojiVariantManager.getInstance(); }); initEmojiCompat(); - searchService = SearchService.getInstance(); // initDmService(); initDmUnreadCount(); + initSearchInput(); } private void setupCookie() { @@ -216,25 +209,74 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage }); } + private void initSearchInput() { + binding.searchInputLayout.setEndIconOnClickListener(v -> { + final EditText editText = binding.searchInputLayout.getEditText(); + if (editText == null) return; + editText.setText(""); + }); + binding.searchInputLayout.addOnEditTextAttachedListener(textInputLayout -> { + textInputLayout.setEndIconVisible(false); + final EditText editText = textInputLayout.getEditText(); + if (editText == null) return; + editText.addTextChangedListener(new TextWatcherAdapter() { + @Override + public void afterTextChanged(final Editable s) { + binding.searchInputLayout.setEndIconVisible(!TextUtils.isEmpty(s)); + } + }); + }); + } + @Override public boolean onCreateOptionsMenu(final Menu menu) { getMenuInflater().inflate(R.menu.main_menu, menu); searchMenuItem = menu.findItem(R.id.search); - if (showSearch && currentNavControllerLiveData != null) { - final NavController navController = currentNavControllerLiveData.getValue(); - if (navController != null) { - final NavDestination currentDestination = navController.getCurrentDestination(); - if (currentDestination != null) { - final int destinationId = currentDestination.getId(); - showSearch = destinationId == R.id.profileFragment; - } + final NavController navController = currentNavControllerLiveData.getValue(); + if (navController != null) { + final NavDestination currentDestination = navController.getCurrentDestination(); + if (currentDestination != null) { + @SuppressLint("RestrictedApi") final Deque backStack = navController.getBackStack(); + setupMenu(backStack.size(), currentDestination.getId()); } } - if (!showSearch) { - searchMenuItem.setVisible(false); - return true; + // if (binding.searchInputLayout.getVisibility() == View.VISIBLE) { + // searchMenuItem.setVisible(false).setEnabled(false); + // return true; + // } + // searchMenuItem.setVisible(true).setEnabled(true); + // if (showSearch && currentNavControllerLiveData != null) { + // final NavController navController = currentNavControllerLiveData.getValue(); + // if (navController != null) { + // final NavDestination currentDestination = navController.getCurrentDestination(); + // if (currentDestination != null) { + // final int destinationId = currentDestination.getId(); + // showSearch = destinationId == R.id.profileFragment; + // } + // } + // } + // if (!showSearch) { + // searchMenuItem.setVisible(false); + // return true; + // } + // return setupSearchView(); + return true; + } + + @Override + public boolean onOptionsItemSelected(@NonNull final MenuItem item) { + if (item.getItemId() == R.id.search) { + final NavController navController = currentNavControllerLiveData.getValue(); + if (navController == null) return false; + try { + navController.navigate(R.id.action_global_search); + return true; + } catch (Exception e) { + Log.e(TAG, "onOptionsItemSelected: ", e); + } + return false; } - return setupSearchView(); + return super.onOptionsItemSelected(item); } @Override @@ -336,176 +378,6 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage notificationManager.createNotificationChannel(silentNotificationChannel); } - private void setupSuggestions() { - suggestionsFetchHandler = new Handler(); - suggestionAdapter = new SuggestionsAdapter(this, (type, query) -> { - if (searchMenuItem != null) searchMenuItem.collapseActionView(); - if (searchView != null && !searchView.isIconified()) searchView.setIconified(true); - if (currentNavControllerLiveData == null) return; - final NavController navController = currentNavControllerLiveData.getValue(); - if (navController == null) return; - final Bundle bundle = new Bundle(); - switch (type) { - case TYPE_LOCATION: - bundle.putLong("locationId", Long.parseLong(query)); - navController.navigate(R.id.action_global_locationFragment, bundle); - break; - case TYPE_HASHTAG: - bundle.putString("hashtag", query); - navController.navigate(R.id.action_global_hashTagFragment, bundle); - break; - case TYPE_USER: - bundle.putString("username", query); - navController.navigate(R.id.action_global_profileFragment, bundle); - break; - } - }); - } - - private boolean setupSearchView() { - final View actionView = searchMenuItem.getActionView(); - if (!(actionView instanceof SearchView)) return false; - searchView = (SearchView) actionView; - searchView.setSuggestionsAdapter(suggestionAdapter); - searchView.setMaxWidth(Integer.MAX_VALUE); - final View searchText = searchView.findViewById(R.id.search_src_text); - if (searchText instanceof AutoCompleteTextView) { - searchAutoComplete = (AutoCompleteTextView) searchText; - } - searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { - private boolean searchUser; - private boolean searchHash; - private Call prevSuggestionAsync; - private final String[] COLUMNS = { - BaseColumns._ID, - Constants.EXTRAS_USERNAME, - Constants.EXTRAS_NAME, - Constants.EXTRAS_TYPE, - "query", - "pfp", - "verified" - }; - private String currentSearchQuery; - - private final Callback cb = new Callback() { - @Override - public void onResponse(@NonNull final Call call, - @NonNull final Response response) { - final MatrixCursor cursor; - final SearchResponse body = response.body(); - if (body == null) { - cursor = null; - return; - } - final List result = new ArrayList<>(); - if (isLoggedIn) { - if (body.getList() != null) { - result.addAll(searchHash ? body.getList() - .stream() - .filter(i -> i.getUser() == null) - .collect(Collectors.toList()) - : body.getList()); - } - } else { - if (body.getUsers() != null && !searchHash) result.addAll(body.getUsers()); - if (body.getHashtags() != null) result.addAll(body.getHashtags()); - if (body.getPlaces() != null) result.addAll(body.getPlaces()); - } - cursor = new MatrixCursor(COLUMNS, 0); - for (int i = 0; i < result.size(); i++) { - final SearchItem suggestionModel = result.get(i); - if (suggestionModel != null) { - Object[] objects = null; - if (suggestionModel.getUser() != null) - objects = new Object[]{ - suggestionModel.getPosition(), - suggestionModel.getUser().getUsername(), - suggestionModel.getUser().getFullName(), - SuggestionType.TYPE_USER, - suggestionModel.getUser().getUsername(), - suggestionModel.getUser().getProfilePicUrl(), - suggestionModel.getUser().isVerified()}; - else if (suggestionModel.getHashtag() != null) - objects = new Object[]{ - suggestionModel.getPosition(), - suggestionModel.getHashtag().getName(), - suggestionModel.getHashtag().getSubtitle(), - SuggestionType.TYPE_HASHTAG, - suggestionModel.getHashtag().getName(), - "res:/" + R.drawable.ic_hashtag, - false}; - else if (suggestionModel.getPlace() != null) - objects = new Object[]{ - suggestionModel.getPosition(), - suggestionModel.getPlace().getTitle(), - suggestionModel.getPlace().getSubtitle(), - SuggestionType.TYPE_LOCATION, - suggestionModel.getPlace().getLocation().getPk(), - "res:/" + R.drawable.ic_location, - false}; - cursor.addRow(objects); - } - } - suggestionAdapter.changeCursor(cursor); - } - - @Override - public void onFailure(@NonNull final Call call, - @NonNull Throwable t) { - if (!call.isCanceled()) { - Log.e(TAG, "Exception on search:", t); - } - } - }; - - private final Runnable runnable = () -> { - cancelSuggestionsAsync(); - if (TextUtils.isEmpty(currentSearchQuery)) { - suggestionAdapter.changeCursor(null); - return; - } - searchUser = currentSearchQuery.charAt(0) == '@'; - searchHash = currentSearchQuery.charAt(0) == '#'; - if (currentSearchQuery.length() == 1 && (searchHash || searchUser)) { - if (searchAutoComplete != null) { - searchAutoComplete.setThreshold(2); - } - } else { - if (searchAutoComplete != null) { - searchAutoComplete.setThreshold(1); - } - prevSuggestionAsync = searchService.search(isLoggedIn, - searchUser || searchHash ? currentSearchQuery.substring(1) - : currentSearchQuery, - searchUser ? "user" : (searchHash ? "hashtag" : "blended")); - suggestionAdapter.changeCursor(null); - prevSuggestionAsync.enqueue(cb); - } - }; - - private void cancelSuggestionsAsync() { - if (prevSuggestionAsync != null) - try { - prevSuggestionAsync.cancel(); - } catch (final Exception ignored) {} - } - - @Override - public boolean onQueryTextSubmit(final String query) { - return onQueryTextChange(query); - } - - @Override - public boolean onQueryTextChange(final String query) { - suggestionsFetchHandler.removeCallbacks(runnable); - currentSearchQuery = query; - suggestionsFetchHandler.postDelayed(runnable, 800); - return true; - } - }); - return true; - } - private void setupBottomNavigationBar(final boolean setDefaultTabFromSettings) { currentTabs = !isLoggedIn ? setupAnonBottomNav() : setupMainBottomNav(); final List mainNavList = currentTabs.stream() @@ -606,20 +478,6 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage binding.bottomNavView.setSelectedItemId(navGraphRootId); } - // @NonNull - // private List getMainNavList(final int main_nav_ids) { - // final TypedArray navIds = getResources().obtainTypedArray(main_nav_ids); - // final List mainNavList = new ArrayList<>(navIds.length()); - // final int length = navIds.length(); - // for (int i = 0; i < length; i++) { - // final int resourceId = navIds.getResourceId(i, -1); - // if (resourceId < 0) continue; - // mainNavList.add(resourceId); - // } - // navIds.recycle(); - // return mainNavList; - // } - private void setupNavigation(final Toolbar toolbar, final NavController navController) { if (navController == null) return; NavigationUI.setupWithNavController(toolbar, navController); @@ -651,12 +509,10 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage private void setupMenu(final int backStackSize, final int destinationId) { if (searchMenuItem == null) return; - if (backStackSize >= 2 && destinationId == R.id.profileFragment) { - showSearch = true; + if (backStackSize >= 2 && SEARCH_VISIBLE_DESTINATIONS.contains(destinationId)) { searchMenuItem.setVisible(true); return; } - showSearch = false; searchMenuItem.setVisible(false); } @@ -935,10 +791,6 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage return currentTabs; } - // public boolean isNavRootInCurrentTabs(@IdRes final int navRootId) { - // return showBottomViewDestinations.stream().anyMatch(id -> id == navRootId); - // } - private void setNavBarDMUnreadCountBadge(final int unseenCount) { final BadgeDrawable badge = binding.bottomNavView.getOrCreateBadge(R.id.direct_messages_nav_graph); if (badge == null) return; @@ -953,4 +805,14 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage badge.setNumber(unseenCount); badge.setVisible(true); } + + @NonNull + public TextInputLayout showSearchView() { + binding.searchInputLayout.setVisibility(View.VISIBLE); + return binding.searchInputLayout; + } + + public void hideSearchView() { + binding.searchInputLayout.setVisibility(View.GONE); + } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/adapters/FavoritesAdapter.java b/app/src/main/java/awais/instagrabber/adapters/FavoritesAdapter.java index d697343a..b9d89ce7 100644 --- a/app/src/main/java/awais/instagrabber/adapters/FavoritesAdapter.java +++ b/app/src/main/java/awais/instagrabber/adapters/FavoritesAdapter.java @@ -19,7 +19,7 @@ import java.util.List; import awais.instagrabber.R; import awais.instagrabber.adapters.viewholder.FavoriteViewHolder; import awais.instagrabber.databinding.ItemFavSectionHeaderBinding; -import awais.instagrabber.databinding.ItemSuggestionBinding; +import awais.instagrabber.databinding.ItemSearchResultBinding; import awais.instagrabber.db.entities.Favorite; import awais.instagrabber.models.enums.FavoriteType; @@ -73,7 +73,7 @@ public class FavoritesAdapter extends RecyclerView.Adapter categories; + + public SearchCategoryAdapter(@NonNull final Fragment fragment, + @NonNull final List categories) { + super(fragment); + this.categories = categories; + + } + + @NonNull + @Override + public Fragment createFragment(final int position) { + return SearchCategoryFragment.newInstance(categories.get(position)); + } + + @Override + public int getItemCount() { + return categories.size(); + } +} diff --git a/app/src/main/java/awais/instagrabber/adapters/SearchItemsAdapter.java b/app/src/main/java/awais/instagrabber/adapters/SearchItemsAdapter.java new file mode 100644 index 00000000..e26059de --- /dev/null +++ b/app/src/main/java/awais/instagrabber/adapters/SearchItemsAdapter.java @@ -0,0 +1,215 @@ +package awais.instagrabber.adapters; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.AdapterListUpdateCallback; +import androidx.recyclerview.widget.AsyncDifferConfig; +import androidx.recyclerview.widget.AsyncListDiffer; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import awais.instagrabber.R; +import awais.instagrabber.adapters.viewholder.SearchItemViewHolder; +import awais.instagrabber.databinding.ItemFavSectionHeaderBinding; +import awais.instagrabber.databinding.ItemSearchResultBinding; +import awais.instagrabber.fragments.search.SearchCategoryFragment.OnSearchItemClickListener; +import awais.instagrabber.models.enums.FavoriteType; +import awais.instagrabber.repositories.responses.search.SearchItem; + +public final class SearchItemsAdapter extends RecyclerView.Adapter { + private static final String TAG = SearchItemsAdapter.class.getSimpleName(); + private static final DiffUtil.ItemCallback DIFF_CALLBACK = new DiffUtil.ItemCallback() { + @Override + public boolean areItemsTheSame(@NonNull final SearchItemOrHeader oldItem, @NonNull final SearchItemOrHeader newItem) { + return Objects.equals(oldItem, newItem); + } + + @Override + public boolean areContentsTheSame(@NonNull final SearchItemOrHeader oldItem, @NonNull final SearchItemOrHeader newItem) { + return Objects.equals(oldItem, newItem); + } + }; + private static final String RECENT = "recent"; + private static final String FAVORITE = "favorite"; + private static final int VIEW_TYPE_HEADER = 0; + private static final int VIEW_TYPE_ITEM = 1; + + private final OnSearchItemClickListener onSearchItemClickListener; + private final AsyncListDiffer differ; + + public SearchItemsAdapter(final OnSearchItemClickListener onSearchItemClickListener) { + differ = new AsyncListDiffer<>(new AdapterListUpdateCallback(this), + new AsyncDifferConfig.Builder<>(DIFF_CALLBACK).build()); + this.onSearchItemClickListener = onSearchItemClickListener; + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { + final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); + if (viewType == VIEW_TYPE_HEADER) { + return new HeaderViewHolder(ItemFavSectionHeaderBinding.inflate(layoutInflater, parent, false)); + } + final ItemSearchResultBinding binding = ItemSearchResultBinding.inflate(layoutInflater, parent, false); + return new SearchItemViewHolder(binding, onSearchItemClickListener); + } + + @Override + public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) { + if (getItemViewType(position) == VIEW_TYPE_HEADER) { + final SearchItemOrHeader searchItemOrHeader = getItem(position); + if (!searchItemOrHeader.isHeader()) return; + ((HeaderViewHolder) holder).bind(searchItemOrHeader.header); + return; + } + ((SearchItemViewHolder) holder).bind(getItem(position).searchItem); + } + + protected SearchItemOrHeader getItem(int position) { + return differ.getCurrentList().get(position); + } + + @Override + public int getItemCount() { + return differ.getCurrentList().size(); + } + + @Override + public int getItemViewType(final int position) { + return getItem(position).isHeader() ? VIEW_TYPE_HEADER : VIEW_TYPE_ITEM; + } + + public void submitList(@Nullable final List list) { + if (list == null) { + differ.submitList(null); + return; + } + differ.submitList(sectionAndSort(list)); + } + + public void submitList(@Nullable final List list, @Nullable final Runnable commitCallback) { + if (list == null) { + differ.submitList(null, commitCallback); + return; + } + differ.submitList(sectionAndSort(list), commitCallback); + } + + @NonNull + private List sectionAndSort(@NonNull final List list) { + final boolean containsRecentOrFavorite = list.stream().anyMatch(searchItem -> searchItem.isRecent() || searchItem.isFavorite()); + // Don't do anything if not showing recent results + if (!containsRecentOrFavorite) { + return list.stream() + .map(SearchItemOrHeader::new) + .collect(Collectors.toList()); + } + final List listCopy = new ArrayList<>(list); + Collections.sort(listCopy, (o1, o2) -> { + final boolean bothRecent = o1.isRecent() && o2.isRecent(); + if (bothRecent) { + // Don't sort + return 0; + } + final boolean bothFavorite = o1.isFavorite() && o2.isFavorite(); + if (bothFavorite) { + if (o1.getType() == o2.getType()) return 0; + // keep users at top + if (o1.getType() == FavoriteType.USER) return -1; + if (o2.getType() == FavoriteType.USER) return 1; + // keep locations at bottom + if (o1.getType() == FavoriteType.LOCATION) return 1; + if (o2.getType() == FavoriteType.LOCATION) return -1; + } + // keep recents at top + if (o1.isRecent()) return -1; + if (o2.isRecent()) return 1; + return 0; + }); + final List itemOrHeaders = new ArrayList<>(); + for (int i = 0; i < listCopy.size(); i++) { + final SearchItem searchItem = listCopy.get(i); + final SearchItemOrHeader prev = itemOrHeaders.isEmpty() ? null : itemOrHeaders.get(itemOrHeaders.size() - 1); + boolean prevWasSameType = prev != null && ((prev.searchItem.isRecent() && searchItem.isRecent()) + || (prev.searchItem.isFavorite() && searchItem.isFavorite())); + if (prevWasSameType) { + // just add the item + itemOrHeaders.add(new SearchItemOrHeader(searchItem)); + continue; + } + // add header and item + // add header only if search item is recent or favorite + if (searchItem.isRecent() || searchItem.isFavorite()) { + itemOrHeaders.add(new SearchItemOrHeader(searchItem.isRecent() ? RECENT : FAVORITE)); + } + itemOrHeaders.add(new SearchItemOrHeader(searchItem)); + } + return itemOrHeaders; + } + + private static class SearchItemOrHeader { + String header; + SearchItem searchItem; + + public SearchItemOrHeader(final SearchItem searchItem) { + this.searchItem = searchItem; + } + + public SearchItemOrHeader(final String header) { + this.header = header; + } + + boolean isHeader() { + return header != null; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final SearchItemOrHeader that = (SearchItemOrHeader) o; + return Objects.equals(header, that.header) && + Objects.equals(searchItem, that.searchItem); + } + + @Override + public int hashCode() { + return Objects.hash(header, searchItem); + } + } + + public static class HeaderViewHolder extends RecyclerView.ViewHolder { + private final ItemFavSectionHeaderBinding binding; + + public HeaderViewHolder(@NonNull final ItemFavSectionHeaderBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + + public void bind(final String header) { + if (header == null) return; + final int headerText; + switch (header) { + case RECENT: + headerText = R.string.recent; + break; + case FAVORITE: + headerText = R.string.title_favorites; + break; + default: + headerText = R.string.unknown; + break; + } + binding.getRoot().setText(headerText); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/adapters/SuggestionsAdapter.java b/app/src/main/java/awais/instagrabber/adapters/SuggestionsAdapter.java deleted file mode 100755 index 6c51f1f7..00000000 --- a/app/src/main/java/awais/instagrabber/adapters/SuggestionsAdapter.java +++ /dev/null @@ -1,77 +0,0 @@ -package awais.instagrabber.adapters; - -import android.content.Context; -import android.database.Cursor; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.cursoradapter.widget.CursorAdapter; - -import awais.instagrabber.databinding.ItemSuggestionBinding; -import awais.instagrabber.models.enums.SuggestionType; - -public final class SuggestionsAdapter extends CursorAdapter { - private static final String TAG = "SuggestionsAdapter"; - - private final OnSuggestionClickListener onSuggestionClickListener; - - public SuggestionsAdapter(final Context context, - final OnSuggestionClickListener onSuggestionClickListener) { - super(context, null, FLAG_REGISTER_CONTENT_OBSERVER); - this.onSuggestionClickListener = onSuggestionClickListener; - } - - @Override - public View newView(final Context context, final Cursor cursor, final ViewGroup parent) { - final LayoutInflater layoutInflater = LayoutInflater.from(context); - final ItemSuggestionBinding binding = ItemSuggestionBinding.inflate(layoutInflater, parent, false); - return binding.getRoot(); - // return layoutInflater.inflate(R.layout.item_suggestion, parent, false); - } - - @Override - public void bindView(@NonNull final View view, final Context context, @NonNull final Cursor cursor) { - // i, username, fullname, type, query, picUrl, verified - // 0, 1 , 2 , 3 , 4 , 5 , 6 - final String fullName = cursor.getString(2); - String username = cursor.getString(1); - String picUrl = cursor.getString(5); - final boolean verified = cursor.getString(6).charAt(0) == 't'; - - final String type = cursor.getString(3); - SuggestionType suggestionType = null; - try { - suggestionType = SuggestionType.valueOf(type); - } catch (IllegalArgumentException e) { - Log.e(TAG, "Unknown suggestion type: " + type, e); - } - if (suggestionType == null) return; - String query = cursor.getString(4); - switch (suggestionType) { - case TYPE_USER: - username = '@' + username; - break; - case TYPE_HASHTAG: - username = '#' + username; - break; - } - - if (onSuggestionClickListener != null) { - final SuggestionType finalSuggestionType = suggestionType; - view.setOnClickListener(v -> onSuggestionClickListener.onSuggestionClick(finalSuggestionType, query)); - } - final ItemSuggestionBinding binding = ItemSuggestionBinding.bind(view); - binding.isVerified.setVisibility(verified ? View.VISIBLE : View.GONE); - binding.tvUsername.setText(username); - binding.tvFullName.setVisibility(View.VISIBLE); - binding.tvFullName.setText(fullName); - binding.ivProfilePic.setImageURI(picUrl); - } - - public interface OnSuggestionClickListener { - void onSuggestionClick(final SuggestionType type, final String query); - } -} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/FavoriteViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/FavoriteViewHolder.java index e16824ea..bf9da891 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/FavoriteViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/FavoriteViewHolder.java @@ -6,7 +6,7 @@ import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import awais.instagrabber.adapters.FavoritesAdapter; -import awais.instagrabber.databinding.ItemSuggestionBinding; +import awais.instagrabber.databinding.ItemSearchResultBinding; import awais.instagrabber.db.entities.Favorite; import awais.instagrabber.models.enums.FavoriteType; import awais.instagrabber.utils.Constants; @@ -14,12 +14,12 @@ import awais.instagrabber.utils.Constants; public class FavoriteViewHolder extends RecyclerView.ViewHolder { private static final String TAG = "FavoriteViewHolder"; - private final ItemSuggestionBinding binding; + private final ItemSearchResultBinding binding; - public FavoriteViewHolder(@NonNull final ItemSuggestionBinding binding) { + public FavoriteViewHolder(@NonNull final ItemSearchResultBinding binding) { super(binding.getRoot()); this.binding = binding; - binding.isVerified.setVisibility(View.GONE); + binding.verified.setVisibility(View.GONE); } public void bind(final Favorite model, @@ -36,12 +36,12 @@ public class FavoriteViewHolder extends RecyclerView.ViewHolder { return longClickListener.onLongClick(model); }); if (model.getType() == FavoriteType.HASHTAG) { - binding.ivProfilePic.setImageURI(Constants.DEFAULT_HASH_TAG_PIC); + binding.profilePic.setImageURI(Constants.DEFAULT_HASH_TAG_PIC); } else { - binding.ivProfilePic.setImageURI(model.getPicUrl()); + binding.profilePic.setImageURI(model.getPicUrl()); } - binding.tvFullName.setText(model.getDisplayName()); - binding.tvUsername.setVisibility(View.VISIBLE); + binding.title.setVisibility(View.VISIBLE); + binding.subtitle.setText(model.getDisplayName()); String query = model.getQuery(); switch (model.getType()) { case HASHTAG: @@ -51,11 +51,11 @@ public class FavoriteViewHolder extends RecyclerView.ViewHolder { query = "@" + query; break; case LOCATION: - binding.tvUsername.setVisibility(View.GONE); + binding.title.setVisibility(View.GONE); break; default: // do nothing } - binding.tvUsername.setText(query); + binding.title.setText(query); } } diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/SearchItemViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/SearchItemViewHolder.java new file mode 100644 index 00000000..4eb93fed --- /dev/null +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/SearchItemViewHolder.java @@ -0,0 +1,80 @@ +package awais.instagrabber.adapters.viewholder; + +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import awais.instagrabber.R; +import awais.instagrabber.databinding.ItemSearchResultBinding; +import awais.instagrabber.fragments.search.SearchCategoryFragment.OnSearchItemClickListener; +import awais.instagrabber.models.enums.FavoriteType; +import awais.instagrabber.repositories.responses.Hashtag; +import awais.instagrabber.repositories.responses.Place; +import awais.instagrabber.repositories.responses.User; +import awais.instagrabber.repositories.responses.search.SearchItem; + +public class SearchItemViewHolder extends RecyclerView.ViewHolder { + + private final ItemSearchResultBinding binding; + private final OnSearchItemClickListener onSearchItemClickListener; + + public SearchItemViewHolder(@NonNull final ItemSearchResultBinding binding, + final OnSearchItemClickListener onSearchItemClickListener) { + super(binding.getRoot()); + this.binding = binding; + this.onSearchItemClickListener = onSearchItemClickListener; + } + + public void bind(final SearchItem searchItem) { + if (searchItem == null) return; + final FavoriteType type = searchItem.getType(); + if (type == null) return; + String title; + String subtitle; + String picUrl; + boolean isVerified = false; + switch (type) { + case USER: + final User user = searchItem.getUser(); + title = "@" + user.getUsername(); + subtitle = user.getFullName(); + picUrl = user.getProfilePicUrl(); + isVerified = user.isVerified(); + break; + case HASHTAG: + final Hashtag hashtag = searchItem.getHashtag(); + title = "#" + hashtag.getName(); + subtitle = hashtag.getSubtitle(); + picUrl = "res:/" + R.drawable.ic_hashtag; + break; + case LOCATION: + final Place place = searchItem.getPlace(); + title = place.getTitle(); + subtitle = place.getSubtitle(); + picUrl = "res:/" + R.drawable.ic_location; + break; + default: + return; + } + itemView.setOnClickListener(v -> { + if (onSearchItemClickListener != null) { + onSearchItemClickListener.onSearchItemClick(searchItem); + } + }); + binding.delete.setVisibility(searchItem.isRecent() ? View.VISIBLE : View.GONE); + if (searchItem.isRecent()) { + binding.delete.setEnabled(true); + binding.delete.setOnClickListener(v -> { + if (onSearchItemClickListener != null) { + binding.delete.setEnabled(false); + onSearchItemClickListener.onSearchItemDelete(searchItem); + } + }); + } + binding.title.setText(title); + binding.subtitle.setText(subtitle); + binding.profilePic.setImageURI(picUrl); + binding.verified.setVisibility(isVerified ? View.VISIBLE : View.GONE); + } +} diff --git a/app/src/main/java/awais/instagrabber/db/AppDatabase.java b/app/src/main/java/awais/instagrabber/db/AppDatabase.java index 63e2ce43..37446d9d 100644 --- a/app/src/main/java/awais/instagrabber/db/AppDatabase.java +++ b/app/src/main/java/awais/instagrabber/db/AppDatabase.java @@ -22,14 +22,16 @@ import java.util.List; import awais.instagrabber.db.dao.AccountDao; import awais.instagrabber.db.dao.DMLastNotifiedDao; import awais.instagrabber.db.dao.FavoriteDao; +import awais.instagrabber.db.dao.RecentSearchDao; import awais.instagrabber.db.entities.Account; import awais.instagrabber.db.entities.DMLastNotified; import awais.instagrabber.db.entities.Favorite; +import awais.instagrabber.db.entities.RecentSearch; import awais.instagrabber.models.enums.FavoriteType; import awais.instagrabber.utils.Utils; -@Database(entities = {Account.class, Favorite.class, DMLastNotified.class}, - version = 5) +@Database(entities = {Account.class, Favorite.class, DMLastNotified.class, RecentSearch.class}, + version = 6) @TypeConverters({Converters.class}) public abstract class AppDatabase extends RoomDatabase { private static final String TAG = AppDatabase.class.getSimpleName(); @@ -42,12 +44,14 @@ public abstract class AppDatabase extends RoomDatabase { public abstract DMLastNotifiedDao dmLastNotifiedDao(); + public abstract RecentSearchDao recentSearchDao(); + public static AppDatabase getDatabase(final Context context) { if (INSTANCE == null) { synchronized (AppDatabase.class) { if (INSTANCE == null) { INSTANCE = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "cookiebox.db") - .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5) + .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6) .build(); } } @@ -156,6 +160,21 @@ public abstract class AppDatabase extends RoomDatabase { } }; + static final Migration MIGRATION_5_6 = new Migration(5, 6) { + @Override + public void migrate(@NonNull final SupportSQLiteDatabase database) { + database.execSQL("CREATE TABLE IF NOT EXISTS `recent_searches` (" + + "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " + + "`ig_id` TEXT NOT NULL, " + + "`name` TEXT NOT NULL, " + + "`username` TEXT, " + + "`pic_url` TEXT, " + + "`type` TEXT NOT NULL, " + + "`last_searched_on` INTEGER NOT NULL)"); + database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_recent_searches_ig_id_type` ON `recent_searches` (`ig_id`, `type`)"); + } + }; + @NonNull private static List backupOldFavorites(@NonNull final SupportSQLiteDatabase db) { // check if old favorites table had the column query_display diff --git a/app/src/main/java/awais/instagrabber/db/dao/RecentSearchDao.java b/app/src/main/java/awais/instagrabber/db/dao/RecentSearchDao.java new file mode 100644 index 00000000..94266524 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/db/dao/RecentSearchDao.java @@ -0,0 +1,37 @@ +package awais.instagrabber.db.dao; + +import androidx.room.Dao; +import androidx.room.Delete; +import androidx.room.Insert; +import androidx.room.Query; +import androidx.room.Update; + +import java.util.List; + +import awais.instagrabber.db.entities.RecentSearch; +import awais.instagrabber.models.enums.FavoriteType; + +@Dao +public interface RecentSearchDao { + + @Query("SELECT * FROM recent_searches ORDER BY last_searched_on DESC") + List getAllRecentSearches(); + + @Query("SELECT * FROM recent_searches WHERE `ig_id` = :igId AND `type` = :type") + RecentSearch getRecentSearchByIgIdAndType(String igId, FavoriteType type); + + @Query("SELECT * FROM recent_searches WHERE instr(`name`, :query) > 0") + List findRecentSearchesWithNameContaining(String query); + + @Insert + Long insertRecentSearch(RecentSearch recentSearch); + + @Update + void updateRecentSearch(RecentSearch recentSearch); + + @Delete + void deleteRecentSearch(RecentSearch recentSearch); + + // @Query("DELETE from recent_searches") + // void deleteAllRecentSearches(); +} diff --git a/app/src/main/java/awais/instagrabber/db/datasources/RecentSearchDataSource.java b/app/src/main/java/awais/instagrabber/db/datasources/RecentSearchDataSource.java new file mode 100644 index 00000000..9fd950a8 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/db/datasources/RecentSearchDataSource.java @@ -0,0 +1,57 @@ +package awais.instagrabber.db.datasources; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import java.util.List; + +import awais.instagrabber.db.AppDatabase; +import awais.instagrabber.db.dao.RecentSearchDao; +import awais.instagrabber.db.entities.RecentSearch; +import awais.instagrabber.models.enums.FavoriteType; + +public class RecentSearchDataSource { + private static final String TAG = RecentSearchDataSource.class.getSimpleName(); + + private static RecentSearchDataSource INSTANCE; + + private final RecentSearchDao recentSearchDao; + + private RecentSearchDataSource(final RecentSearchDao recentSearchDao) { + this.recentSearchDao = recentSearchDao; + } + + public static synchronized RecentSearchDataSource getInstance(@NonNull Context context) { + if (INSTANCE == null) { + synchronized (RecentSearchDataSource.class) { + if (INSTANCE == null) { + final AppDatabase database = AppDatabase.getDatabase(context); + INSTANCE = new RecentSearchDataSource(database.recentSearchDao()); + } + } + } + return INSTANCE; + } + + public RecentSearch getRecentSearchByIgIdAndType(@NonNull final String igId, @NonNull final FavoriteType type) { + return recentSearchDao.getRecentSearchByIgIdAndType(igId, type); + } + + @NonNull + public final List getAllRecentSearches() { + return recentSearchDao.getAllRecentSearches(); + } + + public final void insertOrUpdateRecentSearch(@NonNull final RecentSearch recentSearch) { + if (recentSearch.getId() != 0) { + recentSearchDao.updateRecentSearch(recentSearch); + return; + } + recentSearchDao.insertRecentSearch(recentSearch); + } + + public final void deleteRecentSearch(@NonNull final RecentSearch recentSearch) { + recentSearchDao.deleteRecentSearch(recentSearch); + } +} diff --git a/app/src/main/java/awais/instagrabber/db/entities/RecentSearch.java b/app/src/main/java/awais/instagrabber/db/entities/RecentSearch.java new file mode 100644 index 00000000..95bd368e --- /dev/null +++ b/app/src/main/java/awais/instagrabber/db/entities/RecentSearch.java @@ -0,0 +1,185 @@ +package awais.instagrabber.db.entities; + +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.room.ColumnInfo; +import androidx.room.Entity; +import androidx.room.Ignore; +import androidx.room.Index; +import androidx.room.PrimaryKey; + +import java.time.LocalDateTime; +import java.util.Objects; + +import awais.instagrabber.models.enums.FavoriteType; +import awais.instagrabber.repositories.responses.search.SearchItem; + +@Entity(tableName = RecentSearch.TABLE_NAME, indices = {@Index(value = {RecentSearch.COL_IG_ID, RecentSearch.COL_TYPE}, unique = true)}) +public class RecentSearch { + private static final String TAG = RecentSearch.class.getSimpleName(); + + public static final String TABLE_NAME = "recent_searches"; + private static final String COL_ID = "id"; + public static final String COL_IG_ID = "ig_id"; + private static final String COL_NAME = "name"; + private static final String COL_USERNAME = "username"; + private static final String COL_PIC_URL = "pic_url"; + public static final String COL_TYPE = "type"; + private static final String COL_LAST_SEARCHED_ON = "last_searched_on"; + + @PrimaryKey(autoGenerate = true) + @ColumnInfo(name = COL_ID) + private final int id; + + @ColumnInfo(name = COL_IG_ID) + @NonNull + private final String igId; + + @ColumnInfo(name = COL_NAME) + @NonNull + private final String name; + + @ColumnInfo(name = COL_USERNAME) + private final String username; + + @ColumnInfo(name = COL_PIC_URL) + private final String picUrl; + + @ColumnInfo(name = COL_TYPE) + @NonNull + private final FavoriteType type; + + @ColumnInfo(name = COL_LAST_SEARCHED_ON) + @NonNull + private final LocalDateTime lastSearchedOn; + + @Ignore + public RecentSearch(final String igId, + final String name, + final String username, + final String picUrl, + final FavoriteType type, + final LocalDateTime lastSearchedOn) { + this(0, igId, name, username, picUrl, type, lastSearchedOn); + } + + public RecentSearch(final int id, + @NonNull final String igId, + @NonNull final String name, + final String username, + final String picUrl, + @NonNull final FavoriteType type, + @NonNull final LocalDateTime lastSearchedOn) { + this.id = id; + this.igId = igId; + this.name = name; + this.username = username; + this.picUrl = picUrl; + this.type = type; + this.lastSearchedOn = lastSearchedOn; + } + + public int getId() { + return id; + } + + @NonNull + public String getIgId() { + return igId; + } + + @NonNull + public String getName() { + return name; + } + + public String getUsername() { + return username; + } + + public String getPicUrl() { + return picUrl; + } + + @NonNull + public FavoriteType getType() { + return type; + } + + @NonNull + public LocalDateTime getLastSearchedOn() { + return lastSearchedOn; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final RecentSearch that = (RecentSearch) o; + return Objects.equals(igId, that.igId) && + Objects.equals(name, that.name) && + Objects.equals(username, that.username) && + Objects.equals(picUrl, that.picUrl) && + type == that.type && + Objects.equals(lastSearchedOn, that.lastSearchedOn); + } + + @Override + public int hashCode() { + return Objects.hash(igId, name, username, picUrl, type, lastSearchedOn); + } + + @NonNull + @Override + public String toString() { + return "RecentSearch{" + + "id=" + id + + ", igId='" + igId + '\'' + + ", name='" + name + '\'' + + ", username='" + username + '\'' + + ", picUrl='" + picUrl + '\'' + + ", type=" + type + + ", lastSearchedOn=" + lastSearchedOn + + '}'; + } + + @Nullable + public static RecentSearch fromSearchItem(@NonNull final SearchItem searchItem) { + final FavoriteType type = searchItem.getType(); + if (type == null) return null; + try { + final String igId; + final String name; + final String username; + final String picUrl; + switch (type) { + case USER: + igId = String.valueOf(searchItem.getUser().getPk()); + name = searchItem.getUser().getFullName(); + username = searchItem.getUser().getUsername(); + picUrl = searchItem.getUser().getProfilePicUrl(); + break; + case HASHTAG: + igId = searchItem.getHashtag().getId(); + name = searchItem.getHashtag().getName(); + username = null; + picUrl = null; + break; + case LOCATION: + igId = String.valueOf(searchItem.getPlace().getLocation().getPk()); + name = searchItem.getPlace().getTitle(); + username = null; + picUrl = null; + break; + default: + return null; + } + return new RecentSearch(igId, name, username, picUrl, type, LocalDateTime.now()); + } catch (Exception e) { + Log.e(TAG, "fromSearchItem: ", e); + } + return null; + } +} diff --git a/app/src/main/java/awais/instagrabber/db/repositories/RecentSearchRepository.java b/app/src/main/java/awais/instagrabber/db/repositories/RecentSearchRepository.java new file mode 100644 index 00000000..0832a109 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/db/repositories/RecentSearchRepository.java @@ -0,0 +1,124 @@ +package awais.instagrabber.db.repositories; + +import androidx.annotation.NonNull; + +import java.time.LocalDateTime; +import java.util.List; + +import awais.instagrabber.db.datasources.RecentSearchDataSource; +import awais.instagrabber.db.entities.RecentSearch; +import awais.instagrabber.models.enums.FavoriteType; +import awais.instagrabber.utils.AppExecutors; + +public class RecentSearchRepository { + private static final String TAG = RecentSearchRepository.class.getSimpleName(); + + private static RecentSearchRepository instance; + + private final AppExecutors appExecutors; + private final RecentSearchDataSource recentSearchDataSource; + + private RecentSearchRepository(final AppExecutors appExecutors, final RecentSearchDataSource recentSearchDataSource) { + this.appExecutors = appExecutors; + this.recentSearchDataSource = recentSearchDataSource; + } + + public static RecentSearchRepository getInstance(final RecentSearchDataSource recentSearchDataSource) { + if (instance == null) { + instance = new RecentSearchRepository(AppExecutors.getInstance(), recentSearchDataSource); + } + return instance; + } + + public void getRecentSearch(@NonNull final String igId, + @NonNull final FavoriteType type, + final RepositoryCallback callback) { + // request on the I/O thread + appExecutors.diskIO().execute(() -> { + final RecentSearch recentSearch = recentSearchDataSource.getRecentSearchByIgIdAndType(igId, type); + // notify on the main thread + appExecutors.mainThread().execute(() -> { + if (callback == null) return; + if (recentSearch == null) { + callback.onDataNotAvailable(); + return; + } + callback.onSuccess(recentSearch); + }); + }); + } + + public void getAllRecentSearches(final RepositoryCallback> callback) { + // request on the I/O thread + appExecutors.diskIO().execute(() -> { + final List recentSearches = recentSearchDataSource.getAllRecentSearches(); + // notify on the main thread + appExecutors.mainThread().execute(() -> { + if (callback == null) return; + callback.onSuccess(recentSearches); + }); + }); + } + + public void insertOrUpdateRecentSearch(@NonNull final RecentSearch recentSearch, + final RepositoryCallback callback) { + insertOrUpdateRecentSearch(recentSearch.getIgId(), recentSearch.getName(), recentSearch.getUsername(), recentSearch.getPicUrl(), + recentSearch.getType(), callback); + } + + public void insertOrUpdateRecentSearch(@NonNull final String igId, + @NonNull final String name, + final String username, + final String picUrl, + @NonNull final FavoriteType type, + final RepositoryCallback callback) { + // request on the I/O thread + appExecutors.diskIO().execute(() -> { + RecentSearch recentSearch = recentSearchDataSource.getRecentSearchByIgIdAndType(igId, type); + recentSearch = recentSearch == null + ? new RecentSearch(igId, name, username, picUrl, type, LocalDateTime.now()) + : new RecentSearch(recentSearch.getId(), igId, name, username, picUrl, type, LocalDateTime.now()); + recentSearchDataSource.insertOrUpdateRecentSearch(recentSearch); + // notify on the main thread + appExecutors.mainThread().execute(() -> { + if (callback == null) return; + callback.onSuccess(null); + }); + }); + } + + public void deleteRecentSearchByIgIdAndType(@NonNull final String igId, + @NonNull final FavoriteType type, + final RepositoryCallback callback) { + // request on the I/O thread + appExecutors.diskIO().execute(() -> { + final RecentSearch recentSearch = recentSearchDataSource.getRecentSearchByIgIdAndType(igId, type); + if (recentSearch != null) { + recentSearchDataSource.deleteRecentSearch(recentSearch); + } + // notify on the main thread + appExecutors.mainThread().execute(() -> { + if (callback == null) return; + if (recentSearch == null) { + callback.onDataNotAvailable(); + return; + } + callback.onSuccess(null); + }); + }); + } + + public void deleteRecentSearch(@NonNull final RecentSearch recentSearch, + final RepositoryCallback callback) { + // request on the I/O thread + appExecutors.diskIO().execute(() -> { + + recentSearchDataSource.deleteRecentSearch(recentSearch); + // notify on the main thread + appExecutors.mainThread().execute(() -> { + if (callback == null) return; + callback.onSuccess(null); + }); + }); + } +} diff --git a/app/src/main/java/awais/instagrabber/fragments/FavoritesFragment.java b/app/src/main/java/awais/instagrabber/fragments/FavoritesFragment.java index 7be4156c..975357e8 100644 --- a/app/src/main/java/awais/instagrabber/fragments/FavoritesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/FavoritesFragment.java @@ -41,7 +41,9 @@ public class FavoritesFragment extends Fragment { @Override public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); - favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(getContext())); + final Context context = getContext(); + if (context == null) return; + favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(context)); } @NonNull diff --git a/app/src/main/java/awais/instagrabber/fragments/search/SearchCategoryFragment.java b/app/src/main/java/awais/instagrabber/fragments/search/SearchCategoryFragment.java new file mode 100644 index 00000000..89a2c7e9 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/fragments/search/SearchCategoryFragment.java @@ -0,0 +1,195 @@ +package awais.instagrabber.fragments.search; + +import android.content.Context; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + +import java.io.Serializable; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import awais.instagrabber.adapters.SearchItemsAdapter; +import awais.instagrabber.models.Resource; +import awais.instagrabber.models.enums.FavoriteType; +import awais.instagrabber.repositories.responses.search.SearchItem; +import awais.instagrabber.viewmodels.SearchFragmentViewModel; + +public class SearchCategoryFragment extends Fragment { + private static final String TAG = SearchCategoryFragment.class.getSimpleName(); + private static final String ARG_TYPE = "type"; + + + @Nullable + private SwipeRefreshLayout swipeRefreshLayout; + @Nullable + private RecyclerView list; + private SearchFragmentViewModel viewModel; + private FavoriteType type; + private SearchItemsAdapter searchItemsAdapter; + @Nullable + private OnSearchItemClickListener onSearchItemClickListener; + private boolean skipViewRefresh; + private String prevQuery; + + @NonNull + public static SearchCategoryFragment newInstance(@NonNull final FavoriteType type) { + final SearchCategoryFragment fragment = new SearchCategoryFragment(); + final Bundle args = new Bundle(); + args.putSerializable(ARG_TYPE, type); + fragment.setArguments(args); + return fragment; + } + + public SearchCategoryFragment() {} + + @Override + public void onAttach(@NonNull final Context context) { + super.onAttach(context); + final Fragment parentFragment = getParentFragment(); + if (!(parentFragment instanceof OnSearchItemClickListener)) return; + onSearchItemClickListener = (OnSearchItemClickListener) parentFragment; + } + + @Override + public void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final FragmentActivity fragmentActivity = getActivity(); + if (fragmentActivity == null) return; + viewModel = new ViewModelProvider(fragmentActivity).get(SearchFragmentViewModel.class); + final Bundle args = getArguments(); + if (args == null) { + Log.e(TAG, "onCreate: arguments are null"); + return; + } + final Serializable typeSerializable = args.getSerializable(ARG_TYPE); + if (!(typeSerializable instanceof FavoriteType)) { + Log.e(TAG, "onCreate: type not a FavoriteType"); + return; + } + type = (FavoriteType) typeSerializable; + } + + @Nullable + @Override + public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { + final Context context = getContext(); + if (context == null) return null; + skipViewRefresh = false; + if (swipeRefreshLayout != null) { + skipViewRefresh = true; + return swipeRefreshLayout; + } + swipeRefreshLayout = new SwipeRefreshLayout(context); + swipeRefreshLayout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + list = new RecyclerView(context); + list.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + swipeRefreshLayout.addView(list); + return swipeRefreshLayout; + } + + @Override + public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { + if (skipViewRefresh) return; + setupList(); + } + + @Override + public void onResume() { + super.onResume(); + // Log.d(TAG, "onResume: type: " + type); + setupObservers(); + final String currentQuery = viewModel.getQuery().getValue(); + if (prevQuery != null && currentQuery != null && !Objects.equals(prevQuery, currentQuery)) { + viewModel.search(currentQuery, type); + } + prevQuery = null; + } + + private void setupList() { + if (list == null || swipeRefreshLayout == null) return; + final Context context = getContext(); + if (context == null) return; + list.setLayoutManager(new LinearLayoutManager(context)); + searchItemsAdapter = new SearchItemsAdapter(onSearchItemClickListener); + list.setAdapter(searchItemsAdapter); + swipeRefreshLayout.setOnRefreshListener(() -> { + String currentQuery = viewModel.getQuery().getValue(); + if (currentQuery == null) currentQuery = ""; + viewModel.search(currentQuery, type); + }); + } + + private void setupObservers() { + viewModel.getQuery().observe(getViewLifecycleOwner(), q -> { + if (!isVisible() || Objects.equals(prevQuery, q)) return; + viewModel.search(q, type); + prevQuery = q; + }); + final LiveData>> resultsLiveData = getResultsLiveData(); + if (resultsLiveData != null) { + resultsLiveData.observe(getViewLifecycleOwner(), this::onResults); + } + } + + private void onResults(final Resource> listResource) { + if (listResource == null) return; + switch (listResource.status) { + case SUCCESS: + if (searchItemsAdapter != null) { + searchItemsAdapter.submitList(listResource.data); + } + if (swipeRefreshLayout != null) { + swipeRefreshLayout.setRefreshing(false); + } + break; + case ERROR: + if (searchItemsAdapter != null) { + searchItemsAdapter.submitList(Collections.emptyList()); + } + if (swipeRefreshLayout != null) { + swipeRefreshLayout.setRefreshing(false); + } + break; + case LOADING: + if (swipeRefreshLayout != null) { + swipeRefreshLayout.setRefreshing(true); + } + break; + } + } + + @Nullable + private LiveData>> getResultsLiveData() { + switch (type) { + case TOP: + return viewModel.getTopResults(); + case USER: + return viewModel.getUserResults(); + case HASHTAG: + return viewModel.getHashtagResults(); + case LOCATION: + return viewModel.getLocationResults(); + } + return null; + } + + public interface OnSearchItemClickListener { + void onSearchItemClick(SearchItem searchItem); + + void onSearchItemDelete(SearchItem searchItem); + } +} diff --git a/app/src/main/java/awais/instagrabber/fragments/search/SearchFragment.java b/app/src/main/java/awais/instagrabber/fragments/search/SearchFragment.java new file mode 100644 index 00000000..91ca1519 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/fragments/search/SearchFragment.java @@ -0,0 +1,245 @@ +package awais.instagrabber.fragments.search; + +import android.os.Bundle; +import android.text.Editable; +import android.text.TextUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.LinearLayoutCompat; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; +import androidx.navigation.NavController; +import androidx.navigation.fragment.NavHostFragment; + +import com.google.android.material.snackbar.Snackbar; +import com.google.android.material.tabs.TabLayoutMediator; +import com.google.android.material.textfield.TextInputLayout; + +import java.util.Arrays; +import java.util.List; + +import awais.instagrabber.R; +import awais.instagrabber.activities.MainActivity; +import awais.instagrabber.adapters.SearchCategoryAdapter; +import awais.instagrabber.customviews.helpers.TextWatcherAdapter; +import awais.instagrabber.databinding.FragmentSearchBinding; +import awais.instagrabber.models.Resource; +import awais.instagrabber.models.enums.FavoriteType; +import awais.instagrabber.repositories.responses.search.SearchItem; +import awais.instagrabber.viewmodels.SearchFragmentViewModel; + +public class SearchFragment extends Fragment implements SearchCategoryFragment.OnSearchItemClickListener { + private static final String TAG = SearchFragment.class.getSimpleName(); + private static final String QUERY = "query"; + + private FragmentSearchBinding binding; + private LinearLayoutCompat root; + private boolean shouldRefresh = true; + @Nullable + private TextInputLayout searchInputLayout; + @Nullable + private EditText searchInput; + @Nullable + private MainActivity mainActivity; + private SearchFragmentViewModel viewModel; + + private final TextWatcherAdapter textWatcher = new TextWatcherAdapter() { + @Override + public void afterTextChanged(final Editable s) { + if (s == null) return; + viewModel.submitQuery(s.toString().trim()); + } + }; + + @Override + public void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final FragmentActivity fragmentActivity = getActivity(); + if (!(fragmentActivity instanceof MainActivity)) return; + mainActivity = (MainActivity) fragmentActivity; + viewModel = new ViewModelProvider(mainActivity).get(SearchFragmentViewModel.class); + } + + @Nullable + @Override + public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { + if (root != null) { + shouldRefresh = false; + return root; + } + binding = FragmentSearchBinding.inflate(inflater, container, false); + root = binding.getRoot(); + return root; + } + + @Override + public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { + if (!shouldRefresh) return; + init(savedInstanceState); + shouldRefresh = false; + } + + @Override + public void onSaveInstanceState(@NonNull final Bundle outState) { + super.onSaveInstanceState(outState); + final String current = viewModel.getQuery().getValue(); + if (TextUtils.isEmpty(current)) return; + outState.putString(QUERY, current); + } + + @Override + public void onPause() { + super.onPause(); + if (mainActivity != null) { + mainActivity.hideSearchView(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (mainActivity != null) { + mainActivity.hideSearchView(); + } + if (searchInput != null) { + searchInput.removeTextChangedListener(textWatcher); + searchInput.setText(""); + } + } + + @Override + public void onResume() { + super.onResume(); + if (mainActivity != null) { + mainActivity.showSearchView(); + } + if (searchInputLayout != null) { + searchInputLayout.requestFocus(); + } + } + + private void init(@Nullable final Bundle savedInstanceState) { + if (mainActivity == null) return; + searchInputLayout = mainActivity.showSearchView(); + searchInput = searchInputLayout.getEditText(); + setupObservers(); + setupViewPager(); + setupSearchInput(savedInstanceState); + } + + private void setupObservers() { + viewModel.getQuery().observe(getViewLifecycleOwner(), q -> {}); // need to observe, so that getQuery returns proper value + } + + private void setupSearchInput(@Nullable final Bundle savedInstanceState) { + if (searchInput == null) return; + searchInput.removeTextChangedListener(textWatcher); // make sure we add only 1 instance of textWatcher + searchInput.addTextChangedListener(textWatcher); + boolean triggerEmptyQuery = true; + if (savedInstanceState != null) { + final String savedQuery = savedInstanceState.getString(QUERY); + if (TextUtils.isEmpty(savedQuery)) return; + searchInput.setText(savedQuery); + triggerEmptyQuery = false; + } + searchInput.requestFocus(); + if (triggerEmptyQuery) { + viewModel.submitQuery(""); + } + } + + private void setupViewPager() { + binding.pager.setSaveEnabled(false); + final List categories = Arrays.asList(FavoriteType.values()); + binding.pager.setAdapter(new SearchCategoryAdapter(this, categories)); + final TabLayoutMediator mediator = new TabLayoutMediator(binding.tabLayout, binding.pager, (tab, position) -> { + try { + final FavoriteType type = categories.get(position); + final int resId; + switch (type) { + case TOP: + resId = R.string.top; + break; + case USER: + resId = R.string.accounts; + break; + case HASHTAG: + resId = R.string.hashtags; + break; + case LOCATION: + resId = R.string.locations; + break; + default: + throw new IllegalStateException("Unexpected value: " + type); + } + tab.setText(resId); + } catch (Exception e) { + Log.e(TAG, "setupViewPager: ", e); + } + }); + mediator.attach(); + } + + @Override + public void onSearchItemClick(final SearchItem searchItem) { + if (searchItem == null) return; + final FavoriteType type = searchItem.getType(); + if (type == null) return; + try { + if (!searchItem.isFavorite()) { + viewModel.saveToRecentSearches(searchItem); // insert or update recent + } + final NavController navController = NavHostFragment.findNavController(this); + final Bundle bundle = new Bundle(); + switch (type) { + case USER: + bundle.putString("username", searchItem.getUser().getUsername()); + navController.navigate(R.id.action_global_profileFragment, bundle); + break; + case HASHTAG: + bundle.putString("hashtag", searchItem.getHashtag().getName()); + navController.navigate(R.id.action_global_hashTagFragment, bundle); + break; + case LOCATION: + bundle.putLong("locationId", searchItem.getPlace().getLocation().getPk()); + navController.navigate(R.id.action_global_locationFragment, bundle); + break; + } + } catch (Exception e) { + Log.e(TAG, "onSearchItemClick: ", e); + } + } + + @Override + public void onSearchItemDelete(final SearchItem searchItem) { + final LiveData> liveData = viewModel.deleteRecentSearch(searchItem); + if (liveData == null) return; + liveData.observe(getViewLifecycleOwner(), new Observer>() { + @Override + public void onChanged(final Resource resource) { + if (resource == null) return; + switch (resource.status) { + case SUCCESS: + viewModel.search("", FavoriteType.TOP); + liveData.removeObserver(this); + break; + case ERROR: + Snackbar.make(binding.getRoot(), R.string.error, Snackbar.LENGTH_SHORT); + liveData.removeObserver(this); + break; + case LOADING: + break; + } + } + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/models/enums/FavoriteType.java b/app/src/main/java/awais/instagrabber/models/enums/FavoriteType.java index cdf926a9..536ac05f 100644 --- a/app/src/main/java/awais/instagrabber/models/enums/FavoriteType.java +++ b/app/src/main/java/awais/instagrabber/models/enums/FavoriteType.java @@ -1,7 +1,8 @@ package awais.instagrabber.models.enums; public enum FavoriteType { + TOP, // used just for searching USER, HASHTAG, - LOCATION + LOCATION, } diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/Hashtag.java b/app/src/main/java/awais/instagrabber/repositories/responses/Hashtag.java index 2ce08eb5..f1ef47b3 100755 --- a/app/src/main/java/awais/instagrabber/repositories/responses/Hashtag.java +++ b/app/src/main/java/awais/instagrabber/repositories/responses/Hashtag.java @@ -1,6 +1,7 @@ package awais.instagrabber.repositories.responses; import java.io.Serializable; +import java.util.Objects; import awais.instagrabber.models.enums.FollowingType; @@ -42,4 +43,21 @@ public final class Hashtag implements Serializable { public String getSubtitle() { return searchResultSubtitle; } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final Hashtag hashtag = (Hashtag) o; + return mediaCount == hashtag.mediaCount && + following == hashtag.following && + Objects.equals(id, hashtag.id) && + Objects.equals(name, hashtag.name) && + Objects.equals(searchResultSubtitle, hashtag.searchResultSubtitle); + } + + @Override + public int hashCode() { + return Objects.hash(following, mediaCount, id, name, searchResultSubtitle); + } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/Place.java b/app/src/main/java/awais/instagrabber/repositories/responses/Place.java index c72e1c41..3f10ffd4 100644 --- a/app/src/main/java/awais/instagrabber/repositories/responses/Place.java +++ b/app/src/main/java/awais/instagrabber/repositories/responses/Place.java @@ -1,5 +1,7 @@ package awais.instagrabber.repositories.responses; +import java.util.Objects; + public class Place { private final Location location; // for search @@ -40,4 +42,21 @@ public class Place { public String getStatus() { return status; } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final Place place = (Place) o; + return Objects.equals(location, place.location) && + Objects.equals(title, place.title) && + Objects.equals(subtitle, place.subtitle) && + Objects.equals(slug, place.slug) && + Objects.equals(status, place.status); + } + + @Override + public int hashCode() { + return Objects.hash(location, title, subtitle, slug, status); + } } diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/search/SearchItem.java b/app/src/main/java/awais/instagrabber/repositories/responses/search/SearchItem.java index 4aca79da..296e9c84 100644 --- a/app/src/main/java/awais/instagrabber/repositories/responses/search/SearchItem.java +++ b/app/src/main/java/awais/instagrabber/repositories/responses/search/SearchItem.java @@ -1,15 +1,34 @@ package awais.instagrabber.repositories.responses.search; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import awais.instagrabber.db.entities.Favorite; +import awais.instagrabber.db.entities.RecentSearch; +import awais.instagrabber.models.enums.FavoriteType; import awais.instagrabber.repositories.responses.Hashtag; +import awais.instagrabber.repositories.responses.Location; import awais.instagrabber.repositories.responses.Place; import awais.instagrabber.repositories.responses.User; public class SearchItem { + private static final String TAG = SearchItem.class.getSimpleName(); + private final User user; private final Place place; private final Hashtag hashtag; private final int position; + private boolean isRecent = false; + private boolean isFavorite = false; + public SearchItem(final User user, final Place place, final Hashtag hashtag, @@ -35,4 +54,221 @@ public class SearchItem { public int getPosition() { return position; } + + public boolean isRecent() { + return isRecent; + } + + public void setRecent(final boolean recent) { + isRecent = recent; + } + + public boolean isFavorite() { + return isFavorite; + } + + public void setFavorite(final boolean favorite) { + isFavorite = favorite; + } + + @Nullable + public FavoriteType getType() { + if (user != null) { + return FavoriteType.USER; + } + if (hashtag != null) { + return FavoriteType.HASHTAG; + } + if (place != null) { + return FavoriteType.LOCATION; + } + return null; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final SearchItem that = (SearchItem) o; + return Objects.equals(user, that.user) && + Objects.equals(place, that.place) && + Objects.equals(hashtag, that.hashtag); + } + + @Override + public int hashCode() { + return Objects.hash(user, place, hashtag); + } + + @NonNull + @Override + public String toString() { + return "SearchItem{" + + "user=" + user + + ", place=" + place + + ", hashtag=" + hashtag + + ", position=" + position + + ", isRecent=" + isRecent + + '}'; + } + + @NonNull + public static List fromRecentSearch(final List recentSearches) { + if (recentSearches == null) return Collections.emptyList(); + return recentSearches.stream() + .map(SearchItem::fromRecentSearch) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + @Nullable + private static SearchItem fromRecentSearch(final RecentSearch recentSearch) { + if (recentSearch == null) return null; + try { + final FavoriteType type = recentSearch.getType(); + final SearchItem searchItem; + switch (type) { + case USER: + searchItem = new SearchItem(getUser(recentSearch), null, null, 0); + break; + case HASHTAG: + searchItem = new SearchItem(null, null, getHashtag(recentSearch), 0); + break; + case LOCATION: + searchItem = new SearchItem(null, getPlace(recentSearch), null, 0); + break; + default: + return null; + } + searchItem.setRecent(true); + return searchItem; + } catch (Exception e) { + Log.e(TAG, "fromRecentSearch: ", e); + } + return null; + } + + @NonNull + private static User getUser(@NonNull final RecentSearch recentSearch) { + return new User( + Long.parseLong(recentSearch.getIgId()), + recentSearch.getUsername(), + recentSearch.getName(), + false, + recentSearch.getPicUrl(), + null, null, false, false, false, false, false, + null, null, 0, 0, 0, 0, null, null, + 0, null, null, null, null, null, null + ); + } + + @NonNull + private static Hashtag getHashtag(@NonNull final RecentSearch recentSearch) { + return new Hashtag( + recentSearch.getIgId(), + recentSearch.getName(), + 0, + null, + null + ); + } + + @NonNull + private static Place getPlace(@NonNull final RecentSearch recentSearch) { + final Location location = new Location( + Long.parseLong(recentSearch.getIgId()), + recentSearch.getName(), + recentSearch.getName(), + null, null, 0, 0 + ); + return new Place( + location, + recentSearch.getName(), + null, + null, + null + ); + } + + public static List fromFavorite(final List favorites) { + if (favorites == null) { + return Collections.emptyList(); + } + return favorites.stream() + .map(SearchItem::fromFavorite) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + @Nullable + private static SearchItem fromFavorite(final Favorite favorite) { + if (favorite == null) return null; + final FavoriteType type = favorite.getType(); + if (type == null) return null; + final SearchItem searchItem; + switch (type) { + case USER: + searchItem = new SearchItem(getUser(favorite), null, null, 0); + break; + case HASHTAG: + searchItem = new SearchItem(null, null, getHashtag(favorite), 0); + break; + case LOCATION: + final Place place = getPlace(favorite); + if (place == null) return null; + searchItem = new SearchItem(null, place, null, 0); + break; + default: + return null; + } + searchItem.setFavorite(true); + return searchItem; + } + + @NonNull + private static User getUser(@NonNull final Favorite favorite) { + return new User( + 0, + favorite.getQuery(), + favorite.getDisplayName(), + false, + favorite.getPicUrl(), + null, null, false, false, false, false, false, + null, null, 0, 0, 0, 0, null, null, + 0, null, null, null, null, null, null + ); + } + + @NonNull + private static Hashtag getHashtag(@NonNull final Favorite favorite) { + return new Hashtag( + "0", + favorite.getQuery(), + 0, + null, + null + ); + } + + @Nullable + private static Place getPlace(@NonNull final Favorite favorite) { + try { + final Location location = new Location( + Long.parseLong(favorite.getQuery()), + favorite.getDisplayName(), + favorite.getDisplayName(), + null, null, 0, 0 + ); + return new Place( + location, + favorite.getDisplayName(), + null, + null, + null + ); + } catch (Exception e) { + Log.e(TAG, "getPlace: ", e); + return null; + } + } } diff --git a/app/src/main/java/awais/instagrabber/viewmodels/AppStateViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/AppStateViewModel.java index ab1cbabb..84fd2ba9 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/AppStateViewModel.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/AppStateViewModel.java @@ -8,8 +8,6 @@ import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; -import awais.instagrabber.db.datasources.AccountDataSource; -import awais.instagrabber.db.repositories.AccountRepository; import awais.instagrabber.repositories.responses.User; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; @@ -23,20 +21,18 @@ public class AppStateViewModel extends AndroidViewModel { private static final String TAG = AppStateViewModel.class.getSimpleName(); private final String cookie; - private final boolean isLoggedIn; private final MutableLiveData currentUser = new MutableLiveData<>(); - private AccountRepository accountRepository; private UserService userService; public AppStateViewModel(@NonNull final Application application) { super(application); // Log.d(TAG, "AppStateViewModel: constructor"); cookie = settingsHelper.getString(Constants.COOKIE); - isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) > 0; + final boolean isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != 0; if (!isLoggedIn) return; userService = UserService.getInstance(); - accountRepository = AccountRepository.getInstance(AccountDataSource.getInstance(application)); + // final AccountRepository accountRepository = AccountRepository.getInstance(AccountDataSource.getInstance(application)); fetchProfileDetails(); } @@ -50,6 +46,7 @@ public class AppStateViewModel extends AndroidViewModel { private void fetchProfileDetails() { final long uid = CookieUtils.getUserIdFromCookie(cookie); + if (userService == null) return; userService.getUserInfo(uid, new ServiceCallback() { @Override public void onSuccess(final User user) { diff --git a/app/src/main/java/awais/instagrabber/viewmodels/SearchFragmentViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/SearchFragmentViewModel.java new file mode 100644 index 00000000..229cc9c1 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/viewmodels/SearchFragmentViewModel.java @@ -0,0 +1,352 @@ +package awais.instagrabber.viewmodels; + +import android.app.Application; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.SettableFuture; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import awais.instagrabber.db.datasources.FavoriteDataSource; +import awais.instagrabber.db.datasources.RecentSearchDataSource; +import awais.instagrabber.db.entities.Favorite; +import awais.instagrabber.db.entities.RecentSearch; +import awais.instagrabber.db.repositories.FavoriteRepository; +import awais.instagrabber.db.repositories.RecentSearchRepository; +import awais.instagrabber.db.repositories.RepositoryCallback; +import awais.instagrabber.models.Resource; +import awais.instagrabber.models.enums.FavoriteType; +import awais.instagrabber.repositories.responses.search.SearchItem; +import awais.instagrabber.repositories.responses.search.SearchResponse; +import awais.instagrabber.utils.AppExecutors; +import awais.instagrabber.utils.Constants; +import awais.instagrabber.utils.CookieUtils; +import awais.instagrabber.utils.Debouncer; +import awais.instagrabber.utils.TextUtils; +import awais.instagrabber.webservices.SearchService; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +import static androidx.lifecycle.Transformations.distinctUntilChanged; +import static awais.instagrabber.utils.Utils.settingsHelper; + +public class SearchFragmentViewModel extends AppStateViewModel { + private static final String TAG = SearchFragmentViewModel.class.getSimpleName(); + private static final String QUERY = "query"; + + private final MutableLiveData query = new MutableLiveData<>(); + private final MutableLiveData>> topResults = new MutableLiveData<>(); + private final MutableLiveData>> userResults = new MutableLiveData<>(); + private final MutableLiveData>> hashtagResults = new MutableLiveData<>(); + private final MutableLiveData>> locationResults = new MutableLiveData<>(); + + private final SearchService searchService; + private final Debouncer searchDebouncer; + private final boolean isLoggedIn; + private final LiveData distinctQuery; + private final RecentSearchRepository recentSearchRepository; + private final FavoriteRepository favoriteRepository; + + private String tempQuery; + + public SearchFragmentViewModel(@NonNull final Application application) { + super(application); + final String cookie = settingsHelper.getString(Constants.COOKIE); + isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != 0; + final Debouncer.Callback searchCallback = new Debouncer.Callback() { + @Override + public void call(final String key) { + if (tempQuery == null) return; + query.postValue(tempQuery); + } + + @Override + public void onError(final Throwable t) { + Log.e(TAG, "onError: ", t); + } + }; + searchDebouncer = new Debouncer<>(searchCallback, 500); + distinctQuery = distinctUntilChanged(query); + searchService = SearchService.getInstance(); + recentSearchRepository = RecentSearchRepository.getInstance(RecentSearchDataSource.getInstance(application)); + favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(application)); + } + + public LiveData getQuery() { + return distinctQuery; + } + + public LiveData>> getTopResults() { + return topResults; + } + + public LiveData>> getUserResults() { + return userResults; + } + + public LiveData>> getHashtagResults() { + return hashtagResults; + } + + public LiveData>> getLocationResults() { + return locationResults; + } + + public void submitQuery(@Nullable final String query) { + String localQuery = query; + if (query == null) { + localQuery = ""; + } + if (tempQuery != null && Objects.equals(localQuery.toLowerCase(), tempQuery.toLowerCase())) return; + tempQuery = query; + if (TextUtils.isEmpty(query)) { + // If empty immediately post it + searchDebouncer.cancel(QUERY); + this.query.postValue(""); + return; + } + searchDebouncer.call(QUERY); + } + + public void search(@NonNull final String query, + @NonNull final FavoriteType type) { + final MutableLiveData>> liveData = getLiveDataByType(type); + if (liveData == null) return; + if (TextUtils.isEmpty(query)) { + if (type != FavoriteType.TOP) { + liveData.postValue(Resource.success(Collections.emptyList())); + return; + } + showRecentSearchesAndFavorites(); + return; + } + if (query.equals("@") || query.equals("#")) return; + final String c; + switch (type) { + case TOP: + c = "blended"; + break; + case USER: + c = "user"; + break; + case HASHTAG: + c = "hashtag"; + break; + case LOCATION: + c = "place"; + break; + default: + return; + } + liveData.postValue(Resource.loading(null)); + final Call request = searchService.search(isLoggedIn, query, c); + request.enqueue(new Callback() { + @Override + public void onResponse(@NonNull final Call call, + @NonNull final Response response) { + if (!response.isSuccessful()) { + sendErrorResponse(type); + return; + } + final SearchResponse body = response.body(); + if (body == null) { + sendErrorResponse(type); + return; + } + parseResponse(body, type); + } + + @Override + public void onFailure(@NonNull final Call call, + @NonNull final Throwable t) { + Log.e(TAG, "onFailure: ", t); + } + }); + } + + private void showRecentSearchesAndFavorites() { + final SettableFuture> recentResultsFuture = SettableFuture.create(); + final SettableFuture> favoritesFuture = SettableFuture.create(); + recentSearchRepository.getAllRecentSearches(new RepositoryCallback>() { + @Override + public void onSuccess(final List result) { + recentResultsFuture.set(result); + } + + @Override + public void onDataNotAvailable() { + recentResultsFuture.set(Collections.emptyList()); + } + }); + favoriteRepository.getAllFavorites(new RepositoryCallback>() { + @Override + public void onSuccess(final List result) { + favoritesFuture.set(result); + } + + @Override + public void onDataNotAvailable() { + favoritesFuture.set(Collections.emptyList()); + } + }); + //noinspection UnstableApiUsage + final ListenableFuture>> listenableFuture = Futures.allAsList(recentResultsFuture, favoritesFuture); + Futures.addCallback(listenableFuture, new FutureCallback>>() { + @Override + public void onSuccess(@Nullable final List> result) { + if (!TextUtils.isEmpty(tempQuery)) return; // Make sure user has not entered anything before updating results + if (result == null) { + topResults.postValue(Resource.success(Collections.emptyList())); + return; + } + try { + //noinspection unchecked + topResults.postValue(Resource.success( + ImmutableList.builder() + .addAll(SearchItem.fromRecentSearch((List) result.get(0))) + .addAll(SearchItem.fromFavorite((List) result.get(1))) + .build() + )); + } catch (Exception e) { + Log.e(TAG, "onSuccess: ", e); + topResults.postValue(Resource.success(Collections.emptyList())); + } + } + + @Override + public void onFailure(@NonNull final Throwable t) { + if (!TextUtils.isEmpty(tempQuery)) return; + topResults.postValue(Resource.success(Collections.emptyList())); + Log.e(TAG, "onFailure: ", t); + } + }, AppExecutors.getInstance().mainThread()); + } + + private void sendErrorResponse(@NonNull final FavoriteType type) { + final MutableLiveData>> liveData = getLiveDataByType(type); + if (liveData == null) return; + liveData.postValue(Resource.error(null, Collections.emptyList())); + } + + private MutableLiveData>> getLiveDataByType(@NonNull final FavoriteType type) { + final MutableLiveData>> liveData; + switch (type) { + case TOP: + liveData = topResults; + break; + case USER: + liveData = userResults; + break; + case HASHTAG: + liveData = hashtagResults; + break; + case LOCATION: + liveData = locationResults; + break; + default: + return null; + } + return liveData; + } + + private void parseResponse(@NonNull final SearchResponse body, + @NonNull final FavoriteType type) { + final MutableLiveData>> liveData = getLiveDataByType(type); + if (liveData == null) return; + if (isLoggedIn) { + if (body.getList() == null) { + liveData.postValue(Resource.success(Collections.emptyList())); + return; + } + if (type == FavoriteType.HASHTAG || type == FavoriteType.LOCATION) { + liveData.postValue(Resource.success(body.getList() + .stream() + .filter(i -> i.getUser() == null) + .collect(Collectors.toList()))); + return; + } + liveData.postValue(Resource.success(body.getList())); + return; + } + + // anonymous + final List list; + switch (type) { + case TOP: + list = ImmutableList + .builder() + .addAll(body.getUsers()) + .addAll(body.getHashtags()) + .addAll(body.getPlaces()) + .build(); + break; + case USER: + list = body.getUsers(); + break; + case HASHTAG: + list = body.getHashtags(); + break; + case LOCATION: + list = body.getPlaces(); + break; + default: + return; + } + liveData.postValue(Resource.success(list)); + } + + public void saveToRecentSearches(final SearchItem searchItem) { + if (searchItem == null) return; + try { + final RecentSearch recentSearch = RecentSearch.fromSearchItem(searchItem); + if (recentSearch == null) return; + recentSearchRepository.insertOrUpdateRecentSearch(recentSearch, new RepositoryCallback() { + @Override + public void onSuccess(final Void result) { + // Log.d(TAG, "onSuccess: inserted recent: " + recentSearch); + } + + @Override + public void onDataNotAvailable() {} + }); + } catch (Exception e) { + Log.e(TAG, "saveToRecentSearches: ", e); + } + } + + @Nullable + public LiveData> deleteRecentSearch(final SearchItem searchItem) { + if (searchItem == null || !searchItem.isRecent()) return null; + final RecentSearch recentSearch = RecentSearch.fromSearchItem(searchItem); + if (recentSearch == null) return null; + final MutableLiveData> data = new MutableLiveData<>(); + data.postValue(Resource.loading(null)); + recentSearchRepository.deleteRecentSearchByIgIdAndType(recentSearch.getIgId(), recentSearch.getType(), new RepositoryCallback() { + @Override + public void onSuccess(final Void result) { + // Log.d(TAG, "onSuccess: deleted"); + data.postValue(Resource.success(new Object())); + } + + @Override + public void onDataNotAvailable() { + // Log.e(TAG, "onDataNotAvailable: not deleted"); + data.postValue(Resource.error("Error deleting recent item", null)); + } + }); + return data; + } +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 97bcb0bb..8fadd4a2 100755 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -32,7 +32,29 @@ android:background="?attr/colorSurface" app:layout_collapseMode="pin" app:title="@string/app_name" - tools:menu="@menu/main_menu" /> + tools:menu="@menu/main_menu"> + + + + + + diff --git a/app/src/main/res/layout/fragment_favorites.xml b/app/src/main/res/layout/fragment_favorites.xml index 05a808d6..d437e6e7 100644 --- a/app/src/main/res/layout/fragment_favorites.xml +++ b/app/src/main/res/layout/fragment_favorites.xml @@ -4,4 +4,4 @@ android:id="@+id/favorite_list" android:layout_width="match_parent" android:layout_height="match_parent" - tools:listitem="@layout/item_suggestion" /> \ No newline at end of file + tools:listitem="@layout/item_search_result" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml new file mode 100644 index 00000000..39c24c3c --- /dev/null +++ b/app/src/main/res/layout/fragment_search.xml @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_suggestion.xml b/app/src/main/res/layout/item_search_result.xml old mode 100755 new mode 100644 similarity index 63% rename from app/src/main/res/layout/item_suggestion.xml rename to app/src/main/res/layout/item_search_result.xml index 8020090a..e6987925 --- a/app/src/main/res/layout/item_suggestion.xml +++ b/app/src/main/res/layout/item_search_result.xml @@ -13,7 +13,7 @@ android:paddingBottom="8dp"> + + \ No newline at end of file diff --git a/app/src/main/res/menu/main_menu.xml b/app/src/main/res/menu/main_menu.xml index 2ad8da42..b73c4b97 100644 --- a/app/src/main/res/menu/main_menu.xml +++ b/app/src/main/res/menu/main_menu.xml @@ -1,25 +1,10 @@ - - - - - - - - - - - - - - + android:title="@string/search" + app:showAsAction="always" /> \ No newline at end of file diff --git a/app/src/main/res/navigation/direct_messages_nav_graph.xml b/app/src/main/res/navigation/direct_messages_nav_graph.xml index 1cf39e25..51dfd27b 100644 --- a/app/src/main/res/navigation/direct_messages_nav_graph.xml +++ b/app/src/main/res/navigation/direct_messages_nav_graph.xml @@ -96,6 +96,10 @@ android:id="@+id/action_global_user_search" app:destination="@id/user_search_nav_graph" /> + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/discover_nav_graph.xml b/app/src/main/res/navigation/discover_nav_graph.xml index 537e5eae..2c2d0f08 100644 --- a/app/src/main/res/navigation/discover_nav_graph.xml +++ b/app/src/main/res/navigation/discover_nav_graph.xml @@ -95,6 +95,10 @@ app:argType="long" /> + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/favorites_nav_graph.xml b/app/src/main/res/navigation/favorites_nav_graph.xml index 1a288bf1..a1a218ff 100644 --- a/app/src/main/res/navigation/favorites_nav_graph.xml +++ b/app/src/main/res/navigation/favorites_nav_graph.xml @@ -1,6 +1,7 @@ @@ -36,8 +37,18 @@ app:argType="long" /> + + + android:label="@string/title_favorites" + tools:layout="@layout/fragment_favorites" /> + \ No newline at end of file diff --git a/app/src/main/res/navigation/feed_nav_graph.xml b/app/src/main/res/navigation/feed_nav_graph.xml index f9e26cc8..19dec632 100644 --- a/app/src/main/res/navigation/feed_nav_graph.xml +++ b/app/src/main/res/navigation/feed_nav_graph.xml @@ -106,6 +106,10 @@ app:nullable="false" /> + + - + \ No newline at end of file diff --git a/app/src/main/res/navigation/hashtag_nav_graph.xml b/app/src/main/res/navigation/hashtag_nav_graph.xml index b4d6d31a..7446c366 100644 --- a/app/src/main/res/navigation/hashtag_nav_graph.xml +++ b/app/src/main/res/navigation/hashtag_nav_graph.xml @@ -65,6 +65,10 @@ app:argType="long" /> + + + diff --git a/app/src/main/res/navigation/location_nav_graph.xml b/app/src/main/res/navigation/location_nav_graph.xml index acc33413..3761330f 100644 --- a/app/src/main/res/navigation/location_nav_graph.xml +++ b/app/src/main/res/navigation/location_nav_graph.xml @@ -66,6 +66,10 @@ app:nullable="false" /> + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/profile_nav_graph.xml b/app/src/main/res/navigation/profile_nav_graph.xml index a07c4df1..4bc8c444 100644 --- a/app/src/main/res/navigation/profile_nav_graph.xml +++ b/app/src/main/res/navigation/profile_nav_graph.xml @@ -112,6 +112,10 @@ app:argType="boolean" /> + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9cf27407..8530174f 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -493,4 +493,7 @@ If saved, all DM related features will be disabled on next launch Copy caption Copy reply + Top + Recent + Clear diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index f31e985d..c0d79025 100755 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -225,4 +225,13 @@ 0dp 0dp + + + + From 548582a6d2780a1918a603cb22e8d729d4caca14 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Wed, 14 Apr 2021 01:02:42 +0900 Subject: [PATCH 42/95] Fix options menu item background in dark black theme. Fixes https://github.com/austinhuang0131/barinsta/issues/1076 --- app/src/main/res/values/themes.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index a0e7a3b8..28de10d8 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -126,7 +126,7 @@ @style/Widget.Dialog.Dark.Black @style/Widget.AlertDialog.Dark.Black @style/Widget.AppCompat.CompoundButton.Switch.Dark.Black - @style/Widget.AppCompat.ListView.DropDown.Dark.Black + @style/PreferenceFragmentCompatStyle.Dark.Black @style/Widget.MaterialComponents.AppBarLayout.Primary @color/grey_600 From ea2956f3caa3080a3c3cc8385dea775bd8165627 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Wed, 14 Apr 2021 01:08:08 +0900 Subject: [PATCH 43/95] Add state saved check before show. Fixes https://github.com/austinhuang0131/barinsta/issues/1071. --- .../webservices/interceptors/IgErrorsInterceptor.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/webservices/interceptors/IgErrorsInterceptor.java b/app/src/main/java/awais/instagrabber/webservices/interceptors/IgErrorsInterceptor.java index e27eeae2..01cec822 100644 --- a/app/src/main/java/awais/instagrabber/webservices/interceptors/IgErrorsInterceptor.java +++ b/app/src/main/java/awais/instagrabber/webservices/interceptors/IgErrorsInterceptor.java @@ -5,6 +5,7 @@ import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.StringRes; +import androidx.fragment.app.FragmentManager; import com.google.android.material.snackbar.Snackbar; @@ -59,7 +60,7 @@ public class IgErrorsInterceptor implements Interceptor { return; case 302: // redirect final String location = response.header("location"); - if (location.equals("https://www.instagram.com/accounts/login/")) { + if (location != null && location.equals("https://www.instagram.com/accounts/login/")) { // rate limited showErrorDialog(R.string.rate_limit); } @@ -70,7 +71,7 @@ public class IgErrorsInterceptor implements Interceptor { try { final String bodyString = body.string(); final JSONObject jsonObject = new JSONObject(bodyString); - String message = jsonObject.optString("message", null); + String message = jsonObject.optString("message"); if (!TextUtils.isEmpty(message)) { message = message.toLowerCase(); switch (message) { @@ -91,7 +92,7 @@ public class IgErrorsInterceptor implements Interceptor { return; } } - final String errorType = jsonObject.optString("error_type", null); + final String errorType = jsonObject.optString("error_type"); if (TextUtils.isEmpty(errorType)) return; if (errorType.equals("sentry_block")) { showErrorDialog(R.string.sentry_block); @@ -127,7 +128,9 @@ public class IgErrorsInterceptor implements Interceptor { 0, 0 ); - dialogFragment.show(mainActivity.getSupportFragmentManager(), "network_error_dialog"); + final FragmentManager fragmentManager = mainActivity.getSupportFragmentManager(); + if (fragmentManager.isStateSaved()) return; + dialogFragment.show(fragmentManager, "network_error_dialog"); } public void destroy() { From af3670e3ece32aa1af2848534a1bc5c01dd2a835 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Wed, 14 Apr 2021 01:16:32 +0900 Subject: [PATCH 44/95] Add fragment manager destroyed checks. Fixes https://github.com/austinhuang0131/barinsta/issues/1068 --- .../instagrabber/asyncs/PostFetcher.java | 9 +-- .../fragments/HashTagFragment.java | 13 ++-- .../fragments/LocationFragment.java | 65 ++++++++++--------- 3 files changed, 47 insertions(+), 40 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/asyncs/PostFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/PostFetcher.java index ddd670f1..c0e63ea6 100755 --- a/app/src/main/java/awais/instagrabber/asyncs/PostFetcher.java +++ b/app/src/main/java/awais/instagrabber/asyncs/PostFetcher.java @@ -12,9 +12,6 @@ import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.utils.NetworkUtils; import awais.instagrabber.utils.ResponseBodyUtils; -//import awaisomereport.LogCollector; - -//import static awais.instagrabber.utils.Utils.logCollector; public final class PostFetcher extends AsyncTask { private static final String TAG = "PostFetcher"; @@ -136,9 +133,9 @@ public final class PostFetcher extends AsyncTask { return ResponseBodyUtils.parseGraphQLItem(media, null); } } catch (Exception e) { -// if (logCollector != null) { -// logCollector.appendException(e, LogCollector.LogFile.ASYNC_POST_FETCHER, "doInBackground"); -// } + // if (logCollector != null) { + // logCollector.appendException(e, LogCollector.LogFile.ASYNC_POST_FETCHER, "doInBackground"); + // } Log.e(TAG, "Error fetching post", e); } finally { if (conn != null) { diff --git a/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java b/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java index 81530dc5..c620f4f2 100644 --- a/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java @@ -26,6 +26,7 @@ import androidx.appcompat.app.ActionBar; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.content.PermissionChecker; import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; import androidx.navigation.NavController; import androidx.navigation.NavDirections; import androidx.navigation.fragment.NavHostFragment; @@ -60,6 +61,7 @@ import awais.instagrabber.repositories.requests.StoryViewerOptions; import awais.instagrabber.repositories.responses.Hashtag; import awais.instagrabber.repositories.responses.Location; import awais.instagrabber.repositories.responses.Media; +import awais.instagrabber.repositories.responses.User; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.DownloadUtils; @@ -213,7 +215,9 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe final View mainPostImage, final int position) { if (opening) return; - if (TextUtils.isEmpty(feedModel.getUser().getUsername())) { + final User user = feedModel.getUser(); + if (user == null) return; + if (TextUtils.isEmpty(user.getUsername())) { opening = true; new PostFetcher(feedModel.getCode(), newFeedModel -> { opening = false; @@ -231,7 +235,9 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe builder.setSharedProfilePicElement(profilePicView) .setSharedMainPostElement(mainPostImage); } - builder.build().show(getChildFragmentManager(), "post_view"); + final FragmentManager fragmentManager = getChildFragmentManager(); + if (fragmentManager.isDestroyed()) return; + builder.build().show(fragmentManager, "post_view"); opening = false; } }; @@ -403,8 +409,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe try { Toast.makeText(getContext(), R.string.error_loading_hashtag, Toast.LENGTH_SHORT).show(); binding.swipeRefreshLayout.setEnabled(false); - } - catch (Exception ignored) {} + } catch (Exception ignored) {} return; } setTitle(); diff --git a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java index c42809c5..75690dd0 100644 --- a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java @@ -24,6 +24,7 @@ import androidx.appcompat.app.ActionBar; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.content.PermissionChecker; import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; import androidx.navigation.NavController; import androidx.navigation.NavDirections; import androidx.navigation.fragment.NavHostFragment; @@ -56,6 +57,7 @@ import awais.instagrabber.models.enums.FavoriteType; import awais.instagrabber.repositories.requests.StoryViewerOptions; import awais.instagrabber.repositories.responses.Location; import awais.instagrabber.repositories.responses.Media; +import awais.instagrabber.repositories.responses.User; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.DownloadUtils; @@ -204,7 +206,9 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR final View mainPostImage, final int position) { if (opening) return; - if (TextUtils.isEmpty(feedModel.getUser().getUsername())) { + final User user = feedModel.getUser(); + if (user == null) return; + if (TextUtils.isEmpty(user.getUsername())) { opening = true; new PostFetcher(feedModel.getCode(), newFeedModel -> { opening = false; @@ -223,7 +227,9 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR builder.setSharedProfilePicElement(profilePicView) .setSharedMainPostElement(mainPostImage); } - builder.build().show(getChildFragmentManager(), "post_view"); + final FragmentManager fragmentManager = getChildFragmentManager(); + if (fragmentManager.isDestroyed()) return; + builder.build().show(fragmentManager, "post_view"); opening = false; } }; @@ -399,8 +405,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR try { Toast.makeText(getContext(), R.string.error_loading_location, Toast.LENGTH_SHORT).show(); binding.swipeRefreshLayout.setEnabled(false); - } - catch (Exception ignored) {} + } catch (Exception ignored) {} return; } setTitle(); @@ -409,16 +414,16 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR final long locationId = locationModel.getPk(); // binding.swipeRefreshLayout.setRefreshing(true); locationDetailsBinding.mainLocationImage.setImageURI("res:/" + R.drawable.ic_location); -// final String postCount = String.valueOf(locationModel.getCount()); -// final SpannableStringBuilder span = new SpannableStringBuilder(getResources().getQuantityString(R.plurals.main_posts_count_inline, -// locationModel.getPostCount() > 2000000000L -// ? 2000000000 -// : locationModel.getPostCount().intValue(), -// postCount)); -// span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0); -// span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0); -// locationDetailsBinding.mainLocPostCount.setText(span); -// locationDetailsBinding.mainLocPostCount.setVisibility(View.VISIBLE); + // final String postCount = String.valueOf(locationModel.getCount()); + // final SpannableStringBuilder span = new SpannableStringBuilder(getResources().getQuantityString(R.plurals.main_posts_count_inline, + // locationModel.getPostCount() > 2000000000L + // ? 2000000000 + // : locationModel.getPostCount().intValue(), + // postCount)); + // span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0); + // span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0); + // locationDetailsBinding.mainLocPostCount.setText(span); + // locationDetailsBinding.mainLocPostCount.setVisibility(View.VISIBLE); locationDetailsBinding.locationFullName.setText(locationModel.getName()); CharSequence biography = locationModel.getAddress() + "\n" + locationModel.getCity(); // binding.locationBiography.setCaptionIsExpandable(true); @@ -431,22 +436,22 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR } else { locationDetailsBinding.locationBiography.setVisibility(View.VISIBLE); locationDetailsBinding.locationBiography.setText(biography); -// locationDetailsBinding.locationBiography.addOnHashtagListener(autoLinkItem -> { -// final NavController navController = NavHostFragment.findNavController(this); -// final Bundle bundle = new Bundle(); -// final String originalText = autoLinkItem.getOriginalText().trim(); -// bundle.putString(ARG_HASHTAG, originalText); -// navController.navigate(R.id.action_global_hashTagFragment, bundle); -// }); -// locationDetailsBinding.locationBiography.addOnMentionClickListener(autoLinkItem -> { -// final String originalText = autoLinkItem.getOriginalText().trim(); -// navigateToProfile(originalText); -// }); -// locationDetailsBinding.locationBiography.addOnEmailClickListener(autoLinkItem -> Utils.openEmailAddress(context, -// autoLinkItem.getOriginalText() -// .trim())); -// locationDetailsBinding.locationBiography -// .addOnURLClickListener(autoLinkItem -> Utils.openURL(context, autoLinkItem.getOriginalText().trim())); + // locationDetailsBinding.locationBiography.addOnHashtagListener(autoLinkItem -> { + // final NavController navController = NavHostFragment.findNavController(this); + // final Bundle bundle = new Bundle(); + // final String originalText = autoLinkItem.getOriginalText().trim(); + // bundle.putString(ARG_HASHTAG, originalText); + // navController.navigate(R.id.action_global_hashTagFragment, bundle); + // }); + // locationDetailsBinding.locationBiography.addOnMentionClickListener(autoLinkItem -> { + // final String originalText = autoLinkItem.getOriginalText().trim(); + // navigateToProfile(originalText); + // }); + // locationDetailsBinding.locationBiography.addOnEmailClickListener(autoLinkItem -> Utils.openEmailAddress(context, + // autoLinkItem.getOriginalText() + // .trim())); + // locationDetailsBinding.locationBiography + // .addOnURLClickListener(autoLinkItem -> Utils.openURL(context, autoLinkItem.getOriginalText().trim())); locationDetailsBinding.locationBiography.setOnLongClickListener(v -> { Utils.copyText(context, biography); return true; From de9891f368dfecf98122b6de55ed0430ee97fede Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Wed, 14 Apr 2021 01:27:31 +0900 Subject: [PATCH 45/95] Add null check. Fixes https://github.com/austinhuang0131/barinsta/issues/1078 --- .../customviews/VideoPlayerViewHelper.java | 50 ++++++++++--------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/customviews/VideoPlayerViewHelper.java b/app/src/main/java/awais/instagrabber/customviews/VideoPlayerViewHelper.java index b2cfc2f1..b8776bfd 100644 --- a/app/src/main/java/awais/instagrabber/customviews/VideoPlayerViewHelper.java +++ b/app/src/main/java/awais/instagrabber/customviews/VideoPlayerViewHelper.java @@ -14,8 +14,8 @@ import androidx.appcompat.widget.AppCompatTextView; import androidx.appcompat.widget.PopupMenu; import com.facebook.drawee.backends.pipeline.Fresco; +import com.facebook.drawee.backends.pipeline.PipelineDraweeControllerBuilder; import com.facebook.drawee.controller.BaseControllerListener; -import com.facebook.drawee.interfaces.DraweeController; import com.facebook.imagepipeline.image.ImageInfo; import com.facebook.imagepipeline.request.ImageRequest; import com.facebook.imagepipeline.request.ImageRequestBuilder; @@ -156,29 +156,33 @@ public class VideoPlayerViewHelper implements Player.EventListener { private void setThumbnail() { binding.thumbnail.setAspectRatio(thumbnailAspectRatio); - final ImageRequest thumbnailRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(thumbnailUrl)) - .build(); - final DraweeController controller = Fresco.newDraweeControllerBuilder() - .setControllerListener(new BaseControllerListener() { - @Override - public void onFailure(final String id, final Throwable throwable) { - if (videoPlayerCallback != null) { - videoPlayerCallback.onThumbnailLoaded(); - } - } + ImageRequest thumbnailRequest = null; + if (thumbnailUrl != null) { + thumbnailRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(thumbnailUrl)).build(); + } + final PipelineDraweeControllerBuilder builder = Fresco.newDraweeControllerBuilder() + .setControllerListener(new BaseControllerListener() { + @Override + public void onFailure(final String id, + final Throwable throwable) { + if (videoPlayerCallback != null) { + videoPlayerCallback.onThumbnailLoaded(); + } + } - @Override - public void onFinalImageSet(final String id, - final ImageInfo imageInfo, - final Animatable animatable) { - if (videoPlayerCallback != null) { - videoPlayerCallback.onThumbnailLoaded(); - } - } - }) - .setImageRequest(thumbnailRequest) - .build(); - binding.thumbnail.setController(controller); + @Override + public void onFinalImageSet(final String id, + final ImageInfo imageInfo, + final Animatable animatable) { + if (videoPlayerCallback != null) { + videoPlayerCallback.onThumbnailLoaded(); + } + } + }); + if (thumbnailRequest != null) { + builder.setImageRequest(thumbnailRequest); + } + binding.thumbnail.setController(builder.build()); } private void loadPlayer() { From 34e54837d5eccce0f961c2e7818ced7aaf87ec43 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Wed, 14 Apr 2021 20:17:00 +0900 Subject: [PATCH 46/95] Create new caption object if null. Fixes https://github.com/austinhuang0131/barinsta/issues/1030 --- .../instagrabber/repositories/responses/Media.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/Media.java b/app/src/main/java/awais/instagrabber/repositories/responses/Media.java index c11669a8..135eee89 100644 --- a/app/src/main/java/awais/instagrabber/repositories/responses/Media.java +++ b/app/src/main/java/awais/instagrabber/repositories/responses/Media.java @@ -34,7 +34,7 @@ public class Media implements Serializable { private final boolean hasAudio; private final double videoDuration; private final long viewCount; - private final Caption caption; + private Caption caption; private final boolean canViewerSave; private final Audio audio; private final String title; @@ -271,7 +271,14 @@ public class Media implements Serializable { } public void setPostCaption(final String caption) { - final Caption caption1 = getCaption(); + Caption caption1 = getCaption(); + if (caption1 == null) { + final User user = getUser(); + if (user == null) return; + caption1 = new Caption(user.getPk(), caption); + this.caption = caption1; + return; + } caption1.setText(caption); } From d4ee1b9f4217d33cc787697fc5c3435f6ac7946f Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Wed, 14 Apr 2021 20:23:42 +0900 Subject: [PATCH 47/95] Set click listener on parent view. Fixes https://github.com/austinhuang0131/barinsta/issues/1055 --- .../adapters/viewholder/feed/FeedItemViewHolder.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedItemViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedItemViewHolder.java index 860bd027..042264c9 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedItemViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedItemViewHolder.java @@ -55,7 +55,7 @@ public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder { private void setupComments(@NonNull final Media feedModel) { final long commentsCount = feedModel.getCommentCount(); bottomBinding.commentsCount.setText(String.valueOf(commentsCount)); - bottomBinding.commentsCount.setOnClickListener(v -> feedItemCallback.onCommentsClick(feedModel)); + bottomBinding.btnComments.setOnClickListener(v -> feedItemCallback.onCommentsClick(feedModel)); } private void setupProfilePic(@NonNull final Media media) { @@ -75,6 +75,7 @@ public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder { // final SpannableString spannableString = new SpannableString(); // spannableString.setSpan(new CommentMentionClickSpan(), 0, titleLen, 0); final User user = media.getUser(); + if (user == null) return; final String title = "@" + user.getUsername(); topBinding.title.setText(title); topBinding.title.setOnClickListener(v -> feedItemCallback.onNameClick(media, topBinding.ivProfilePic)); @@ -120,8 +121,7 @@ public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder { topBinding.title.setLayoutParams(new RelativeLayout.LayoutParams( RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT )); - } - else { + } else { final String locationName = location.getName(); if (TextUtils.isEmpty(locationName)) { topBinding.location.setVisibility(View.GONE); From f2a3506b19a0f05277ea5dc41d78767fcc74168a Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Wed, 14 Apr 2021 20:48:42 +0900 Subject: [PATCH 48/95] Make refreshStory synchronised? May fix https://github.com/austinhuang0131/barinsta/issues/945 --- .../fragments/StoryViewerFragment.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java index ddd84ce3..c13efdc1 100644 --- a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java @@ -2,14 +2,12 @@ package awais.instagrabber.fragments; import android.annotation.SuppressLint; import android.content.Context; -import android.content.Intent; import android.content.pm.PackageManager; import android.graphics.drawable.Animatable; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.util.Log; -import android.util.Pair; import android.view.GestureDetector; import android.view.Gravity; import android.view.LayoutInflater; @@ -72,7 +70,6 @@ import awais.instagrabber.BuildConfig; import awais.instagrabber.R; import awais.instagrabber.adapters.StoriesAdapter; import awais.instagrabber.asyncs.CreateThreadAction; -import awais.instagrabber.asyncs.PostFetcher; import awais.instagrabber.customviews.helpers.SwipeGestureListener; import awais.instagrabber.databinding.FragmentStoryViewerBinding; import awais.instagrabber.fragments.main.ProfileFragmentDirections; @@ -105,7 +102,6 @@ import awais.instagrabber.webservices.DirectMessagesService; import awais.instagrabber.webservices.MediaService; import awais.instagrabber.webservices.ServiceCallback; import awais.instagrabber.webservices.StoriesService; -//import awaisomereport.LogCollector; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; @@ -113,7 +109,6 @@ import retrofit2.Response; import static awais.instagrabber.customviews.helpers.SwipeGestureListener.SWIPE_THRESHOLD; import static awais.instagrabber.customviews.helpers.SwipeGestureListener.SWIPE_VELOCITY_THRESHOLD; import static awais.instagrabber.utils.Constants.MARK_AS_SEEN; -//import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.settingsHelper; public class StoryViewerFragment extends Fragment { @@ -417,10 +412,10 @@ public class StoryViewerFragment extends Fragment { return true; } } catch (final Exception e) { -// if (logCollector != null) -// logCollector.appendException(e, LogCollector.LogFile.ACTIVITY_STORY_VIEWER, "setupListeners", -// new Pair<>("swipeEvent", swipeEvent), -// new Pair<>("diffX", diffX)); + // if (logCollector != null) + // logCollector.appendException(e, LogCollector.LogFile.ACTIVITY_STORY_VIEWER, "setupListeners", + // new Pair<>("swipeEvent", swipeEvent), + // new Pair<>("diffX", diffX)); if (BuildConfig.DEBUG) Log.e(TAG, "Error", e); } return false; @@ -838,7 +833,7 @@ public class StoryViewerFragment extends Fragment { } } - private void refreshStory() { + private synchronized void refreshStory() { if (binding.storiesList.getVisibility() == View.VISIBLE) { final List storyModels = storiesViewModel.getList().getValue(); if (storyModels != null && storyModels.size() > 0) { From eed4036ad94ff29636cabd39fc888e8618a60d9c Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Wed, 14 Apr 2021 21:17:07 +0900 Subject: [PATCH 49/95] Fix setItemsToThread. Fixes https://github.com/austinhuang0131/barinsta/issues/967 --- .../java/awais/instagrabber/managers/InboxManager.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/managers/InboxManager.java b/app/src/main/java/awais/instagrabber/managers/InboxManager.java index efca55f5..eb54b8e2 100644 --- a/app/src/main/java/awais/instagrabber/managers/InboxManager.java +++ b/app/src/main/java/awais/instagrabber/managers/InboxManager.java @@ -285,8 +285,13 @@ public final class InboxManager { if (index < 0) return; final List threads = inbox.getThreads(); final DirectThread thread = threads.get(index); - thread.setItems(updatedItems); - setThread(inbox, index, thread); + try { + final DirectThread threadClone = (DirectThread) thread.clone(); + threadClone.setItems(updatedItems); + setThread(inbox, index, threadClone); + } catch (Exception e) { + Log.e(TAG, "setItemsToThread: ", e); + } } } From cfd1d4a421685791ff8597343b5e33c7f279d9a2 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Wed, 14 Apr 2021 21:17:24 +0900 Subject: [PATCH 50/95] Null check csrf token --- .../java/awais/instagrabber/managers/ThreadManager.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/awais/instagrabber/managers/ThreadManager.java b/app/src/main/java/awais/instagrabber/managers/ThreadManager.java index 5b84c97c..6074363e 100644 --- a/app/src/main/java/awais/instagrabber/managers/ThreadManager.java +++ b/app/src/main/java/awais/instagrabber/managers/ThreadManager.java @@ -92,14 +92,14 @@ public final class ThreadManager { private final MutableLiveData pendingRequests = new MutableLiveData<>(null); private final String threadId; - private final DirectMessagesService service; private final long viewerId; private final ThreadIdOrUserIds threadIdOrUserIds; private final User currentUser; private final ContentResolver contentResolver; - private final MediaService mediaService; - private final FriendshipService friendshipService; + private DirectMessagesService service; + private MediaService mediaService; + private FriendshipService friendshipService; private InboxManager inboxManager; private LiveData thread; private LiveData inputMode; @@ -157,6 +157,7 @@ public final class ThreadManager { viewerId = CookieUtils.getUserIdFromCookie(cookie); final String deviceUuid = settingsHelper.getString(Constants.DEVICE_UUID); final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie); + if (csrfToken == null) return; // if (TextUtils.isEmpty(csrfToken) || viewerId <= 0 || TextUtils.isEmpty(deviceUuid)) { // throw new IllegalArgumentException("User is not logged in!"); // } From 81a55151c18ec7d95c29a2ac9927b9a59e484fc9 Mon Sep 17 00:00:00 2001 From: Ammar Githam Date: Thu, 15 Apr 2021 23:33:00 +0900 Subject: [PATCH 51/95] Fix styles --- app/src/main/res/layout/activity_main.xml | 4 ++-- app/src/main/res/values/attrs.xml | 3 +++ app/src/main/res/values/styles.xml | 26 +++++++++++++++++++++++ app/src/main/res/values/themes.xml | 15 +++++++++++++ 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 8fadd4a2..1e46b341 100755 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -29,14 +29,14 @@ android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" - android:background="?attr/colorSurface" + android:background="?attr/toolbarColor" app:layout_collapseMode="pin" app:title="@string/app_name" tools:menu="@menu/main_menu"> + + + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index c0d79025..129f3875 100755 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -234,4 +234,30 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 28de10d8..cceca619 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -23,6 +23,7 @@ @color/grey_600 @color/deep_purple_400 @color/deep_purple_600 + @style/Widget.MaterialComponents.TextInputLayout.OutlinedBox @@ -103,6 +112,7 @@ @color/white @color/blue_800 @color/white + @style/Widget.MaterialComponents.TextInputLayout.OutlinedBox + + + + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index cceca619..d781845f 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -146,6 +146,7 @@ @color/deep_purple_400 @color/deep_purple_600 @style/Widget.MaterialComponents.TabLayout.Dark.Black + @style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dark.Black - - diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index d781845f..660a2189 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -53,6 +53,7 @@ @color/blue_800 @color/black @style/Widget.MaterialComponents.TabLayout.Light.White + @color/parent_comment_light_white @@ -146,7 +149,8 @@ @color/deep_purple_400 @color/deep_purple_600 @style/Widget.MaterialComponents.TabLayout.Dark.Black - @style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dark.Black + @style/Widget.MaterialComponents.TextInputLayout.FilledBox.Dark.Black + @color/parent_comment_dark_materialdark