diff --git a/app/build.gradle b/app/build.gradle index d96458bd..8d90e12b 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -111,7 +111,6 @@ dependencies { implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'org.apache.commons:commons-imaging:1.0-alpha2' - implementation 'com.ibm.icu:icu4j:68.1' implementation 'com.github.ammargitham:uCrop:2.3-native-beta-2' implementation 'com.github.ammargitham:android-gpuimage:2.1.1-beta4' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index fd517a33..ce5e77c9 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -22,7 +22,7 @@ android:label="@string/app_name" android:requestLegacyExternalStorage="true" android:supportsRtl="true" - android:theme="@style/AppTheme.Light.White" + android:theme="@style/AppTheme.Launcher" tools:ignore="UnusedAttribute"> backStack = navController.getBackStack(); + if (backStack != null) { + currentNavControllerBackStack = backStack.size(); + } + } + } + if (isTaskRoot() && isBackStackEmpty && currentNavControllerBackStack == 2) { finishAfterTransition(); return; } @@ -577,10 +586,10 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage private void showThread(@NonNull final Intent intent) { final String threadId = intent.getStringExtra(Constants.DM_THREAD_ACTION_EXTRA_THREAD_ID); final String threadTitle = intent.getStringExtra(Constants.DM_THREAD_ACTION_EXTRA_THREAD_TITLE); - navigateToThread(threadId, threadTitle, null); + navigateToThread(threadId, threadTitle); } - public void navigateToThread(final String threadId, final String threadTitle, final DirectThread backup) { + public void navigateToThread(final String threadId, final String threadTitle) { if (threadId == null || threadTitle == null) return; currentNavControllerLiveData.observe(this, new Observer() { @Override @@ -594,7 +603,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage // need handler.post() to wait for the fragment manager to be ready to navigate new Handler().post(() -> { final DirectMessageInboxFragmentDirections.ActionInboxToThread action = DirectMessageInboxFragmentDirections - .actionInboxToThread(threadId, threadTitle, backup); + .actionInboxToThread(threadId, threadTitle); navController.navigate(action); }); return; @@ -607,7 +616,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage @Nullable final Bundle arguments) { if (destination.getId() == R.id.directMessagesInboxFragment) { final DirectMessageInboxFragmentDirections.ActionInboxToThread action = DirectMessageInboxFragmentDirections - .actionInboxToThread(threadId, threadTitle, backup); + .actionInboxToThread(threadId, threadTitle); controller.navigate(action); controller.removeOnDestinationChangedListener(this); } diff --git a/app/src/main/java/awais/instagrabber/adapters/SliderCallbackAdapter.java b/app/src/main/java/awais/instagrabber/adapters/SliderCallbackAdapter.java index 803256af..1cfec468 100644 --- a/app/src/main/java/awais/instagrabber/adapters/SliderCallbackAdapter.java +++ b/app/src/main/java/awais/instagrabber/adapters/SliderCallbackAdapter.java @@ -12,4 +12,7 @@ public class SliderCallbackAdapter implements SliderItemsAdapter.SliderCallback @Override public void onPlayerPause(final int position) {} + + @Override + public void onPlayerRelease(final int position) {} } diff --git a/app/src/main/java/awais/instagrabber/adapters/SliderItemsAdapter.java b/app/src/main/java/awais/instagrabber/adapters/SliderItemsAdapter.java index 5d4092c0..c805d316 100644 --- a/app/src/main/java/awais/instagrabber/adapters/SliderItemsAdapter.java +++ b/app/src/main/java/awais/instagrabber/adapters/SliderItemsAdapter.java @@ -148,5 +148,7 @@ public final class SliderItemsAdapter extends ListAdapter= 0 && sliderPosition < media.getCarouselMedia().size()) { @@ -1206,7 +1220,7 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment im @Override public void onPlayerViewLoaded() { - binding.playerControls.getRoot().setVisibility(View.VISIBLE); + // binding.playerControls.getRoot().setVisibility(View.VISIBLE); final ViewGroup.LayoutParams layoutParams = binding.videoPost.playerView.getLayoutParams(); final int requiredWidth = Utils.displayMetrics.widthPixels; final int resultingHeight = NumberUtils @@ -1218,10 +1232,27 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment im @Override public void onPlay() { + final FragmentActivity activity = getActivity(); + if (activity == null) return; + Utils.enabledKeepScreenOn(activity); if (detailsVisible) { new Handler().postDelayed(() -> toggleDetails(), DETAILS_HIDE_DELAY_MILLIS); } } + + @Override + public void onPause() { + final FragmentActivity activity = getActivity(); + if (activity == null) return; + Utils.disableKeepScreenOn(activity); + } + + @Override + public void onRelease() { + final FragmentActivity activity = getActivity(); + if (activity == null) return; + Utils.disableKeepScreenOn(activity); + } }; final float aspectRatio = (float) media.getOriginalWidth() / media.getOriginalHeight(); String videoUrl = null; diff --git a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java index 5c62d592..4b8bece2 100644 --- a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java @@ -257,7 +257,7 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh navigating = true; if (isAdded()) { final DirectMessageInboxFragmentDirections.ActionInboxToThread directions = DirectMessageInboxFragmentDirections - .actionInboxToThread(thread.getThreadId(), thread.getThreadTitle(), thread); + .actionInboxToThread(thread.getThreadId(), thread.getThreadTitle()); NavHostFragment.findNavController(this).navigate(directions); } navigating = false; 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 1ca1552a..bbd174f2 100644 --- a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageSettingsFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageSettingsFragment.java @@ -81,7 +81,6 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi final AppStateViewModel appStateViewModel = new ViewModelProvider(fragmentActivity).get(AppStateViewModel.class); viewModel = new ViewModelProvider(this, new DirectSettingsViewModelFactory(fragmentActivity.getApplication(), args.getThreadId(), - args.getBackup(), args.getPending(), appStateViewModel.getCurrentUser())) .get(DirectSettingsViewModel.class); @@ -348,15 +347,15 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi usersAdapter = new DirectUsersAdapter( inviter != null ? inviter.getPk() : -1, (position, user, selected) -> { - if (!TextUtils.isEmpty(user.getFbId())) { + if (TextUtils.isEmpty(user.getUsername()) && !TextUtils.isEmpty(user.getFbId())) { Utils.openURL(context, "https://facebook.com/" + user.getFbId()); + return; } - else { - final ProfileNavGraphDirections.ActionGlobalProfileFragment directions = ProfileNavGraphDirections - .actionGlobalProfileFragment() - .setUsername("@" + user.getUsername()); - NavHostFragment.findNavController(this).navigate(directions); - } + if (TextUtils.isEmpty(user.getUsername())) return; + final ProfileNavGraphDirections.ActionGlobalProfileFragment directions = ProfileNavGraphDirections + .actionGlobalProfileFragment() + .setUsername("@" + user.getUsername()); + NavHostFragment.findNavController(this).navigate(directions); }, (position, user) -> { final ArrayList> options = viewModel.createUserOptions(user); 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 7edbe49c..d1e4b5f9 100644 --- a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java @@ -100,6 +100,7 @@ import awais.instagrabber.repositories.responses.directmessages.DirectThread; import awais.instagrabber.repositories.responses.directmessages.RankedRecipient; import awais.instagrabber.utils.AppExecutors; import awais.instagrabber.utils.Constants; +import awais.instagrabber.utils.DMUtils; import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.PermissionUtils; import awais.instagrabber.utils.ResponseBodyUtils; @@ -317,6 +318,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact backStackSavedStateResultLiveData.postValue(null); }; private final MutableLiveData inputLength = new MutableLiveData<>(0); + private MenuItem markAsSeenMenuItem; @Override public void onCreate(@Nullable final Bundle savedInstanceState) { @@ -329,7 +331,6 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact final DirectMessageThreadFragmentArgs fragmentArgs = DirectMessageThreadFragmentArgs.fromBundle(arguments); viewModel = new ViewModelProvider(this, new DirectThreadViewModelFactory(fragmentActivity.getApplication(), fragmentArgs.getThreadId(), - fragmentArgs.getBackup(), fragmentArgs.getPending(), appStateViewModel.getCurrentUser())) .get(DirectThreadViewModel.class); @@ -368,9 +369,13 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact @Override public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) { inflater.inflate(R.menu.dm_thread_menu, menu); - final MenuItem markAsSeenMenuItem = menu.findItem(R.id.mark_as_seen); + markAsSeenMenuItem = menu.findItem(R.id.mark_as_seen); if (markAsSeenMenuItem != null) { - markAsSeenMenuItem.setVisible(false); + if (autoMarkAsSeen) { + markAsSeenMenuItem.setVisible(false); + } else { + markAsSeenMenuItem.setEnabled(false); + } } } @@ -379,15 +384,14 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact final int itemId = item.getItemId(); if (itemId == R.id.info) { final DirectMessageThreadFragmentDirections.ActionThreadToSettings directions = DirectMessageThreadFragmentDirections - .actionThreadToSettings(viewModel.getThreadId(), null, null); + .actionThreadToSettings(viewModel.getThreadId(), null); final Boolean pending = viewModel.isPending().getValue(); directions.setPending(pending == null ? false : pending); NavHostFragment.findNavController(this).navigate(directions); return true; } if (itemId == R.id.mark_as_seen) { - // new ThreadAction().execute("seen", lastMessage); - item.setVisible(false); + handleMarkAsSeen(item); return true; } if (itemId == R.id.refresh && viewModel != null) { @@ -397,6 +401,40 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact return super.onOptionsItemSelected(item); } + private void handleMarkAsSeen(@NonNull final MenuItem item) { + final LiveData> resourceLiveData = viewModel.markAsSeen(); + resourceLiveData.observe(getViewLifecycleOwner(), new Observer>() { + @Override + public void onChanged(final Resource resource) { + try { + if (resource == null) return; + final Context context = getContext(); + if (context == null) return; + switch (resource.status) { + case SUCCESS: + Toast.makeText(context, R.string.marked_as_seen, Toast.LENGTH_SHORT).show(); + case LOADING: + item.setEnabled(false); + break; + case ERROR: + item.setEnabled(true); + if (resource.message != null) { + Snackbar.make(context, binding.getRoot(), resource.message, Snackbar.LENGTH_LONG).show(); + return; + } + if (resource.resId != 0) { + Snackbar.make(binding.getRoot(), resource.resId, Snackbar.LENGTH_LONG).show(); + return; + } + break; + } + } finally { + resourceLiveData.removeObserver(this); + } + } + }); + } + @Override public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); @@ -464,6 +502,12 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact cleanup(); } + @Override + public void onDestroy() { + viewModel.deleteThreadIfRequired(); + super.onDestroy(); + } + @SuppressLint("UnsafeExperimentalUsageError") private void cleanup() { if (prevTitleRunnable != null) { @@ -903,9 +947,15 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact } private void submitItemsToAdapter(final List items) { - if (autoMarkAsSeen) { - binding.chats.post(() -> viewModel.markAsSeen()); - } + binding.chats.post(() -> { + if (autoMarkAsSeen) { + viewModel.markAsSeen(); + return; + } + final DirectThread thread = threadLiveData.getValue(); + if (thread == null) return; + markAsSeenMenuItem.setEnabled(!DMUtils.isRead(thread)); + }); if (itemsAdapter == null) return; itemsAdapter.submitList(items, () -> { itemOrHeaders = itemsAdapter.getList(); diff --git a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectPendingInboxFragment.java b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectPendingInboxFragment.java index c242e564..8ee0d1c4 100644 --- a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectPendingInboxFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectPendingInboxFragment.java @@ -163,7 +163,7 @@ public class DirectPendingInboxFragment extends Fragment implements SwipeRefresh navigating = true; if (isAdded()) { final DirectPendingInboxFragmentDirections.ActionPendingInboxToThread directions = DirectPendingInboxFragmentDirections - .actionPendingInboxToThread(thread.getThreadId(), thread.getThreadTitle(), thread); + .actionPendingInboxToThread(thread.getThreadId(), thread.getThreadTitle()); directions.setPending(true); NavHostFragment.findNavController(this).navigate(directions); } 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 4cd53a0b..12a42ff6 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java @@ -74,6 +74,8 @@ import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment; import awais.instagrabber.dialogs.ProfilePicDialogFragment; import awais.instagrabber.fragments.PostViewV2Fragment; import awais.instagrabber.interfaces.FetchListener; +import awais.instagrabber.managers.DirectMessagesManager; +import awais.instagrabber.managers.InboxManager; import awais.instagrabber.models.HighlightModel; import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.models.StoryModel; @@ -590,8 +592,11 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe private void fetchProfileDetails() { accountIsUpdated = false; - new ProfileFetcher(TextUtils.isEmpty(username) ? null : username.trim().substring(1), - myId, isLoggedIn, new FetchListener() { + String usernameTemp = username.trim(); + if (usernameTemp.startsWith("@")) { + usernameTemp = usernameTemp.substring(1); + } + new ProfileFetcher(TextUtils.isEmpty(username) ? null : usernameTemp, myId, isLoggedIn, new FetchListener() { @Override public void onResult(final User user) { if (getContext() == null) return; @@ -612,7 +617,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe isLoggedIn ? R.string.error_loading_profile_loggedin : R.string.error_loading_profile, Toast.LENGTH_LONG).show(); else Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show(); - } catch (final Throwable e) {} + } catch (final Throwable ignored) {} } }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); @@ -1073,7 +1078,12 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe profileDetailsBinding.btnDM.setEnabled(true); return; } - fragmentActivity.navigateToThread(thread.getThreadId(), profileModel.getUsername(), thread); + final InboxManager inboxManager = DirectMessagesManager.getInstance().getInboxManager(); + if (!inboxManager.containsThread(thread.getThreadId())) { + thread.setTemp(true); + inboxManager.addThread(thread, 0); + } + fragmentActivity.navigateToThread(thread.getThreadId(), profileModel.getUsername()); profileDetailsBinding.btnDM.setEnabled(true); }).execute(); }); diff --git a/app/src/main/java/awais/instagrabber/managers/DirectMessagesManager.java b/app/src/main/java/awais/instagrabber/managers/DirectMessagesManager.java index 99b07b50..6bc7f4da 100644 --- a/app/src/main/java/awais/instagrabber/managers/DirectMessagesManager.java +++ b/app/src/main/java/awais/instagrabber/managers/DirectMessagesManager.java @@ -74,9 +74,8 @@ public final class DirectMessagesManager { public ThreadManager getThreadManager(@NonNull final String threadId, final boolean pending, - final DirectThread backup, @NonNull final User currentUser, @NonNull final ContentResolver contentResolver) { - return ThreadManager.getInstance(threadId, pending, backup, currentUser, contentResolver); + return ThreadManager.getInstance(threadId, pending, currentUser, contentResolver); } } diff --git a/app/src/main/java/awais/instagrabber/managers/InboxManager.java b/app/src/main/java/awais/instagrabber/managers/InboxManager.java index 70457e03..98e12b46 100644 --- a/app/src/main/java/awais/instagrabber/managers/InboxManager.java +++ b/app/src/main/java/awais/instagrabber/managers/InboxManager.java @@ -18,6 +18,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.LinkedList; import java.util.List; +import java.util.Objects; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -357,4 +358,15 @@ public final class InboxManager { public void setPendingRequestsTotal(final int total) { pendingRequestsTotal.postValue(total); } + + public boolean containsThread(final String threadId) { + if (threadId == null) return false; + synchronized (this.inbox) { + final DirectInbox currentDirectInbox = getCurrentDirectInbox(); + if (currentDirectInbox == null) return false; + final List threads = currentDirectInbox.getThreads(); + if (threads == null) return false; + return threads.stream().anyMatch(thread -> Objects.equals(thread.getThreadId(), threadId)); + } + } } diff --git a/app/src/main/java/awais/instagrabber/managers/ThreadManager.java b/app/src/main/java/awais/instagrabber/managers/ThreadManager.java index 6e7de7ab..90d67df3 100644 --- a/app/src/main/java/awais/instagrabber/managers/ThreadManager.java +++ b/app/src/main/java/awais/instagrabber/managers/ThreadManager.java @@ -123,7 +123,6 @@ public final class ThreadManager { public static ThreadManager getInstance(@NonNull final String threadId, final boolean pending, - final DirectThread backup, @NonNull final User currentUser, @NonNull final ContentResolver contentResolver) { ThreadManager instance = INSTANCE_MAP.get(threadId); @@ -131,7 +130,7 @@ public final class ThreadManager { synchronized (LOCK) { instance = INSTANCE_MAP.get(threadId); if (instance == null) { - instance = new ThreadManager(threadId, pending, backup, currentUser, contentResolver); + instance = new ThreadManager(threadId, pending, currentUser, contentResolver); INSTANCE_MAP.put(threadId, instance); } } @@ -145,7 +144,6 @@ public final class ThreadManager { private ThreadManager(@NonNull final String threadId, final boolean pending, - final DirectThread backup, @NonNull final User currentUser, @NonNull final ContentResolver contentResolver) { final DirectMessagesManager messagesManager = DirectMessagesManager.getInstance(); @@ -164,17 +162,17 @@ public final class ThreadManager { service = DirectMessagesService.getInstance(csrfToken, viewerId, deviceUuid); mediaService = MediaService.getInstance(deviceUuid, csrfToken, viewerId); friendshipService = FriendshipService.getInstance(deviceUuid, csrfToken, viewerId); - setupTransformations(backup); + setupTransformations(); // fetchChats(); } public void moveFromPending() { final DirectMessagesManager messagesManager = DirectMessagesManager.getInstance(); this.inboxManager = messagesManager.getInboxManager(); - setupTransformations(null); + setupTransformations(); } - private void setupTransformations(final DirectThread backup) { + private void setupTransformations() { // Transformations thread = distinctUntilChanged(map(inboxManager.getInbox(), inboxResource -> { if (inboxResource == null) { @@ -188,7 +186,7 @@ public final class ThreadManager { final DirectThread thread = threads.stream() .filter(t -> t.getThreadId().equals(threadId)) .findFirst() - .orElse(backup); + .orElse(null); if (thread != null) { cursor = thread.getOldestCursor(); hasOlder = thread.hasOlder(); @@ -1799,18 +1797,23 @@ public final class ThreadManager { return inviter; } - public void markAsSeen(@NonNull final DirectItem directItem) { + public LiveData> markAsSeen(@NonNull final DirectItem directItem) { + final MutableLiveData> data = new MutableLiveData<>(); + data.postValue(Resource.loading(null)); final Call request = service.markAsSeen(threadId, directItem); request.enqueue(new Callback() { @Override public void onResponse(@NonNull final Call call, @NonNull final Response response) { if (!response.isSuccessful()) { - handleErrorBody(call, response, null); + handleErrorBody(call, response, data); return; } final DirectItemSeenResponse seenResponse = response.body(); - if (seenResponse == null) return; + if (seenResponse == null) { + data.postValue(Resource.error(R.string.generic_null_response, null)); + return; + } inboxManager.fetchUnseenCount(); final DirectItemSeenResponsePayload payload = seenResponse.getPayload(); if (payload == null) return; @@ -1822,14 +1825,17 @@ public final class ThreadManager { lastSeenAt.put(currentUser.getPk(), new DirectThreadLastSeenAt(timestamp, directItem.getItemId())); thread.setLastSeenAt(lastSeenAt); setThread(thread, true); + data.postValue(Resource.success(new Object())); } @Override public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { Log.e(TAG, "onFailure: ", t); + data.postValue(Resource.error(t.getMessage(), null)); } }); + return data; } private interface OnSuccessAction { diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectThread.java b/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectThread.java index 67b58db4..1f6ffd66 100644 --- a/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectThread.java +++ b/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectThread.java @@ -45,6 +45,7 @@ public class DirectThread implements Serializable, Cloneable { private boolean approvalRequiredForNewMembers; private int inputMode; private final List threadContextItems; + private boolean isTemp; public DirectThread(final String threadId, final String threadV2Id, @@ -292,6 +293,14 @@ public class DirectThread implements Serializable, Cloneable { return threadContextItems; } + public boolean isTemp() { + return isTemp; + } + + public void setTemp(final boolean isTemp) { + this.isTemp = isTemp; + } + @Nullable public DirectItem getFirstDirectItem() { DirectItem firstItem = null; diff --git a/app/src/main/java/awais/instagrabber/utils/Utils.java b/app/src/main/java/awais/instagrabber/utils/Utils.java index 29425a27..bf716e5d 100644 --- a/app/src/main/java/awais/instagrabber/utils/Utils.java +++ b/app/src/main/java/awais/instagrabber/utils/Utils.java @@ -359,4 +359,16 @@ public final class Utils { } return drawable; } + + public static void enabledKeepScreenOn(@NonNull final Activity activity) { + final Window window = activity.getWindow(); + if (window == null) return; + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + + public static void disableKeepScreenOn(@NonNull final Activity activity) { + final Window window = activity.getWindow(); + if (window == null) return; + window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } } diff --git a/app/src/main/java/awais/instagrabber/utils/emoji/EmojiParser.java b/app/src/main/java/awais/instagrabber/utils/emoji/EmojiParser.java index 7cae5167..09a67225 100644 --- a/app/src/main/java/awais/instagrabber/utils/emoji/EmojiParser.java +++ b/app/src/main/java/awais/instagrabber/utils/emoji/EmojiParser.java @@ -2,27 +2,25 @@ package awais.instagrabber.utils.emoji; import android.util.Log; -import com.google.common.base.CharMatcher; -import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; -import com.ibm.icu.impl.Utility; -import com.ibm.icu.lang.CharSequences; -import com.ibm.icu.text.UnicodeSet; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; -import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; +import java.lang.reflect.Type; import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedHashMap; +import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; import awais.instagrabber.customviews.emoji.Emoji; import awais.instagrabber.customviews.emoji.EmojiCategory; import awais.instagrabber.customviews.emoji.EmojiCategoryType; +import awais.instagrabber.utils.NetworkUtils; public final class EmojiParser { private static final String TAG = EmojiParser.class.getSimpleName(); @@ -30,51 +28,8 @@ public final class EmojiParser { private static EmojiParser instance; - // private static final String COMBINING_ENCLOSING_KEYCAP = "\u20E3"; - // private static final String ZWJ = "\u200D"; - // private static final UnicodeSet REGIONAL_INDICATORS = new UnicodeSet(0x1F1E6, 0x1F1FF).freeze(); - // private static final UnicodeSet TAGS = new UnicodeSet(0xE0000, 0xE007F).freeze(); - // private static final UnicodeSet FAMILY = new UnicodeSet("[\u200D ๐Ÿ‘ฆ-๐Ÿ‘ฉ ๐Ÿ’‹ โค]").freeze(); - // private static final UnicodeSet GENDER = new UnicodeSet().add(0x2640).add(0x2642).freeze(); - // private static final UnicodeSet SPECIALS = new UnicodeSet("[" - // + "{๐Ÿˆโ€โฌ›}{๐Ÿปโ€โ„}{๐Ÿ‘จโ€๐Ÿผ}{๐Ÿ‘ฉโ€๐Ÿผ}{๐Ÿง‘โ€๐Ÿผ}{๐Ÿง‘โ€๐ŸŽ„}{๐Ÿง‘โ€๐Ÿคโ€๐Ÿง‘}{๐Ÿณโ€๐ŸŒˆ} {๐Ÿ‘โ€๐Ÿ—จ} {๐Ÿดโ€โ˜ } {๐Ÿ•โ€๐Ÿฆบ} {๐Ÿ‘จโ€๐Ÿฆฏ} {๐Ÿ‘จโ€๐Ÿฆผ} {๐Ÿ‘จโ€๐Ÿฆฝ} {๐Ÿ‘ฉโ€๐Ÿฆฏ} {๐Ÿ‘ฉโ€๐Ÿฆผ} {๐Ÿ‘ฉโ€๐Ÿฆฝ}" - // + "{๐Ÿณโ€โšง}{๐Ÿง‘โ€โš•}{๐Ÿง‘โ€โš–}{๐Ÿง‘โ€โœˆ}{๐Ÿง‘โ€๐ŸŒพ}{๐Ÿง‘โ€๐Ÿณ}{๐Ÿง‘โ€๐ŸŽ“}{๐Ÿง‘โ€๐ŸŽค}{๐Ÿง‘โ€๐ŸŽจ}{๐Ÿง‘โ€๐Ÿซ}{๐Ÿง‘โ€๐Ÿญ}{๐Ÿง‘โ€๐Ÿ’ป}{๐Ÿง‘โ€๐Ÿ’ผ}{๐Ÿง‘โ€๐Ÿ”ง}{๐Ÿง‘โ€๐Ÿ”ฌ}{๐Ÿง‘โ€๐Ÿš€}{๐Ÿง‘โ€๐Ÿš’}{๐Ÿง‘โ€๐Ÿฆฏ}{๐Ÿง‘โ€๐Ÿฆผ}{๐Ÿง‘โ€๐Ÿฆฝ}" - // + "{โคโ€๐Ÿ”ฅ}, {โคโ€๐Ÿฉน}, {๐Ÿ˜ฎโ€๐Ÿ’จ}, {๐Ÿ˜ตโ€๐Ÿ’ซ}" // #E13.1 - // + "]").freeze(); - // May have to add from above, if there is a failure in testAnnotationPaths. Failure will be like: - // got java.util.TreeSet<[//ldml/annotations/annotation[@cp="๐Ÿณโ€โšง"][@type="tts"], //ldml/annotations/annotation[@cp="๐Ÿง‘โ€โš•"][@type="tts"], ... - // just extract the items in "...", and change into {...} for adding above. - // Example: //ldml/annotations/annotation[@cp="๐Ÿง‘โ€โš•"] ==> {๐Ÿง‘โ€โš•} - // private static final UnicodeSet MAN_WOMAN = new UnicodeSet("[๐Ÿ‘จ ๐Ÿ‘ฉ]").freeze(); - // private static final UnicodeSet OBJECT = new UnicodeSet("[๐Ÿ‘ฉ ๐ŸŽ“ ๐ŸŒพ ๐Ÿณ ๐Ÿซ ๐Ÿญ ๐ŸŽจ ๐Ÿš’ โœˆ ๐Ÿš€ ๐ŸŽค ๐Ÿ’ป ๐Ÿ”ฌ ๐Ÿ’ผ ๐Ÿ”ง โš– โš•]").freeze(); - // private static final String TYPE_TTS = "[@type=\"tts\"]"; - private static final String EMOJI_VARIANT = "\uFE0F"; - private static final UnicodeSet SKIN_TONE_MODIFIERS = new UnicodeSet("[๐Ÿป-๐Ÿฟ]").freeze(); - private static final String SKIN_TONE_PATTERN = SKIN_TONE_MODIFIERS.toPattern(true); - private static final Map CATEGORY_MAP = new LinkedHashMap<>(); - private static final Map ALL_EMOJIS = new HashMap<>(); - - // private final UnicodeMap emojiToMajorCategory = new UnicodeMap<>(); - // private final UnicodeMap emojiToMinorCategory = new UnicodeMap<>(); - // private final UnicodeMap toName = new UnicodeMap<>(); - // /** - // * A mapping from a majorCategory to a unique ordering number, based on the first time it is encountered. - // */ - // private final Map majorToOrder = new HashMap<>(); - // private final List majorToOrder = new LinkedList(); - // /** - // * A mapping from a minorCategory to a unique ordering number, based on the first time it is encountered. - // */ - // private final Map minorToOrder = new HashMap<>(); - // private final Map emojiToOrder = new LinkedHashMap<>(); - // private final UnicodeSet nonConstructed = new UnicodeSet(); - // private final UnicodeSet allRgi = new UnicodeSet(); - // private final UnicodeSet allRgiNoES = new UnicodeSet(); - // private final UnicodeMap EXTRA_SYMBOL_MINOR_CATEGORIES = new UnicodeMap<>(); - // private final Map EXTRA_SYMBOL_ORDER; - // private final boolean DEBUG = false; - // private Set NAME_PATHS = null; - // private Set KEYWORD_PATHS = null; + private Map allEmojis = Collections.emptyMap(); + private Map categoryMap = Collections.emptyMap(); private ImmutableList categories; public static EmojiParser getInstance() { @@ -89,267 +44,56 @@ public final class EmojiParser { } private EmojiParser() { - // Log.d(TAG, "Emoji: " + new Date()); - // String[][] data = { - // {"arrow", "โ†’ โ†“ โ†‘ โ† โ†” โ†• โ‡† โ‡…"}, - // {"alphanum", "ยฉ ยฎ โ„— โ„ข ยต"}, - // {"geometric", "โ–ผ โ–ถ โ–ฒ โ—€ โ— โ—‹ โ—ฏ โ—Š"}, - // {"math", "ร— รท โˆš โˆž โˆ† โˆ‡ โป ยน ยฒ ยณ โ‰ก โˆˆ โŠ‚ โˆฉ โˆช ยฐ + ยฑ โˆ’ = โ‰ˆ โ‰  > < โ‰ค โ‰ฅ ยฌ | ~"}, - // {"punctuation", "ยง โ€  โ€ก \\u0020 , ใ€ ุŒ ; : ุ› ! ยก ? ยฟ ุŸ ยถ โ€ป / \\ & # % โ€ฐ โ€ฒ โ€ณ โ€ด @ * โ™ช โ™ญ โ™ฏ ` ยด ^ ยจ โ€ โ€• _ - โ€“ โ€” โ€ข ยท . โ€ฆ ใ€‚ โ€ง ใƒป โ€˜ โ€™ โ€š ' โ€œ โ€ โ€ž ยป ยซ ( ) [ ] { } ใ€” ใ€• ใ€ˆ ใ€‰ ใ€Š ใ€‹ ใ€Œ ใ€ ใ€Ž ใ€ ใ€– ใ€— ใ€ ใ€‘"}, - // {"currency", "โ‚ฌ ยฃ ยฅ โ‚น โ‚ฝ $ ยข เธฟ โ‚ช โ‚บ โ‚ซ โ‚ฑ โ‚ฉ โ‚ก โ‚ฆ โ‚ฎ เงณ โ‚ด โ‚ธ โ‚ฒ โ‚ต แŸ› โ‚ญ ึ โ‚ฅ โ‚พ โ‚ผ โ‚ฟ ุ‹"}, - // {"other-symbol", "โ€พโ€ฝโ€ธโ‚โ†šโ†›โ†ฎโ†™โ†œโ†โ†žโ†Ÿโ† โ†กโ†ขโ†ฃโ†คโ†ฅโ†ฆโ†งโ†จโ†ซโ†ฌโ†ญโ†ฏโ†ฐโ†ฑโ†ฒโ†ณโ†ดโ†ตโ†ถโ†ทโ†ธโ†นโ†บโ†ปโ†ผโ†ฝโ†พโ†ฟโ‡€โ‡โ‡‚โ‡ƒโ‡„โ‡‡โ‡ˆโ‡‰โ‡Šโ‡‹โ‡Œโ‡โ‡โ‡‘โ‡’โ‡โ‡“โ‡”โ‡Žโ‡–โ‡—โ‡˜โ‡™โ‡šโ‡›โ‡œโ‡โ‡žโ‡Ÿโ‡ โ‡กโ‡ขโ‡ฃโ‡คโ‡ฅโ‡ฆโ‡งโ‡จโ‡ฉโ‡ชโ‡ตโˆ€โˆ‚โˆƒโˆ…โˆ‰โˆ‹โˆŽโˆโˆ‘โ‰ฎโ‰ฏโˆ“โˆ•โ„โˆ—โˆ˜โˆ™โˆโˆŸโˆ โˆฃโˆฅโˆงโˆซโˆฌโˆฎโˆดโˆตโˆถโˆทโˆผโˆฝโˆพโ‰ƒโ‰…โ‰Œโ‰’โ‰–โ‰ฃโ‰ฆโ‰งโ‰ชโ‰ซโ‰ฌโ‰ณโ‰บโ‰ปโŠโŠƒโŠ†โŠ‡โŠ•โŠ–โŠ—โŠ˜โŠ™โŠšโŠ›โŠžโŠŸโŠฅโŠฎโŠฐโŠฑโ‹ญโŠถโŠนโŠฟโ‹โ‹‚โ‹ƒโ‹…โ‹†โ‹ˆโ‹’โ‹˜โ‹™โ‹ฎโ‹ฏโ‹ฐโ‹ฑโ– โ–กโ–ขโ–ฃโ–คโ–ฅโ–ฆโ–งโ–จโ–ฉโ–ฌโ–ญโ–ฎโ–ฐโ–ณโ–ดโ–ตโ–ทโ–ธโ–นโ–บโ–ปโ–ฝโ–พโ–ฟโ—โ—‚โ—ƒโ—„โ—…โ—†โ—‡โ—ˆโ—‰โ—Œโ—โ—Žโ—โ—‘โ—’โ—“โ—”โ—•โ—–โ——โ—˜โ—™โ—œโ—โ—žโ—Ÿโ— โ—กโ—ขโ—ฃโ—คโ—ฅโ—ฆโ—ณโ—ทโ—ปโ—ฝโ—ฟโจงโจฏโจผโฉฃโฉฝโชโชšโชบโ‚ขโ‚ฃโ‚คโ‚ฐโ‚ณโ‚ถโ‚ทโ‚จ๏ทผ"}, - // }; - // get the maximum suborder for each subcategory - // Map subcategoryToMaxSuborder = new HashMap<>(); - // for (String[] row : data) { - // final String subcategory = row[0]; - // for (Entry entry : emojiToMinorCategory.entrySet()) { - // if (entry.getValue().equals(subcategory)) { - // String emoji = entry.getKey(); - // Long order = emojiToOrder.get(emoji); - // Long currentMax = subcategoryToMaxSuborder.get(subcategory); - // if (order == null) continue; - // if (currentMax == null || currentMax < order) { - // subcategoryToMaxSuborder.put(subcategory, order); - // } - // } - // } - // } - // if (DEBUG) System.out.println(subcategoryToMaxSuborder); - // Map _EXTRA_SYMBOL_ORDER = new LinkedHashMap<>(); - // for (String[] row : data) { - // final String subcategory = row[0]; - // final String characters = row[1]; - // - // List items = new ArrayList<>(); - // for (int cp : With.codePointArray(characters)) { - // if (cp != ' ') { - // items.add(With.fromCodePoint(cp)); - // } - // } - // final UnicodeSet uset = new UnicodeSet().addAll(items); - // if (uset.containsSome(EXTRA_SYMBOL_MINOR_CATEGORIES.keySet())) { - // throw new IllegalArgumentException("Duplicate values in " + EXTRA_SYMBOL_MINOR_CATEGORIES); - // } - // EXTRA_SYMBOL_MINOR_CATEGORIES.putAll(uset, subcategory); - // final Long countObject = subcategoryToMaxSuborder.get(subcategory); - // if (countObject == null) continue; - // long count = countObject; - // for (String s : items) { - // ++count; - // _EXTRA_SYMBOL_ORDER.put(s, count); - // } - // subcategoryToMaxSuborder.put(subcategory, count); - // } - // if (DEBUG) System.out.println(_EXTRA_SYMBOL_ORDER); - // EXTRA_SYMBOL_MINOR_CATEGORIES.freeze(); - // EXTRA_SYMBOL_ORDER = ImmutableMap.copyOf(_EXTRA_SYMBOL_ORDER); - - /* - # group: Smileys & People - # subgroup: face-positive - 1F600 ; fully-qualified # ๐Ÿ˜€ grinning face - */ - final Splitter semi = Splitter.on(CharMatcher.anyOf(";#")).trimResults(); - String majorCategory; - - final String file = "res/raw/emoji_test.txt"; + final String file = "res/raw/emojis.json"; final ClassLoader classLoader = getClass().getClassLoader(); if (classLoader == null) { Log.e(TAG, "Emoji: classLoader is null"); return; } - try (final InputStream in = classLoader.getResourceAsStream(file); - final BufferedReader reader = new BufferedReader(new InputStreamReader(in))) { - String line; - EmojiCategoryType categoryType = null; - while ((line = reader.readLine()) != null) { - if (line.startsWith("#")) { - line = line.substring(1).trim(); - if (line.startsWith("group:")) { - majorCategory = line.substring("group:".length()).trim(); - if (!majorCategory.equals("Component")) { // Skip Component - if (majorCategory.equals("Smileys & Emotion") || majorCategory.equals("People & Body")) { - // Put 'People & Body' in 'Smileys & Emotion' category - categoryType = EmojiCategoryType.SMILEYS_AND_EMOTION; - } else { - categoryType = EmojiCategoryType.valueOfName(majorCategory); - } - final boolean contains = CATEGORY_MAP.containsKey(categoryType); - if (!contains) { - CATEGORY_MAP.put(categoryType, new EmojiCategory(categoryType)); - } - } - } - continue; - } - if (categoryType == null) continue; - line = line.trim(); - if (line.isEmpty()) { - continue; - } - final Iterator it = semi.split(line).iterator(); - String emojiHex = it.next(); - String original = Utility.fromHex(emojiHex, 4, " "); - String status = it.next(); - if (!status.startsWith("fully-qualified")) { // only use fully qualified - continue; - } - final EmojiCategory emojiCategory = CATEGORY_MAP.get(categoryType); - final Map emojis = emojiCategory == null ? new LinkedHashMap<>() : emojiCategory.getEmojis(); - String comment = it.next(); - // The comment is now of the form: # ๐Ÿ˜ E0.6 beaming face with smiling eyes - int spacePos = comment.indexOf(' '); - spacePos = comment.indexOf(' ', spacePos + 1); // get second space - final String name = comment.substring(spacePos + 1).trim(); - final Emoji emoji = new Emoji(original, name); - ALL_EMOJIS.put(original, emoji); - String minimal = original.replace(EMOJI_VARIANT, ""); - //noinspection deprecation - boolean singleton = CharSequences.getSingleCodePoint(minimal) != Integer.MAX_VALUE; - if (!singleton && SKIN_TONE_MODIFIERS.containsSome(minimal)) { - // skin tone variant - final String parent = minimal.replaceAll(SKIN_TONE_PATTERN, ""); - final Emoji parentEmoji = emojis.get(parent); - if (parentEmoji != null) { - parentEmoji.addVariant(emoji); - } - continue; - } - emojis.put(original, emoji); - // skip constructed values - // if (minimal.contains(COMBINING_ENCLOSING_KEYCAP) - // || REGIONAL_INDICATORS.containsSome(minimal) - // || TAGS.containsSome(minimal) - // || !singleton && MODIFIERS.containsSome(minimal) - // || !singleton && FAMILY.containsAll(minimal)) { - // // do nothing - // } else if (minimal.contains(ZWJ)) { // only do certain ZWJ sequences - // if (SPECIALS.contains(minimal) - // || GENDER.containsSome(minimal) - // || MAN_WOMAN.contains(minimal.codePointAt(0)) && OBJECT.contains(minimal.codePointBefore(minimal.length()))) { - // // nonConstructed.add(minimal); - // } - // } else if (!minimal.contains("๐Ÿ”Ÿ")) { - // // nonConstructed.add(minimal); - // } - } - // for (Entry, String> entry : majorPlusMinorToEmoji.entries()) { - // String minimal = entry.getValue(); - // emojiToOrder.put(minimal, emojiToOrder.size()); - // } + try (final InputStream in = classLoader.getResourceAsStream(file)) { + final String json = NetworkUtils.readFromInputStream(in); + final Gson gson = new Gson(); + final Type type = new TypeToken>() {}.getType(); + categoryMap = gson.fromJson(json, type); + // Log.d(TAG, "EmojiParser: " + categoryMap); + allEmojis = categoryMap.values() + .stream() + .flatMap((Function>) emojiCategory -> { + final Map emojis = emojiCategory.getEmojis(); + return emojis.values().stream(); + }) + .flatMap(emoji -> ImmutableList.builder() + .add(emoji) + .addAll(emoji.getVariants()) + .build() + .stream()) + .collect(Collectors.toMap(Emoji::getUnicode, Function.identity())); } catch (IOException e) { - Log.e(TAG, "Emoji: ", e); + Log.e(TAG, "EmojiParser: ", e); } } - // private static void putUnique(Map map, K key, V value) { - // V oldValue = map.put(key, value); - // if (oldValue != null) { - // throw new ICUException("Attempt to change value of " + map - // + " for " + key - // + " from " + oldValue - // + " to " + value - // ); - // } - // } - public Map getCategoryMap() { - return CATEGORY_MAP; + return categoryMap; } public List getEmojiCategories() { if (categories == null) { - final Collection categoryCollection = CATEGORY_MAP.values(); + final Collection categoryCollection = categoryMap.values(); categories = ImmutableList.copyOf(categoryCollection); } return categories; } public Map getAllEmojis() { - return ALL_EMOJIS; + return allEmojis; } public Emoji getEmoji(final String emoji) { if (emoji == null) { return null; } - return ALL_EMOJIS.get(emoji); + return allEmojis.get(emoji); } - - // public String getMinorCategory(String emoji) { - // String minorCat = emojiToMinorCategory.get(emoji); - // if (minorCat == null) { - // minorCat = EXTRA_SYMBOL_MINOR_CATEGORIES.get(emoji); - // if (minorCat == null) { - // throw new InternalCldrException("No minor category (aka subgroup) found for " + emoji - // + ". Update emoji-test.txt to latest, and setValue PathHeader.. functionMap.put(\"minor\", ..."); - // } - // } - // return minorCat; - // } - - // public long getEmojiToOrder(String emoji) { - // Long result = emojiToOrder.get(emoji); - // if (result == null) { - // result = EXTRA_SYMBOL_ORDER.get(emoji); - // if (result == null) { - // throw new InternalCldrException("No Order found for " + emoji - // + ". Update emoji-test.txt to latest, and setValue PathHeader.. functionMap.put(\"minor\", ..."); - // } - // } - // return result; - // } - - // public long getEmojiMinorOrder(String minor) { - // Long result = minorToOrder.get(minor); - // if (result == null) { - // throw new InternalCldrException("No minor category (aka subgroup) found for " + minor - // + ". Update emoji-test.txt to latest, and setValue PathHeader.. functionMap.put(\"minor\", ..."); - // } - // return result; - // } - - // public String getMajorCategory(String emoji) { - // String majorCat = emojiToMajorCategory.get(emoji); - // if (majorCat == null) { - // if (EXTRA_SYMBOL_MINOR_CATEGORIES.containsKey(emoji)) { - // majorCat = "Symbols"; - // } else { - // throw new InternalCldrException("No minor category (aka subgroup) found for " + emoji - // + ". Update emoji-test.txt to latest, and setValue PathHeader.. functionMap.put(\"major\", ..."); - // } - // } - // return majorCat; - // } - - // public Set getMinorCategoriesWithExtras() { - // Set result = new LinkedHashSet<>(emojiToMinorCategory.values()); - // result.addAll(EXTRA_SYMBOL_MINOR_CATEGORIES.getAvailableValues()); - // return ImmutableSet.copyOf(result); - // } - - // public UnicodeSet getEmojiInMinorCategoriesWithExtras(String minorCategory) { - // return new UnicodeSet(emojiToMinorCategory.getSet(minorCategory)) - // .addAll(EXTRA_SYMBOL_MINOR_CATEGORIES.getSet(minorCategory)) - // .freeze(); - // } - - // public synchronized Set getNamePaths() { - // return NAME_PATHS != null ? NAME_PATHS : (NAME_PATHS = buildPaths(TYPE_TTS)); - // } - - // public synchronized Set getKeywordPaths() { - // return KEYWORD_PATHS != null ? KEYWORD_PATHS : (KEYWORD_PATHS = buildPaths("")); - // } - - // private ImmutableSet buildPaths(String suffix) { - // ImmutableSet.Builder builder = ImmutableSet.builder(); - // for (String s : getNonConstructed()) { - // String base = "//ldml/annotations/annotation[@cp=\"" + s + "\"]" + suffix; - // builder.add(base); - // } - // return builder.build(); - // } } diff --git a/app/src/main/java/awais/instagrabber/utils/emoji/ImmutableEntry.java b/app/src/main/java/awais/instagrabber/utils/emoji/ImmutableEntry.java deleted file mode 100644 index 84671dcb..00000000 --- a/app/src/main/java/awais/instagrabber/utils/emoji/ImmutableEntry.java +++ /dev/null @@ -1,50 +0,0 @@ -package awais.instagrabber.utils.emoji; - -// ยฉ 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html#License -/* - ******************************************************************************* - * Copyright (C) 2009-2012, International Business Machines Corporation and * - * others. All Rights Reserved. * - ******************************************************************************* - */ - -import java.util.Map; - -/** - * @author markdavis - */ -public class ImmutableEntry implements Map.Entry { - final K k; - final V v; - - ImmutableEntry(K key, V value) { - k = key; - v = value; - } - - public K getKey() {return k;} - - public V getValue() {return v;} - - public V setValue(V value) { - throw new UnsupportedOperationException(); - } - - public boolean equals(Object o) { - try { - Map.Entry e = (Map.Entry) o; - return UnicodeMap.areEqual(e.getKey(), k) && UnicodeMap.areEqual(e.getValue(), v); - } catch (ClassCastException e) { - return false; - } - } - - public int hashCode() { - return ((k == null ? 0 : k.hashCode()) ^ (v == null ? 0 : v.hashCode())); - } - - public String toString() { - return k + "=" + v; - } -} diff --git a/app/src/main/java/awais/instagrabber/utils/emoji/UnicodeMap.java b/app/src/main/java/awais/instagrabber/utils/emoji/UnicodeMap.java deleted file mode 100644 index 4c507be0..00000000 --- a/app/src/main/java/awais/instagrabber/utils/emoji/UnicodeMap.java +++ /dev/null @@ -1,1305 +0,0 @@ -package awais.instagrabber.utils.emoji; - -// ยฉ 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html#License -/* - ******************************************************************************* - * Copyright (C) 1996-2016, International Business Machines Corporation and * - * others. All Rights Reserved. * - ******************************************************************************* - */ - -import com.ibm.icu.impl.Utility; -import com.ibm.icu.text.StringTransform; -import com.ibm.icu.text.UTF16; -import com.ibm.icu.text.UnicodeSet; -import com.ibm.icu.text.UnicodeSetIterator; -import com.ibm.icu.util.Freezable; - -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Set; -import java.util.TreeMap; -import java.util.TreeSet; - -/** - * Class for mapping Unicode characters and strings to values, optimized for single code points, - * where ranges of code points have the same value. - * Much smaller storage than using HashMap, and much faster and more compact than - * a list of UnicodeSets. The API design mimics Map but can't extend it due to some - * necessary changes (much as UnicodeSet mimics Set). Note that nulls are not permitted as values; - * that is, a put(x,null) is the same as remove(x).
- * At this point "" is also not allowed as a key, although that may change. - * - * @author markdavis - */ - -public final class UnicodeMap implements Cloneable, Freezable>, StringTransform, Iterable { - /** - * For serialization - */ - //private static final long serialVersionUID = -6540936876295804105L; - static final boolean ASSERTIONS = false; - static final long GROWTH_PERCENT = 200; // 100 is no growth! - static final long GROWTH_GAP = 10; // extra bump! - - private int length; - // two parallel arrays to save memory. Wish Java had structs. - private int[] transitions; - /* package private */ T[] values; - - private LinkedHashSet availableValues = new LinkedHashSet(); - private transient boolean staleAvailableValues; - - private transient boolean errorOnReset; - private volatile transient boolean locked; - private int lastIndex; - private TreeMap stringMap; - - { clear(); } - - public UnicodeMap() { - } - - public UnicodeMap(UnicodeMap other) { - this.putAll(other); - } - - public UnicodeMap clear() { - if (locked) { - throw new UnsupportedOperationException("Attempt to modify locked object"); - } - length = 2; - transitions = new int[]{0, 0x110000, 0, 0, 0, 0, 0, 0, 0, 0}; - values = (T[]) new Object[10]; - - availableValues.clear(); - staleAvailableValues = false; - - errorOnReset = false; - lastIndex = 0; - stringMap = null; - return this; - } - - /* Boilerplate */ - public boolean equals(Object other) { - if (other == null) return false; - try { - UnicodeMap that = (UnicodeMap) other; - if (length != that.length) return false; - for (int i = 0; i < length - 1; ++i) { - if (transitions[i] != that.transitions[i]) return false; - if (!areEqual(values[i], that.values[i])) return false; - } - return true; - } catch (ClassCastException e) { - return false; - } - } - - public static boolean areEqual(Object a, Object b) { - if (a == b) return true; - if (a == null || b == null) return false; - return a.equals(b); - } - - public int hashCode() { - int result = length; - // TODO might want to abbreviate this for speed. - for (int i = 0; i < length - 1; ++i) { - result = 37 * result + transitions[i]; - result = 37 * result; - if (values[i] != null) { - result += values[i].hashCode(); - } - } - if (stringMap != null) { - result = 37 * result + stringMap.hashCode(); - } - return result; - } - - /** - * Standard clone. Warning, as with Collections, does not do deep clone. - */ - public UnicodeMap cloneAsThawed() { - UnicodeMap that = new UnicodeMap(); - that.length = length; - that.transitions = (int[]) transitions.clone(); - that.values = (T[]) values.clone(); - that.availableValues = new LinkedHashSet(availableValues); - that.locked = false; - that.stringMap = stringMap == null ? null : (TreeMap) stringMap.clone(); - return that; - } - - /* for internal consistency checking */ - - void _checkInvariants() { - if (length < 2 - || length > transitions.length - || transitions.length != values.length) { - throw new IllegalArgumentException("Invariant failed: Lengths bad"); - } - for (int i = 1; i < length - 1; ++i) { - if (areEqual(values[i - 1], values[i])) { - throw new IllegalArgumentException("Invariant failed: values shared at " - + "\t" + Utility.hex(i - 1) + ": <" + values[i - 1] + ">" - + "\t" + Utility.hex(i) + ": <" + values[i] + ">" - ); - } - } - if (transitions[0] != 0 || transitions[length - 1] != 0x110000) { - throw new IllegalArgumentException("Invariant failed: bounds set wrong"); - } - for (int i = 1; i < length - 1; ++i) { - if (transitions[i - 1] >= transitions[i]) { - throw new IllegalArgumentException("Invariant failed: not monotonic" - + "\t" + Utility.hex(i - 1) + ": " + transitions[i - 1] - + "\t" + Utility.hex(i) + ": " + transitions[i] - ); - } - } - } - - /** - * Finds an index such that inversionList[i] <= codepoint < inversionList[i+1] - * Assumes that 0 <= codepoint <= 0x10FFFF - * - * @param c codepoint - * @return the index - */ - private int _findIndex(int c) { - int lo = 0; - int hi = length - 1; - int i = (lo + hi) >>> 1; - // invariant: c >= list[lo] - // invariant: c < list[hi] - while (i != lo) { - if (c < transitions[i]) { - hi = i; - } else { - lo = i; - } - i = (lo + hi) >>> 1; - } - if (ASSERTIONS) _checkFind(c, lo); - return lo; - } - - private void _checkFind(int codepoint, int value) { - int other = __findIndex(codepoint); - if (other != value) { - throw new IllegalArgumentException("Invariant failed: binary search" - + "\t" + Utility.hex(codepoint) + ": " + value - + "\tshould be: " + other); - } - } - - private int __findIndex(int codepoint) { - for (int i = length - 1; i > 0; --i) { - if (transitions[i] <= codepoint) return i; - } - return 0; - } - - /* - * Try indexed lookup - - static final int SHIFT = 8; - int[] starts = new int[0x10FFFF>>SHIFT]; // lowest transition index where codepoint>>x can be found - boolean startsValid = false; - private int findIndex(int codepoint) { - if (!startsValid) { - int start = 0; - for (int i = 1; i < length; ++i) { - - } - } - for (int i = length-1; i > 0; --i) { - if (transitions[i] <= codepoint) return i; - } - return 0; - } - */ - - /** - * Remove the items from index through index+count-1. - * Logically reduces the size of the internal arrays. - * - * @param index - * @param count - */ - private void _removeAt(int index, int count) { - for (int i = index + count; i < length; ++i) { - transitions[i - count] = transitions[i]; - values[i - count] = values[i]; - } - length -= count; - } - - /** - * Add a gap from index to index+count-1. - * The values there are undefined, and must be set. - * Logically grows arrays to accomodate. Actual growth is limited - * - * @param index - * @param count - */ - private void _insertGapAt(int index, int count) { - int newLength = length + count; - int[] oldtransitions = transitions; - T[] oldvalues = values; - if (newLength > transitions.length) { - int allocation = (int) (GROWTH_GAP + (newLength * GROWTH_PERCENT) / 100); - transitions = new int[allocation]; - values = (T[]) new Object[allocation]; - for (int i = 0; i < index; ++i) { - transitions[i] = oldtransitions[i]; - values[i] = oldvalues[i]; - } - } - for (int i = length - 1; i >= index; --i) { - transitions[i + count] = oldtransitions[i]; - values[i + count] = oldvalues[i]; - } - length = newLength; - } - - /** - * Associates code point with value. Removes any previous association. - * All code that calls this MUST check for frozen first! - * - * @param codepoint - * @param value - * @return this, for chaining - */ - private UnicodeMap _put(int codepoint, T value) { - // Warning: baseIndex is an invariant; must - // be defined such that transitions[baseIndex] < codepoint - // at end of this routine. - int baseIndex; - if (transitions[lastIndex] <= codepoint - && codepoint < transitions[lastIndex + 1]) { - baseIndex = lastIndex; - } else { - baseIndex = _findIndex(codepoint); - } - int limitIndex = baseIndex + 1; - // cases are (a) value is already set - if (areEqual(values[baseIndex], value)) return this; - if (locked) { - throw new UnsupportedOperationException("Attempt to modify locked object"); - } - if (errorOnReset && values[baseIndex] != null) { - throw new UnsupportedOperationException("Attempt to reset value for " + Utility.hex(codepoint) - + " when that is disallowed. Old: " + values[baseIndex] + "; New: " + value); - } - - // setValue the available values - staleAvailableValues = true; - availableValues.add(value); // add if not there already - - int baseCP = transitions[baseIndex]; - int limitCP = transitions[limitIndex]; - // we now start walking through the difference case, - // based on whether we are at the start or end of range - // and whether the range is a single character or multiple - - if (baseCP == codepoint) { - // CASE: At very start of range - boolean connectsWithPrevious = - baseIndex != 0 && areEqual(value, values[baseIndex - 1]); - - if (limitCP == codepoint + 1) { - // CASE: Single codepoint range - boolean connectsWithFollowing = - baseIndex < length - 2 && areEqual(value, values[limitIndex]); // was -1 - - if (connectsWithPrevious) { - // A1a connects with previous & following, so remove index - if (connectsWithFollowing) { - _removeAt(baseIndex, 2); - } else { - _removeAt(baseIndex, 1); // extend previous - } - --baseIndex; // fix up - } else if (connectsWithFollowing) { - _removeAt(baseIndex, 1); // extend following backwards - transitions[baseIndex] = codepoint; - } else { - // doesn't connect on either side, just reset - values[baseIndex] = value; - } - } else if (connectsWithPrevious) { - // A.1: start of multi codepoint range - // if connects - ++transitions[baseIndex]; // extend previous - } else { - // otherwise insert new transition - transitions[baseIndex] = codepoint + 1; // fix following range - _insertGapAt(baseIndex, 1); - values[baseIndex] = value; - transitions[baseIndex] = codepoint; - } - } else if (limitCP == codepoint + 1) { - // CASE: at end of range - // if connects, just back up range - boolean connectsWithFollowing = - baseIndex < length - 2 && areEqual(value, values[limitIndex]); // was -1 - - if (connectsWithFollowing) { - --transitions[limitIndex]; - return this; - } else { - _insertGapAt(limitIndex, 1); - transitions[limitIndex] = codepoint; - values[limitIndex] = value; - } - } else { - // CASE: in middle of range - // insert gap, then set the new range - _insertGapAt(++baseIndex, 2); - transitions[baseIndex] = codepoint; - values[baseIndex] = value; - transitions[baseIndex + 1] = codepoint + 1; - values[baseIndex + 1] = values[baseIndex - 1]; // copy lower range values - } - lastIndex = baseIndex; // store for next time - return this; - } - - private UnicodeMap _putAll(int startCodePoint, int endCodePoint, T value) { - // TODO optimize - for (int i = startCodePoint; i <= endCodePoint; ++i) { - _put(i, value); - if (ASSERTIONS) _checkInvariants(); - } - return this; - } - - /** - * Sets the codepoint value. - * - * @param codepoint - * @param value - * @return this (for chaining) - */ - public UnicodeMap put(int codepoint, T value) { - if (codepoint < 0 || codepoint > 0x10FFFF) { - throw new IllegalArgumentException("Codepoint out of range: " + codepoint); - } - _put(codepoint, value); - if (ASSERTIONS) _checkInvariants(); - return this; - } - - /** - * Sets the codepoint value. - * - * @param string codepoint - * @param value - * @return this (for chaining) - */ - public UnicodeMap put(String string, T value) { - int v = UnicodeSet.getSingleCodePoint(string); - if (v == Integer.MAX_VALUE) { - if (locked) { - throw new UnsupportedOperationException("Attempt to modify locked object"); - } - if (value != null) { - if (stringMap == null) { - stringMap = new TreeMap(); - } - stringMap.put(string, value); - staleAvailableValues = true; - } else if (stringMap != null) { - if (stringMap.remove(string) != null) { - staleAvailableValues = true; - } - } - return this; - } - return put(v, value); - } - - /** - * Adds bunch o' codepoints; otherwise like put. - * - * @param codepoints - * @param value - * @return this (for chaining) - */ - public UnicodeMap putAll(UnicodeSet codepoints, T value) { - UnicodeSetIterator it = new UnicodeSetIterator(codepoints); - while (it.nextRange()) { - if (it.string == null) { - _putAll(it.codepoint, it.codepointEnd, value); - } else { - put(it.string, value); - } - } - return this; - } - - /** - * Adds bunch o' codepoints; otherwise like add. - * - * @param startCodePoint - * @param endCodePoint - * @param value - * @return this (for chaining) - */ - public UnicodeMap putAll(int startCodePoint, int endCodePoint, T value) { - if (locked) { - throw new UnsupportedOperationException("Attempt to modify locked object"); - } - if (startCodePoint < 0 || endCodePoint > 0x10FFFF) { - throw new IllegalArgumentException("Codepoint out of range: " - + Utility.hex(startCodePoint) + ".." + Utility.hex(endCodePoint)); - } - return _putAll(startCodePoint, endCodePoint, value); - } - - /** - * Add all the (main) values from a UnicodeMap - * - * @param unicodeMap the property to add to the map - * @return this (for chaining) - */ - public UnicodeMap putAll(UnicodeMap unicodeMap) { - for (int i = 0; i < unicodeMap.length; ++i) { - T value = unicodeMap.values[i]; - if (value != null) { - _putAll(unicodeMap.transitions[i], unicodeMap.transitions[i + 1] - 1, value); - } - if (ASSERTIONS) _checkInvariants(); - } - if (unicodeMap.stringMap != null && !unicodeMap.stringMap.isEmpty()) { - if (stringMap == null) { - stringMap = new TreeMap(); - } - stringMap.putAll(unicodeMap.stringMap); - } - return this; - } - - /** - * Add all the (main) values from a Unicode property - * - * @param prop the property to add to the map - * @return this (for chaining) - */ - public UnicodeMap putAllFiltered(UnicodeMap prop, UnicodeSet filter) { - // TODO optimize - for (UnicodeSetIterator it = new UnicodeSetIterator(filter); it.next(); ) { - if (it.codepoint != UnicodeSetIterator.IS_STRING) { - T value = prop.getValue(it.codepoint); - if (value != null) { - _put(it.codepoint, value); - } - } - } - // now do the strings - for (String key : filter.strings()) { - T value = prop.get(key); - if (value != null) { - put(key, value); - } - } - return this; - } - - /** - * Set the currently unmapped Unicode code points to the given value. - * - * @param value the value to set - * @return this (for chaining) - */ - public UnicodeMap setMissing(T value) { - // fast path, if value not yet present - if (!getAvailableValues().contains(value)) { - staleAvailableValues = true; - availableValues.add(value); - for (int i = 0; i < length; ++i) { - if (values[i] == null) values[i] = value; - } - return this; - } else { - return putAll(keySet(null), value); - } - } - - /** - * Returns the keyset consisting of all the keys that would produce the given value. Deposits into - * result if it is not null. Remember to clear if you just want - * the new values. - */ - public UnicodeSet keySet(T value, UnicodeSet result) { - if (result == null) result = new UnicodeSet(); - for (int i = 0; i < length - 1; ++i) { - if (areEqual(value, values[i])) { - result.add(transitions[i], transitions[i + 1] - 1); - } - } - if (value != null && stringMap != null) { - for (String key : stringMap.keySet()) { - T newValue = stringMap.get(key); - if (value.equals(newValue)) { - result.add((String) key); - } - } - } - return result; - } - - /** - * Returns the keyset consisting of all the keys that would produce the given value. - * the new values. - */ - public UnicodeSet keySet(T value) { - return keySet(value, null); - } - - /** - * Returns the keyset consisting of all the keys that would produce (non-null) values. - */ - public UnicodeSet keySet() { - UnicodeSet result = new UnicodeSet(); - for (int i = 0; i < length - 1; ++i) { - if (values[i] != null) { - result.add(transitions[i], transitions[i + 1] - 1); - } - } - if (stringMap != null) { - result.addAll(stringMap.keySet()); - } - return result; - } - - /** - * Returns the list of possible values. Deposits each non-null value into - * result. Creates result if it is null. Remember to clear result if - * you are not appending to existing collection. - * - * @param result - * @return result - */ - public > U values(U result) { - if (staleAvailableValues) { - // collect all the current values - // retain them in the availableValues - Set temp = new HashSet(); - for (int i = 0; i < length - 1; ++i) { - if (values[i] != null) temp.add(values[i]); - } - availableValues.retainAll(temp); - if (stringMap != null) { - availableValues.addAll(stringMap.values()); - } - staleAvailableValues = false; - } - if (result == null) { - result = (U) new LinkedHashSet(availableValues.size()); - } - result.addAll(availableValues); - return result; - } - - /** - * Convenience method - */ - public Set values() { - return getAvailableValues(null); - } - - /** - * Gets the value associated with a given code point. - * Returns null, if there is no such value. - * - * @param codepoint - * @return the value - */ - public T get(int codepoint) { - if (codepoint < 0 || codepoint > 0x10FFFF) { - throw new IllegalArgumentException("Codepoint out of range: " + codepoint); - } - return values[_findIndex(codepoint)]; - } - - /** - * Gets the value associated with a given code point. - * Returns null, if there is no such value. - * - * @param value codepoint - * @return the value - */ - public T get(String value) { - if (UTF16.hasMoreCodePointsThan(value, 1)) { - if (stringMap == null) { - return null; - } - return stringMap.get(value); - } - return getValue(UTF16.charAt(value, 0)); - } - - - /** - * Change a new string from the source string according to the mappings. - * For each code point cp, if getValue(cp) is null, append the character, otherwise append getValue(cp).toString() - * TODO: extend to strings - * - * @param source - * @return - */ - public String transform(String source) { - StringBuffer result = new StringBuffer(); - int cp; - for (int i = 0; i < source.length(); i += UTF16.getCharCount(cp)) { - cp = UTF16.charAt(source, i); - T mResult = getValue(cp); - if (mResult != null) { - result.append(mResult); - } else { - UTF16.append(result, cp); - } - } - return result.toString(); - } - - /** - * Used to add complex values, where the value isn't replaced but in some sense composed - * - * @author markdavis - */ - public abstract static class Composer { - /** - * This will be called with either a string or a code point. The result is the new value for that item. - * If the codepoint is used, the string is null; if the string is used, the codepoint is -1. - * - * @param a - * @param b - */ - public abstract T compose(int codePoint, String string, T a, T b); - } - - public UnicodeMap composeWith(UnicodeMap other, Composer composer) { - for (T value : other.getAvailableValues()) { - UnicodeSet set = other.keySet(value); - composeWith(set, value, composer); - } - return this; - } - - public UnicodeMap composeWith(UnicodeSet set, T value, Composer composer) { - for (UnicodeSetIterator it = new UnicodeSetIterator(set); it.next(); ) { - int i = it.codepoint; - if (i == UnicodeSetIterator.IS_STRING) { - String s = it.string; - T v1 = getValue(s); - T v3 = composer.compose(-1, s, v1, value); - if (!Objects.equals(v1, v3)) { - put(s, v3); - } - } else { - T v1 = getValue(i); - T v3 = composer.compose(i, null, v1, value); - if (!Objects.equals(v1, v3)) { - put(i, v3); - } - } - } - return this; - } - - public String toString() { - return toString(null); - } - - public String toString(Comparator collected) { - StringBuffer result = new StringBuffer(); - if (collected == null) { - for (int i = 0; i < length - 1; ++i) { - T value = values[i]; - if (value == null) continue; - int start = transitions[i]; - int end = transitions[i + 1] - 1; - result.append(Utility.hex(start)); - if (start != end) result.append("-").append(Utility.hex(end)); - result.append("=").append(value.toString()).append("\n"); - } - if (stringMap != null) { - for (String s : stringMap.keySet()) { - result.append(Utility.hex(s)).append("=").append(stringMap.get(s).toString()).append("\n"); - } - } - } else { - Set set = values(new TreeSet(collected)); - for (Iterator it = set.iterator(); it.hasNext(); ) { - T value = it.next(); - UnicodeSet s = keySet(value); - result.append(value).append("=").append(s.toString()).append("\n"); - } - } - return result.toString(); - } - - /** - * @return Returns the errorOnReset value. - */ - public boolean getErrorOnReset() { - return errorOnReset; - } - - /** - * Puts the UnicodeMap into a state whereby new mappings are accepted, but changes to old mappings cause an exception. - * - * @param errorOnReset The errorOnReset to set. - */ - public UnicodeMap setErrorOnReset(boolean errorOnReset) { - this.errorOnReset = errorOnReset; - return this; - } - - /* (non-Javadoc) - * @see com.ibm.icu.dev.test.util.Freezable#isFrozen() - */ - public boolean isFrozen() { - // TODO Auto-generated method stub - return locked; - } - - /* (non-Javadoc) - * @see com.ibm.icu.dev.test.util.Freezable#lock() - */ - public UnicodeMap freeze() { - locked = true; - return this; - } - - /** - * Utility to find the maximal common prefix of two strings. - * TODO: fix supplemental support - */ - static public int findCommonPrefix(String last, String s) { - int minLen = Math.min(last.length(), s.length()); - for (int i = 0; i < minLen; ++i) { - if (last.charAt(i) != s.charAt(i)) return i; - } - return minLen; - } - - /** - * Get the number of ranges; used for getRangeStart/End. The ranges together cover all of the single-codepoint keys in the UnicodeMap. Other keys can be gotten with getStrings(). - */ - public int getRangeCount() { - return length - 1; - } - - /** - * Get the start of a range. All code points between start and end are in the UnicodeMap's keyset. - */ - public int getRangeStart(int range) { - return transitions[range]; - } - - /** - * Get the start of a range. All code points between start and end are in the UnicodeMap's keyset. - */ - public int getRangeEnd(int range) { - return transitions[range + 1] - 1; - } - - /** - * Get the value for the range. - */ - public T getRangeValue(int range) { - return values[range]; - } - - /** - * Get the strings that are not in the ranges. Returns null if there are none. - * - * @return - */ - public Set getNonRangeStrings() { - if (stringMap == null || stringMap.isEmpty()) { - return null; - } - return Collections.unmodifiableSet(stringMap.keySet()); - } - - static final boolean DEBUG_WRITE = false; - - /* (non-Javadoc) - * @see java.util.Map#containsKey(java.lang.Object) - */ - public boolean containsKey(String key) { - return getValue(key) != null; - } - - /* (non-Javadoc) - * @see java.util.Map#containsKey(java.lang.Object) - */ - public boolean containsKey(int key) { - return getValue(key) != null; - } - - /* (non-Javadoc) - * @see java.util.Map#containsValue(java.lang.Object) - */ - public boolean containsValue(T value) { - // TODO Optimize - return getAvailableValues().contains(value); - } - - /* (non-Javadoc) - * @see java.util.Map#isEmpty() - */ - public boolean isEmpty() { - return size() == 0; - } - - /* (non-Javadoc) - * @see java.util.Map#putAll(java.util.Map) - */ - public UnicodeMap putAll(Map map) { - for (String key : map.keySet()) { - put(key, map.get(key)); - } - return this; - } - - /** - * Utility for extracting map - * - * @deprecated - */ - public UnicodeMap putAllIn(Map map) { - for (String key : keySet()) { - map.put(key, get(key)); - } - return this; - } - - /** - * Utility for extracting map - */ - public > U putAllInto(U map) { - for (EntryRange entry : entryRanges()) { - if (entry.string != null) { - break; - } - for (int cp = entry.codepoint; cp <= entry.codepointEnd; ++cp) { - map.put(UTF16.valueOf(cp), entry.value); - } - } - map.putAll(stringMap); - return map; - } - - /** - * Utility for extracting map - */ - public > U putAllCodepointsInto(U map) { - for (EntryRange entry : entryRanges()) { - if (entry.string != null) { - break; - } - for (int cp = entry.codepoint; cp <= entry.codepointEnd; ++cp) { - map.put(cp, entry.value); - } - } - return map; - } - - /* (non-Javadoc) - * @see java.util.Map#remove(java.lang.Object) - */ - public UnicodeMap remove(String key) { - return put(key, null); - } - - /* (non-Javadoc) - * @see java.util.Map#remove(java.lang.Object) - */ - public UnicodeMap remove(int key) { - return put(key, null); - } - - /* (non-Javadoc) - * @see java.util.Map#size() - */ - public int size() { - int result = stringMap == null ? 0 : stringMap.size(); - for (int i = 0; i < length - 1; ++i) { - T value = values[i]; - if (value == null) continue; - result += transitions[i + 1] - transitions[i]; - } - return result; - } - - /* (non-Javadoc) - * @see java.util.Map#entrySet() - */ - public Iterable> entrySet() { - return new EntrySetX(); - } - - private class EntrySetX implements Iterable> { - public Iterator> iterator() { - return new IteratorX(); - } - - public String toString() { - StringBuffer b = new StringBuffer(); - for (Iterator it = iterator(); it.hasNext(); ) { - Object item = it.next(); - b.append(item.toString()).append(' '); - } - return b.toString(); - } - } - - private class IteratorX implements Iterator> { - Iterator iterator = keySet().iterator(); - - /* (non-Javadoc) - * @see java.util.Iterator#hasNext() - */ - public boolean hasNext() { - return iterator.hasNext(); - } - - /* (non-Javadoc) - * @see java.util.Iterator#next() - */ - public Entry next() { - String key = iterator.next(); - return new ImmutableEntry(key, get(key)); - } - - /* (non-Javadoc) - * @see java.util.Iterator#remove() - */ - public void remove() { - throw new UnsupportedOperationException(); - } - - } - - /** - * Struct-like class used to iterate over a UnicodeMap in a for loop. - * If the value is a string, then codepoint == codepointEnd == -1. Otherwise the string is null; - * Caution: The contents may change during the iteration! - */ - public static class EntryRange { - public int codepoint; - public int codepointEnd; - public String string; - public T value; - - @Override - public String toString() { - return (string != null ? Utility.hex(string) - : Utility.hex(codepoint) + (codepoint == codepointEnd ? "" : ".." + Utility.hex(codepointEnd))) - + "=" + value; - } - } - - /** - * Returns an Iterable over EntryRange, designed for efficient for loops over UnicodeMaps. - * Caution: For efficiency, the EntryRange may be reused, so the EntryRange may change on each iteration! - * The value is guaranteed never to be null. The entryRange.string values (non-null) are after all the ranges. - * - * @return entry range, for for loops - */ - public Iterable> entryRanges() { - return new EntryRanges(); - } - - private class EntryRanges implements Iterable>, Iterator> { - private int pos; - private EntryRange result = new EntryRange(); - private int lastRealRange = values[length - 2] == null ? length - 2 : length - 1; - private Iterator> stringIterator = stringMap == null ? null : stringMap.entrySet().iterator(); - - public Iterator> iterator() { - return this; - } - - public boolean hasNext() { - return pos < lastRealRange || (stringIterator != null && stringIterator.hasNext()); - } - - public EntryRange next() { - // a range may be null, but then the next one must not be (except the final range) - if (pos < lastRealRange) { - T temp = values[pos]; - if (temp == null) { - temp = values[++pos]; - } - result.codepoint = transitions[pos]; - result.codepointEnd = transitions[pos + 1] - 1; - result.string = null; - result.value = temp; - ++pos; - } else { - Entry entry = stringIterator.next(); - result.codepoint = result.codepointEnd = -1; - result.string = entry.getKey(); - result.value = entry.getValue(); - } - return result; - } - - public void remove() { - throw new UnsupportedOperationException(); - } - } - - /* (non-Javadoc) - * @see java.lang.Iterable#iterator() - */ - public Iterator iterator() { - return keySet().iterator(); - } - - /** - * Old form for compatibility - */ - public T getValue(String key) { - return get(key); - } - - /** - * Old form for compatibility - */ - public T getValue(int key) { - // TODO Auto-generated method stub - return get(key); - } - - /** - * Old form for compatibility - */ - public Collection getAvailableValues() { - return values(); - } - - /** - * Old form for compatibility - */ - public > U getAvailableValues(U result) { - return values(result); - } - - /** - * Old form for compatibility - */ - public UnicodeSet getSet(T value) { - return keySet(value); - } - - /** - * Old form for compatibility - */ - public UnicodeSet getSet(T value, UnicodeSet result) { - return keySet(value, result); - } - - // This is to support compressed serialization. It works; just commented out for now as we shift to Generics - // TODO Fix once generics are cleaned up. - // // TODO Fix to serialize more than just strings. - // // Only if all the items are strings will we do the following compression - // // Otherwise we'll just use Java Serialization, bulky as it is - // public void writeExternal(ObjectOutput out1) throws IOException { - // DataOutputCompressor sc = new DataOutputCompressor(out1); - // // if all objects are strings - // Collection availableVals = getAvailableValues(); - // boolean allStrings = allAreString(availableVals); - // sc.writeBoolean(allStrings); - // Map object_index = new LinkedHashMap(); - // if (allAreString(availableVals)) { - // sc.writeStringSet(new TreeSet(availableVals), object_index); - // } else { - // sc.writeCollection(availableVals, object_index); - // } - // sc.writeUInt(length); - // int lastTransition = -1; - // int lastValueNumber = 0; - // if (DEBUG_WRITE) System.out.println("Trans count: " + length); - // for (int i = 0; i < length; ++i) { - // int valueNumber = ((Integer)object_index.get(values[i])).intValue(); - // if (DEBUG_WRITE) System.out.println("Trans: " + transitions[i] + ",\t" + valueNumber); - // - // int deltaTransition = transitions[i] - lastTransition; - // lastTransition = transitions[i]; - // int deltaValueNumber = valueNumber - lastValueNumber; - // lastValueNumber = valueNumber; - // - // deltaValueNumber <<= 1; // make room for one bit - // boolean canCombine = deltaTransition == 1; - // if (canCombine) deltaValueNumber |= 1; - // sc.writeInt(deltaValueNumber); - // if (DEBUG_WRITE) System.out.println("deltaValueNumber: " + deltaValueNumber); - // if (!canCombine) { - // sc.writeUInt(deltaTransition); - // if (DEBUG_WRITE) System.out.println("deltaTransition: " + deltaTransition); - // } - // } - // sc.flush(); - // } - // - // /** - // * - // */ - // private boolean allAreString(Collection availableValues2) { - // //if (true) return false; - // for (Iterator it = availableValues2.iterator(); it.hasNext();) { - // if (!(it.next() instanceof String)) return false; - // } - // return true; - // } - // - // public void readExternal(ObjectInput in1) throws IOException, ClassNotFoundException { - // DataInputCompressor sc = new DataInputCompressor(in1); - // boolean allStrings = sc.readBoolean(); - // T[] valuesList; - // availableValues = new LinkedHashSet(); - // if (allStrings) { - // valuesList = sc.readStringSet(availableValues); - // } else { - // valuesList = sc.readCollection(availableValues); - // } - // length = sc.readUInt(); - // transitions = new int[length]; - // if (DEBUG_WRITE) System.out.println("Trans count: " + length); - // values = (T[]) new Object[length]; - // int currentTransition = -1; - // int currentValue = 0; - // int deltaTransition; - // for (int i = 0; i < length; ++i) { - // int temp = sc.readInt(); - // if (DEBUG_WRITE) System.out.println("deltaValueNumber: " + temp); - // boolean combined = (temp & 1) != 0; - // temp >>= 1; - // values[i] = valuesList[currentValue += temp]; - // if (!combined) { - // deltaTransition = sc.readUInt(); - // if (DEBUG_WRITE) System.out.println("deltaTransition: " + deltaTransition); - // } else { - // deltaTransition = 1; - // } - // transitions[i] = currentTransition += deltaTransition; // delta value - // if (DEBUG_WRITE) System.out.println("Trans: " + transitions[i] + ",\t" + currentValue); - // } - // } - - public final UnicodeMap removeAll(UnicodeSet set) { - return putAll(set, null); - } - - public final UnicodeMap removeAll(UnicodeMap reference) { - return removeRetainAll(reference, true); - } - - public final UnicodeMap retainAll(UnicodeSet set) { - UnicodeSet toNuke = new UnicodeSet(); - // TODO Optimize - for (EntryRange ae : entryRanges()) { - if (ae.string != null) { - if (!set.contains(ae.string)) { - toNuke.add(ae.string); - } - } else { - for (int i = ae.codepoint; i <= ae.codepointEnd; ++i) { - if (!set.contains(i)) { - toNuke.add(i); - } - } - } - } - return putAll(toNuke, null); - } - - public final UnicodeMap retainAll(UnicodeMap reference) { - return removeRetainAll(reference, false); - } - - private final UnicodeMap removeRetainAll(UnicodeMap reference, boolean remove) { - UnicodeSet toNuke = new UnicodeSet(); - // TODO Optimize - for (EntryRange ae : entryRanges()) { - if (ae.string != null) { - if (ae.value.equals(reference.get(ae.string)) == remove) { - toNuke.add(ae.string); - } - } else { - for (int i = ae.codepoint; i <= ae.codepointEnd; ++i) { - if (ae.value.equals(reference.get(i)) == remove) { - toNuke.add(i); - } - } - } - } - return putAll(toNuke, null); - } - - /** - * Returns the keys that consist of multiple code points. - * - * @return - */ - public final Set stringKeys() { - return getNonRangeStrings(); - } - - /** - * Gets the inverse of this map, adding to the target. Like putAllIn - * - * @return - */ - public > U addInverseTo(U target) { - for (T value : values()) { - UnicodeSet uset = getSet(value); - target.put(value, uset); - } - return target; - } - - /** - * Freeze an inverse map. - * - * @param target - * @return - */ - public static Map freeze(Map target) { - for (UnicodeSet entry : target.values()) { - entry.freeze(); - } - return Collections.unmodifiableMap(target); - } - - /** - * @param source - * @return - */ - public UnicodeMap putAllInverse(Map source) { - for (Entry entry : source.entrySet()) { - putAll(entry.getValue(), entry.getKey()); - } - return this; - } -} - diff --git a/app/src/main/java/awais/instagrabber/viewmodels/DirectSettingsViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/DirectSettingsViewModel.java index 32883c92..e9d33085 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/DirectSettingsViewModel.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/DirectSettingsViewModel.java @@ -45,7 +45,6 @@ public class DirectSettingsViewModel extends AndroidViewModel { public DirectSettingsViewModel(final Application application, @NonNull final String threadId, - final DirectThread backup, final boolean pending, @NonNull final User currentUser) { super(application); @@ -59,7 +58,7 @@ public class DirectSettingsViewModel extends AndroidViewModel { final ContentResolver contentResolver = application.getContentResolver(); resources = getApplication().getResources(); final DirectMessagesManager messagesManager = DirectMessagesManager.getInstance(); - threadManager = messagesManager.getThreadManager(threadId, pending, backup, currentUser, contentResolver); + threadManager = messagesManager.getThreadManager(threadId, pending, currentUser, contentResolver); } @NonNull diff --git a/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java index 4c01c658..3e2a3603 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java @@ -23,6 +23,7 @@ import java.util.stream.Collectors; import awais.instagrabber.customviews.emoji.Emoji; import awais.instagrabber.managers.DirectMessagesManager; +import awais.instagrabber.managers.InboxManager; import awais.instagrabber.managers.ThreadManager; import awais.instagrabber.models.Resource; import awais.instagrabber.repositories.responses.User; @@ -57,7 +58,6 @@ public class DirectThreadViewModel extends AndroidViewModel { public DirectThreadViewModel(@NonNull final Application application, @NonNull final String threadId, - final DirectThread backup, final boolean pending, @NonNull final User currentUser) { super(application); @@ -74,7 +74,7 @@ public class DirectThreadViewModel extends AndroidViewModel { contentResolver = application.getContentResolver(); recordingsDir = DirectoryUtils.getOutputMediaDirectory(application, "Recordings"); final DirectMessagesManager messagesManager = DirectMessagesManager.getInstance(); - threadManager = messagesManager.getThreadManager(threadId, pending, backup, currentUser, contentResolver); + threadManager = messagesManager.getThreadManager(threadId, pending, currentUser, contentResolver); threadManager.fetchPendingRequests(); } @@ -278,16 +278,24 @@ public class DirectThreadViewModel extends AndroidViewModel { return threadManager.declineRequest(); } - public void markAsSeen() { - if (currentUser == null) return; + public LiveData> markAsSeen() { + if (currentUser == null) { + return getSuccessEventResObjectLiveData(); + } final DirectThread thread = getThread().getValue(); - if (thread == null) return; + if (thread == null) { + return getSuccessEventResObjectLiveData(); + } final List items = thread.getItems(); - if (items == null || items.isEmpty()) return; + if (items == null || items.isEmpty()) { + return getSuccessEventResObjectLiveData(); + } final Optional itemOptional = items.stream() .filter(item -> item.getUserId() != currentUser.getPk()) .findFirst(); - if (!itemOptional.isPresent()) return; + if (!itemOptional.isPresent()) { + return getSuccessEventResObjectLiveData(); + } final DirectItem directItem = itemOptional.get(); final Map lastSeenAt = thread.getLastSeenAt(); if (lastSeenAt != null) { @@ -296,10 +304,29 @@ public class DirectThreadViewModel extends AndroidViewModel { if (seenAt != null && (Objects.equals(seenAt.getItemId(), directItem.getItemId()) || Long.parseLong(seenAt.getTimestamp()) >= directItem.getTimestamp())) { - return; + return getSuccessEventResObjectLiveData(); } - } catch (Exception ignored) {} + } catch (Exception ignored) { + return getSuccessEventResObjectLiveData(); + } } - threadManager.markAsSeen(directItem); + return threadManager.markAsSeen(directItem); } + + @NonNull + private MutableLiveData> getSuccessEventResObjectLiveData() { + final MutableLiveData> data = new MutableLiveData<>(); + data.postValue(Resource.success(new Object())); + return data; + } + + public void deleteThreadIfRequired() { + final DirectThread thread = getThread().getValue(); + if (thread == null) return; + if (thread.isTemp() && (thread.getItems() == null || thread.getItems().isEmpty())) { + final InboxManager inboxManager = DirectMessagesManager.getInstance().getInboxManager(); + inboxManager.removeThread(threadId); + } + } + } diff --git a/app/src/main/java/awais/instagrabber/viewmodels/factories/DirectSettingsViewModelFactory.java b/app/src/main/java/awais/instagrabber/viewmodels/factories/DirectSettingsViewModelFactory.java index 61c11fc1..2d3503bf 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/factories/DirectSettingsViewModelFactory.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/factories/DirectSettingsViewModelFactory.java @@ -7,25 +7,21 @@ import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; import awais.instagrabber.repositories.responses.User; -import awais.instagrabber.repositories.responses.directmessages.DirectThread; import awais.instagrabber.viewmodels.DirectSettingsViewModel; public class DirectSettingsViewModelFactory implements ViewModelProvider.Factory { private final Application application; private final String threadId; - private final DirectThread backup; private final boolean pending; private final User currentUser; public DirectSettingsViewModelFactory(@NonNull final Application application, @NonNull final String threadId, - @NonNull final DirectThread backup, final boolean pending, @NonNull final User currentUser) { this.application = application; this.threadId = threadId; - this.backup = backup; this.pending = pending; this.currentUser = currentUser; } @@ -34,6 +30,6 @@ public class DirectSettingsViewModelFactory implements ViewModelProvider.Factory @Override public T create(@NonNull final Class modelClass) { //noinspection unchecked - return (T) new DirectSettingsViewModel(application, threadId, backup, pending, currentUser); + return (T) new DirectSettingsViewModel(application, threadId, pending, currentUser); } } diff --git a/app/src/main/java/awais/instagrabber/viewmodels/factories/DirectThreadViewModelFactory.java b/app/src/main/java/awais/instagrabber/viewmodels/factories/DirectThreadViewModelFactory.java index 2d309d07..586e1cc0 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/factories/DirectThreadViewModelFactory.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/factories/DirectThreadViewModelFactory.java @@ -7,25 +7,21 @@ import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; import awais.instagrabber.repositories.responses.User; -import awais.instagrabber.repositories.responses.directmessages.DirectThread; import awais.instagrabber.viewmodels.DirectThreadViewModel; public class DirectThreadViewModelFactory implements ViewModelProvider.Factory { private final Application application; private final String threadId; - private final DirectThread backup; private final boolean pending; private final User currentUser; public DirectThreadViewModelFactory(@NonNull final Application application, @NonNull final String threadId, - final DirectThread backup, final boolean pending, @NonNull final User currentUser) { this.application = application; this.threadId = threadId; - this.backup = backup; this.pending = pending; this.currentUser = currentUser; } @@ -34,6 +30,6 @@ public class DirectThreadViewModelFactory implements ViewModelProvider.Factory { @Override public T create(@NonNull final Class modelClass) { //noinspection unchecked - return (T) new DirectThreadViewModel(application, threadId, backup, pending, currentUser); + return (T) new DirectThreadViewModel(application, threadId, pending, currentUser); } } diff --git a/app/src/main/res/drawable/barinsta_logo.png b/app/src/main/res/drawable/barinsta_logo.png new file mode 100644 index 00000000..ade3e9ef Binary files /dev/null and b/app/src/main/res/drawable/barinsta_logo.png differ diff --git a/app/src/main/res/drawable/launch_screen.xml b/app/src/main/res/drawable/launch_screen.xml new file mode 100644 index 00000000..dbca5931 --- /dev/null +++ b/app/src/main/res/drawable/launch_screen.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_direct_messages_thread.xml b/app/src/main/res/layout/fragment_direct_messages_thread.xml index 56674d95..007005a1 100644 --- a/app/src/main/res/layout/fragment_direct_messages_thread.xml +++ b/app/src/main/res/layout/fragment_direct_messages_thread.xml @@ -257,7 +257,7 @@ android:layout_width="0dp" android:layout_height="250dp" android:translationY="250dp" - android:visibility="gone" + android:visibility="visible" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/app/src/main/res/menu/dm_thread_menu.xml b/app/src/main/res/menu/dm_thread_menu.xml index 34d72c66..25c19c93 100644 --- a/app/src/main/res/menu/dm_thread_menu.xml +++ b/app/src/main/res/menu/dm_thread_menu.xml @@ -8,7 +8,7 @@ app:showAsAction="ifRoom" /> - @@ -156,11 +152,6 @@ android:defaultValue="false" app:argType="boolean" /> - - Edit keyword filters Added keyword: {0} to filter list Removed keyword: {0} from filter list + Marked as seen diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index bf8fdcaf..b0147225 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -140,4 +140,8 @@ @color/deep_purple_400 @color/deep_purple_600 + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 5b775800..b2578fbd 100755 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:4.1.2' + classpath 'com.android.tools.build:gradle:4.1.3' def nav_version = "2.3.4" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" } diff --git a/renovate.json b/renovate.json new file mode 100644 index 00000000..267698f2 --- /dev/null +++ b/renovate.json @@ -0,0 +1,6 @@ +{ + "extends": [ + "config:base" + ], + "prConcurrentLimit": 5 +}