Browse Source
Merge branch 'master' into stamatiap/development
renovate/org.robolectric-robolectric-4.x
Merge branch 'master' into stamatiap/development
renovate/org.robolectric-robolectric-4.x
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
103 changed files with 2980 additions and 2410 deletions
-
10.github/workflows/github_nightly_release.yml
-
10.github/workflows/github_pre_release.yml
-
2.idea/compiler.xml
-
1.idea/gradle.xml
-
2.idea/misc.xml
-
1.idea/runConfigurations.xml
-
52app/build.gradle
-
31app/src/main/java/awais/instagrabber/activities/MainActivity.java
-
2app/src/main/java/awais/instagrabber/adapters/DirectItemsAdapter.java
-
16app/src/main/java/awais/instagrabber/adapters/SliderCallbackAdapter.java
-
22app/src/main/java/awais/instagrabber/adapters/SliderItemsAdapter.java
-
77app/src/main/java/awais/instagrabber/adapters/viewholder/SliderPhotoViewHolder.java
-
117app/src/main/java/awais/instagrabber/adapters/viewholder/SliderVideoViewHolder.java
-
8app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemAnimatedMediaViewHolder.java
-
8app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaShareViewHolder.java
-
9app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaViewHolder.java
-
8app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemRavenMediaViewHolder.java
-
9app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemStoryShareViewHolder.java
-
4app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemViewHolder.java
-
8app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemXmaViewHolder.java
-
4app/src/main/java/awais/instagrabber/adapters/viewholder/feed/FeedSliderViewHolder.java
-
17app/src/main/java/awais/instagrabber/customviews/ChatMessageLayout.java
-
165app/src/main/java/awais/instagrabber/customviews/FormattedNumberTextView.java
-
75app/src/main/java/awais/instagrabber/customviews/FragmentNavigatorWithDefaultAnimations.java
-
60app/src/main/java/awais/instagrabber/customviews/NavHostFragmentWithDefaultAnimations.java
-
23app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java
-
7app/src/main/java/awais/instagrabber/customviews/Tooltip.java
-
10app/src/main/java/awais/instagrabber/customviews/VideoPlayerCallbackAdapter.java
-
411app/src/main/java/awais/instagrabber/customviews/VideoPlayerViewHelper.java
-
2app/src/main/java/awais/instagrabber/customviews/drawee/ZoomableDraweeView.java
-
100app/src/main/java/awais/instagrabber/customviews/emoji/EmojiBottomSheetDialog.java
-
13app/src/main/java/awais/instagrabber/customviews/emoji/EmojiGridAdapter.java
-
163app/src/main/java/awais/instagrabber/customviews/emoji/EmojiPopupWindow.java
-
1app/src/main/java/awais/instagrabber/customviews/emoji/GoogleCompatEmojiDrawable.java
-
320app/src/main/java/awais/instagrabber/customviews/helpers/ChangeText.java
-
17app/src/main/java/awais/instagrabber/customviews/helpers/CustomHideBottomViewOnScrollBehavior.java
-
19app/src/main/java/awais/instagrabber/customviews/helpers/GridSpacingItemDecoration.java
-
9app/src/main/java/awais/instagrabber/dialogs/ProfilePicDialogFragment.java
-
54app/src/main/java/awais/instagrabber/fragments/CollectionPostsFragment.java
-
70app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java
-
54app/src/main/java/awais/instagrabber/fragments/LocationFragment.java
-
14app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java
-
1123app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java
-
19app/src/main/java/awais/instagrabber/fragments/SavedViewerFragment.java
-
32app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java
-
21app/src/main/java/awais/instagrabber/fragments/TopicPostsFragment.java
-
6app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java
-
67app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java
-
50app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java
-
58app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java
-
1app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.java
-
4app/src/main/java/awais/instagrabber/managers/ThreadManager.java
-
3app/src/main/java/awais/instagrabber/repositories/responses/User.java
-
10app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItemEmojiReaction.java
-
8app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItemReactions.java
-
15app/src/main/java/awais/instagrabber/utils/CombinedDrawable.java
-
32app/src/main/java/awais/instagrabber/utils/DMUtils.java
-
3app/src/main/java/awais/instagrabber/utils/NavigationExtensions.java
-
91app/src/main/java/awais/instagrabber/utils/NullSafePair.java
-
75app/src/main/java/awais/instagrabber/utils/NumberUtils.java
-
3app/src/main/java/awais/instagrabber/utils/SettingsHelper.java
-
51app/src/main/java/awais/instagrabber/utils/Utils.java
-
8app/src/main/java/awais/instagrabber/utils/emoji/EmojiParser.java
-
1app/src/main/java/awais/instagrabber/webservices/interceptors/IgErrorsInterceptor.java
-
11app/src/main/res/anim/slide_in_right.xml
-
9app/src/main/res/anim/slide_left.xml
-
11app/src/main/res/anim/slide_out_left.xml
-
9app/src/main/res/anim/slide_right.xml
-
6app/src/main/res/drawable/ic_bookmark.xml
-
10app/src/main/res/drawable/ic_round_bookmark_border_24.xml
-
10app/src/main/res/drawable/ic_round_edit_24.xml
-
2app/src/main/res/drawable/shape_oval_light.xml
-
391app/src/main/res/layout/dialog_post_view.xml
-
2app/src/main/res/layout/fragment_collection_posts.xml
-
3app/src/main/res/layout/fragment_discover.xml
-
45app/src/main/res/layout/fragment_feed.xml
-
23app/src/main/res/layout/fragment_hashtag.xml
-
23app/src/main/res/layout/fragment_location.xml
-
38app/src/main/res/layout/fragment_profile.xml
-
2app/src/main/res/layout/fragment_topic_posts.xml
-
1app/src/main/res/layout/item_highlight.xml
-
15app/src/main/res/layout/layout_dm_base.xml
-
2app/src/main/res/layout/layout_dm_text.xml
-
158app/src/main/res/layout/layout_exo_custom_controls.xml
-
3app/src/main/res/layout/layout_hashtag_details.xml
-
35app/src/main/res/layout/layout_location_details.xml
-
208app/src/main/res/layout/layout_post_view_bottom.xml
-
7app/src/main/res/layout/layout_profile_details.xml
-
24app/src/main/res/layout/layout_video_player_with_thumbnail.xml
-
8app/src/main/res/menu/collection_posts_menu.xml
-
18app/src/main/res/navigation/direct_messages_nav_graph.xml
-
17app/src/main/res/navigation/discover_nav_graph.xml
-
17app/src/main/res/navigation/feed_nav_graph.xml
-
17app/src/main/res/navigation/hashtag_nav_graph.xml
-
17app/src/main/res/navigation/location_nav_graph.xml
-
17app/src/main/res/navigation/notification_viewer_nav_graph.xml
-
21app/src/main/res/navigation/profile_nav_graph.xml
-
23app/src/main/res/navigation/saved_nav_graph.xml
-
5app/src/main/res/values/drawables.xml
-
2app/src/main/res/values/strings.xml
@ -23,10 +23,10 @@ jobs: |
|||
run: chmod +x gradlew |
|||
|
|||
- name: Build Github unsigned apk |
|||
run: ./gradlew assembleGithubRelease --stacktrace --project-prop pre |
|||
run: ./gradlew assembleGithubRelease --stacktrace --project-prop pre --project-prop split |
|||
|
|||
- name: Sign APK |
|||
uses: r0adkll/sign-android-release@v1 |
|||
uses: ammargitham/[email protected].1 |
|||
# ID used to access action output |
|||
id: sign_app |
|||
with: |
|||
@ -45,7 +45,8 @@ jobs: |
|||
uses: actions/upload-artifact@v2 |
|||
with: |
|||
name: barinsta_nightly_${{ steps.date.outputs.date }} |
|||
path: ${{steps.sign_app.outputs.signedReleaseFile}} |
|||
# path: ${{steps.sign_app.outputs.signedReleaseFile}} |
|||
path: app/build/outputs/apk/github/release/*-signed.apk |
|||
|
|||
# Send success notification |
|||
- name: Send success Telegram notification |
|||
@ -55,7 +56,8 @@ jobs: |
|||
to: ${{ secrets.TELEGRAM_BUILDS_CHANNEL_TO }} |
|||
token: ${{ secrets.TELEGRAM_BUILDS_BOT_TOKEN }} |
|||
message: "${{ github.workflow }} ${{ github.job }} #${{ github.run_number }} completed successfully.\nhttps://github.com/${{github.repository}}/actions/runs/${{github.run_id}}" |
|||
document: ${{steps.sign_app.outputs.signedReleaseFile}} |
|||
# document: ${{steps.sign_app.outputs.signedReleaseFile}} |
|||
document: app/build/outputs/apk/github/release/*-signed.apk |
|||
|
|||
# Send failure notification |
|||
- name: Send failure Telegram notification |
|||
|
@ -24,10 +24,10 @@ jobs: |
|||
run: chmod +x gradlew |
|||
|
|||
- name: Build Github unsigned pre-release apk |
|||
run: ./gradlew assembleGithubRelease --stacktrace --project-prop pre |
|||
run: ./gradlew assembleGithubRelease --stacktrace --project-prop pre --project-prop split |
|||
|
|||
- name: Sign APK |
|||
uses: r0adkll/sign-android-release@v1 |
|||
uses: ammargitham/[email protected].1 |
|||
# ID used to access action output |
|||
id: sign_app |
|||
with: |
|||
@ -46,7 +46,8 @@ jobs: |
|||
uses: actions/upload-artifact@v2 |
|||
with: |
|||
name: barinsta_pre-release_${{ steps.date.outputs.date }} |
|||
path: ${{steps.sign_app.outputs.signedReleaseFile}} |
|||
# path: ${{steps.sign_app.outputs.signedReleaseFile}} |
|||
path: app/build/outputs/apk/github/release/*-signed.apk |
|||
|
|||
# Send success notification |
|||
- name: Send success Telegram notification |
|||
@ -56,7 +57,8 @@ jobs: |
|||
to: ${{ secrets.TELEGRAM_BUILDS_CHANNEL_TO }} |
|||
token: ${{ secrets.TELEGRAM_BUILDS_BOT_TOKEN }} |
|||
message: "${{ github.workflow }} ${{ github.job }} #${{ github.run_number }} completed successfully.\nURL: https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}" |
|||
document: ${{steps.sign_app.outputs.signedReleaseFile}} |
|||
# document: ${{steps.sign_app.outputs.signedReleaseFile}} |
|||
document: app/build/outputs/apk/github/release/*-signed.apk |
|||
|
|||
# Send failure notification |
|||
- name: Send failure Telegram notification |
|||
|
@ -1,6 +1,6 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<project version="4"> |
|||
<component name="CompilerConfiguration"> |
|||
<bytecodeTargetLevel target="1.8" /> |
|||
<bytecodeTargetLevel target="11" /> |
|||
</component> |
|||
</project> |
@ -0,0 +1,165 @@ |
|||
package awais.instagrabber.customviews; |
|||
|
|||
import android.content.Context; |
|||
import android.util.AttributeSet; |
|||
import android.util.Log; |
|||
import android.view.ViewGroup; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.appcompat.widget.AppCompatTextView; |
|||
import androidx.transition.ChangeBounds; |
|||
import androidx.transition.Transition; |
|||
import androidx.transition.TransitionManager; |
|||
import androidx.transition.TransitionSet; |
|||
|
|||
import java.time.Duration; |
|||
|
|||
import awais.instagrabber.customviews.helpers.ChangeText; |
|||
import awais.instagrabber.utils.NumberUtils; |
|||
|
|||
public class FormattedNumberTextView extends AppCompatTextView { |
|||
private static final String TAG = FormattedNumberTextView.class.getSimpleName(); |
|||
private static final Transition TRANSITION; |
|||
|
|||
private long number = Long.MIN_VALUE; |
|||
private boolean showAbbreviation = true; |
|||
private boolean animateChanges = false; |
|||
private boolean toggleOnClick = true; |
|||
private boolean autoToggleToAbbreviation = true; |
|||
private long autoToggleTimeoutMs = Duration.ofSeconds(2).toMillis(); |
|||
private boolean initDone = false; |
|||
|
|||
static { |
|||
final TransitionSet transitionSet = new TransitionSet(); |
|||
final ChangeText changeText = new ChangeText().setChangeBehavior(ChangeText.CHANGE_BEHAVIOR_OUT_IN); |
|||
transitionSet.addTransition(changeText).addTransition(new ChangeBounds()); |
|||
TRANSITION = transitionSet; |
|||
} |
|||
|
|||
|
|||
public FormattedNumberTextView(@NonNull final Context context) { |
|||
super(context); |
|||
init(); |
|||
} |
|||
|
|||
public FormattedNumberTextView(@NonNull final Context context, @Nullable final AttributeSet attrs) { |
|||
super(context, attrs); |
|||
init(); |
|||
} |
|||
|
|||
public FormattedNumberTextView(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) { |
|||
super(context, attrs, defStyleAttr); |
|||
init(); |
|||
} |
|||
|
|||
private void init() { |
|||
if (initDone) return; |
|||
setupClickToggle(); |
|||
initDone = true; |
|||
} |
|||
|
|||
private void setupClickToggle() { |
|||
setOnClickListener(null); |
|||
} |
|||
|
|||
private OnClickListener getWrappedClickListener(@Nullable final OnClickListener l) { |
|||
if (!toggleOnClick) { |
|||
return l; |
|||
} |
|||
return v -> { |
|||
toggleAbbreviation(); |
|||
if (l != null) { |
|||
l.onClick(this); |
|||
} |
|||
}; |
|||
} |
|||
|
|||
public void setNumber(final long number) { |
|||
if (this.number == number) return; |
|||
this.number = number; |
|||
format(); |
|||
} |
|||
|
|||
public void clearNumber() { |
|||
if (number == Long.MIN_VALUE) return; |
|||
number = Long.MIN_VALUE; |
|||
format(); |
|||
} |
|||
|
|||
public void setShowAbbreviation(final boolean showAbbreviation) { |
|||
if (this.showAbbreviation && showAbbreviation) return; |
|||
this.showAbbreviation = showAbbreviation; |
|||
format(); |
|||
} |
|||
|
|||
public boolean isShowAbbreviation() { |
|||
return showAbbreviation; |
|||
} |
|||
|
|||
private void toggleAbbreviation() { |
|||
if (number == Long.MIN_VALUE) return; |
|||
setShowAbbreviation(!showAbbreviation); |
|||
} |
|||
|
|||
public void setToggleOnClick(final boolean toggleOnClick) { |
|||
this.toggleOnClick = toggleOnClick; |
|||
} |
|||
|
|||
public boolean isToggleOnClick() { |
|||
return toggleOnClick; |
|||
} |
|||
|
|||
public void setAutoToggleToAbbreviation(final boolean autoToggleToAbbreviation) { |
|||
this.autoToggleToAbbreviation = autoToggleToAbbreviation; |
|||
} |
|||
|
|||
public boolean isAutoToggleToAbbreviation() { |
|||
return autoToggleToAbbreviation; |
|||
} |
|||
|
|||
public void setAutoToggleTimeoutMs(final long autoToggleTimeoutMs) { |
|||
this.autoToggleTimeoutMs = autoToggleTimeoutMs; |
|||
} |
|||
|
|||
public long getAutoToggleTimeoutMs() { |
|||
return autoToggleTimeoutMs; |
|||
} |
|||
|
|||
public void setAnimateChanges(final boolean animateChanges) { |
|||
this.animateChanges = animateChanges; |
|||
} |
|||
|
|||
public boolean isAnimateChanges() { |
|||
return animateChanges; |
|||
} |
|||
|
|||
@Override |
|||
public void setOnClickListener(@Nullable final OnClickListener l) { |
|||
super.setOnClickListener(getWrappedClickListener(l)); |
|||
} |
|||
|
|||
private void format() { |
|||
post(() -> { |
|||
if (animateChanges) { |
|||
try { |
|||
TransitionManager.beginDelayedTransition((ViewGroup) getParent(), TRANSITION); |
|||
} catch (Exception e) { |
|||
Log.e(TAG, "format: ", e); |
|||
} |
|||
} |
|||
if (number == Long.MIN_VALUE) { |
|||
setText(null); |
|||
return; |
|||
} |
|||
if (showAbbreviation) { |
|||
setText(NumberUtils.abbreviate(number)); |
|||
return; |
|||
} |
|||
setText(String.valueOf(number)); |
|||
if (autoToggleToAbbreviation) { |
|||
getHandler().postDelayed(() -> setShowAbbreviation(true), autoToggleTimeoutMs); |
|||
} |
|||
}); |
|||
} |
|||
} |
@ -0,0 +1,75 @@ |
|||
package awais.instagrabber.customviews; |
|||
|
|||
import android.content.Context; |
|||
import android.os.Bundle; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.fragment.app.FragmentManager; |
|||
import androidx.navigation.NavDestination; |
|||
import androidx.navigation.NavOptions; |
|||
import androidx.navigation.Navigator; |
|||
import androidx.navigation.fragment.FragmentNavigator; |
|||
|
|||
import awais.instagrabber.R; |
|||
|
|||
@Navigator.Name("fragment") |
|||
public class FragmentNavigatorWithDefaultAnimations extends FragmentNavigator { |
|||
|
|||
private final NavOptions emptyNavOptions = new NavOptions.Builder().build(); |
|||
// private final NavOptions defaultNavOptions = new NavOptions.Builder() |
|||
// .setEnterAnim(R.animator.nav_default_enter_anim) |
|||
// .setExitAnim(R.animator.nav_default_exit_anim) |
|||
// .setPopEnterAnim(R.animator.nav_default_pop_enter_anim) |
|||
// .setPopExitAnim(R.animator.nav_default_pop_exit_anim) |
|||
// .build(); |
|||
|
|||
private final NavOptions defaultNavOptions = new NavOptions.Builder() |
|||
.setEnterAnim(R.anim.slide_in_right) |
|||
.setExitAnim(R.anim.slide_out_left) |
|||
.setPopEnterAnim(android.R.anim.slide_in_left) |
|||
.setPopExitAnim(android.R.anim.slide_out_right) |
|||
.build(); |
|||
|
|||
public FragmentNavigatorWithDefaultAnimations(@NonNull final Context context, |
|||
@NonNull final FragmentManager manager, |
|||
final int containerId) { |
|||
super(context, manager, containerId); |
|||
} |
|||
|
|||
@Nullable |
|||
@Override |
|||
public NavDestination navigate(@NonNull final Destination destination, |
|||
@Nullable final Bundle args, |
|||
@Nullable final NavOptions navOptions, |
|||
@Nullable final Navigator.Extras navigatorExtras) { |
|||
// this will try to fill in empty animations with defaults when no shared element transitions are set |
|||
// https://developer.android.com/guide/navigation/navigation-animate-transitions#shared-element |
|||
final boolean shouldUseTransitionsInstead = navigatorExtras != null; |
|||
final NavOptions navOptions1 = shouldUseTransitionsInstead ? navOptions : fillEmptyAnimationsWithDefaults(navOptions); |
|||
return super.navigate(destination, args, navOptions1, navigatorExtras); |
|||
} |
|||
|
|||
private NavOptions fillEmptyAnimationsWithDefaults(@Nullable final NavOptions navOptions) { |
|||
if (navOptions == null) { |
|||
return defaultNavOptions; |
|||
} |
|||
return copyNavOptionsWithDefaultAnimations(navOptions); |
|||
} |
|||
|
|||
@NonNull |
|||
private NavOptions copyNavOptionsWithDefaultAnimations(@NonNull final NavOptions navOptions) { |
|||
return new NavOptions.Builder() |
|||
.setLaunchSingleTop(navOptions.shouldLaunchSingleTop()) |
|||
.setPopUpTo(navOptions.getPopUpTo(), navOptions.isPopUpToInclusive()) |
|||
.setEnterAnim(navOptions.getEnterAnim() == emptyNavOptions.getEnterAnim() |
|||
? defaultNavOptions.getEnterAnim() : navOptions.getEnterAnim()) |
|||
.setExitAnim(navOptions.getExitAnim() == emptyNavOptions.getExitAnim() |
|||
? defaultNavOptions.getExitAnim() : navOptions.getExitAnim()) |
|||
.setPopEnterAnim(navOptions.getPopEnterAnim() == emptyNavOptions.getPopEnterAnim() |
|||
? defaultNavOptions.getPopEnterAnim() : navOptions.getPopEnterAnim()) |
|||
.setPopExitAnim(navOptions.getPopExitAnim() == emptyNavOptions.getPopExitAnim() |
|||
? defaultNavOptions.getPopExitAnim() : navOptions.getPopExitAnim()) |
|||
.build(); |
|||
} |
|||
} |
@ -0,0 +1,60 @@ |
|||
package awais.instagrabber.customviews; |
|||
|
|||
import android.os.Bundle; |
|||
|
|||
import androidx.annotation.NavigationRes; |
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.navigation.NavController; |
|||
import androidx.navigation.Navigator; |
|||
import androidx.navigation.fragment.FragmentNavigator; |
|||
import androidx.navigation.fragment.NavHostFragment; |
|||
|
|||
public class NavHostFragmentWithDefaultAnimations extends NavHostFragment { |
|||
private static final String KEY_GRAPH_ID = "android-support-nav:fragment:graphId"; |
|||
private static final String KEY_START_DESTINATION_ARGS = |
|||
"android-support-nav:fragment:startDestinationArgs"; |
|||
private static final String KEY_NAV_CONTROLLER_STATE = |
|||
"android-support-nav:fragment:navControllerState"; |
|||
private static final String KEY_DEFAULT_NAV_HOST = "android-support-nav:fragment:defaultHost"; |
|||
|
|||
@NonNull |
|||
public static NavHostFragment create(@NavigationRes int graphResId) { |
|||
return create(graphResId, null); |
|||
} |
|||
|
|||
@NonNull |
|||
public static NavHostFragment create(@NavigationRes int graphResId, |
|||
@Nullable Bundle startDestinationArgs) { |
|||
Bundle b = null; |
|||
if (graphResId != 0) { |
|||
b = new Bundle(); |
|||
b.putInt(KEY_GRAPH_ID, graphResId); |
|||
} |
|||
if (startDestinationArgs != null) { |
|||
if (b == null) { |
|||
b = new Bundle(); |
|||
} |
|||
b.putBundle(KEY_START_DESTINATION_ARGS, startDestinationArgs); |
|||
} |
|||
|
|||
final NavHostFragmentWithDefaultAnimations result = new NavHostFragmentWithDefaultAnimations(); |
|||
if (b != null) { |
|||
result.setArguments(b); |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
protected Navigator<? extends FragmentNavigator.Destination> createFragmentNavigator() { |
|||
return new FragmentNavigatorWithDefaultAnimations(requireContext(), getChildFragmentManager(), getId()); |
|||
} |
|||
|
|||
@Override |
|||
protected void onCreateNavController(@NonNull final NavController navController) { |
|||
super.onCreateNavController(navController); |
|||
navController.getNavigatorProvider() |
|||
.addNavigator(new FragmentNavigatorWithDefaultAnimations(requireContext(), getChildFragmentManager(), getId())); |
|||
} |
|||
} |
@ -0,0 +1,100 @@ |
|||
package awais.instagrabber.customviews.emoji; |
|||
|
|||
import android.app.Dialog; |
|||
import android.content.Context; |
|||
import android.os.Bundle; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.fragment.app.DialogFragment; |
|||
import androidx.fragment.app.Fragment; |
|||
import androidx.recyclerview.widget.GridLayoutManager; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import com.google.android.material.bottomsheet.BottomSheetDialog; |
|||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
public class EmojiBottomSheetDialog extends BottomSheetDialogFragment { |
|||
public static final String TAG = EmojiBottomSheetDialog.class.getSimpleName(); |
|||
|
|||
private RecyclerView grid; |
|||
private EmojiPicker.OnEmojiClickListener callback; |
|||
|
|||
@NonNull |
|||
public static EmojiBottomSheetDialog newInstance() { |
|||
// Bundle args = new Bundle(); |
|||
// fragment.setArguments(args); |
|||
return new EmojiBottomSheetDialog(); |
|||
} |
|||
|
|||
@Override |
|||
public void onCreate(@Nullable final Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
setStyle(DialogFragment.STYLE_NORMAL, R.style.ThemeOverlay_Rounded_BottomSheetDialog); |
|||
} |
|||
|
|||
@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; |
|||
grid = new RecyclerView(context); |
|||
return grid; |
|||
} |
|||
|
|||
@Override |
|||
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { |
|||
init(); |
|||
} |
|||
|
|||
@Override |
|||
public void onStart() { |
|||
super.onStart(); |
|||
final Dialog dialog = getDialog(); |
|||
if (dialog == null) return; |
|||
final BottomSheetDialog bottomSheetDialog = (BottomSheetDialog) dialog; |
|||
final View bottomSheetInternal = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet); |
|||
if (bottomSheetInternal == null) return; |
|||
bottomSheetInternal.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT; |
|||
bottomSheetInternal.requestLayout(); |
|||
} |
|||
|
|||
@Override |
|||
public void onAttach(@NonNull final Context context) { |
|||
super.onAttach(context); |
|||
final Fragment parentFragment = getParentFragment(); |
|||
if (parentFragment instanceof EmojiPicker.OnEmojiClickListener) { |
|||
callback = (EmojiPicker.OnEmojiClickListener) parentFragment; |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onDestroyView() { |
|||
grid = null; |
|||
super.onDestroyView(); |
|||
} |
|||
|
|||
private void init() { |
|||
final Context context = getContext(); |
|||
if (context == null) return; |
|||
final GridLayoutManager gridLayoutManager = new GridLayoutManager(context, 9); |
|||
grid.setLayoutManager(gridLayoutManager); |
|||
grid.setHasFixedSize(true); |
|||
grid.setClipToPadding(false); |
|||
grid.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(8))); |
|||
final EmojiGridAdapter adapter = new EmojiGridAdapter(null, (view, emoji) -> { |
|||
if (callback != null) { |
|||
callback.onClick(view, emoji); |
|||
} |
|||
dismiss(); |
|||
}, null); |
|||
grid.setAdapter(adapter); |
|||
} |
|||
} |
@ -1,163 +0,0 @@ |
|||
package awais.instagrabber.customviews.emoji; |
|||
|
|||
import android.content.Context; |
|||
import android.graphics.Rect; |
|||
import android.view.Gravity; |
|||
import android.view.View; |
|||
import android.view.WindowManager.LayoutParams; |
|||
import android.widget.PopupWindow; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.customviews.emoji.EmojiPicker.OnBackspaceClickListener; |
|||
import awais.instagrabber.customviews.emoji.EmojiPicker.OnEmojiClickListener; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; |
|||
|
|||
/** |
|||
* https://stackoverflow.com/a/33897583/1436766 |
|||
*/ |
|||
public class EmojiPopupWindow extends PopupWindow { |
|||
|
|||
private int keyBoardHeight = 0; |
|||
private Boolean pendingOpen = false; |
|||
private Boolean isOpened = false; |
|||
private final View rootView; |
|||
private final Context context; |
|||
private final OnEmojiClickListener onEmojiClickListener; |
|||
private final OnBackspaceClickListener onBackspaceClickListener; |
|||
|
|||
private OnSoftKeyboardOpenCloseListener onSoftKeyboardOpenCloseListener; |
|||
|
|||
|
|||
/** |
|||
* Constructor |
|||
* |
|||
* @param rootView The top most layout in your view hierarchy. The difference of this view and the screen height will be used to calculate the keyboard height. |
|||
*/ |
|||
public EmojiPopupWindow(final View rootView, |
|||
final OnEmojiClickListener onEmojiClickListener, |
|||
final OnBackspaceClickListener onBackspaceClickListener) { |
|||
super(rootView.getContext()); |
|||
this.rootView = rootView; |
|||
this.context = rootView.getContext(); |
|||
this.onEmojiClickListener = onEmojiClickListener; |
|||
this.onBackspaceClickListener = onBackspaceClickListener; |
|||
View customView = createCustomView(); |
|||
setContentView(customView); |
|||
setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); |
|||
//default size |
|||
setSize((int) context.getResources().getDimension(R.dimen.keyboard_height), MATCH_PARENT); |
|||
} |
|||
|
|||
/** |
|||
* Set the listener for the event of keyboard opening or closing. |
|||
*/ |
|||
public void setOnSoftKeyboardOpenCloseListener(OnSoftKeyboardOpenCloseListener listener) { |
|||
this.onSoftKeyboardOpenCloseListener = listener; |
|||
} |
|||
|
|||
/** |
|||
* Use this function to show the emoji popup. |
|||
* NOTE: Since, the soft keyboard sizes are variable on different android devices, the |
|||
* library needs you to open the soft keyboard atleast once before calling this function. |
|||
* If that is not possible see showAtBottomPending() function. |
|||
*/ |
|||
public void showAtBottom() { |
|||
showAtLocation(rootView, Gravity.BOTTOM, 0, 0); |
|||
} |
|||
|
|||
/** |
|||
* Use this function when the soft keyboard has not been opened yet. This |
|||
* will show the emoji popup after the keyboard is up next time. |
|||
* Generally, you will be calling InputMethodManager.showSoftInput function after |
|||
* calling this function. |
|||
*/ |
|||
public void showAtBottomPending() { |
|||
if (isKeyBoardOpen()) |
|||
showAtBottom(); |
|||
else |
|||
pendingOpen = true; |
|||
} |
|||
|
|||
/** |
|||
* @return Returns true if the soft keyboard is open, false otherwise. |
|||
*/ |
|||
public Boolean isKeyBoardOpen() { |
|||
return isOpened; |
|||
} |
|||
|
|||
/** |
|||
* Dismiss the popup |
|||
*/ |
|||
@Override |
|||
public void dismiss() { |
|||
super.dismiss(); |
|||
} |
|||
|
|||
/** |
|||
* Call this function to resize the emoji popup according to your soft keyboard size |
|||
*/ |
|||
public void setSizeForSoftKeyboard() { |
|||
rootView.getViewTreeObserver().addOnGlobalLayoutListener(() -> { |
|||
Rect r = new Rect(); |
|||
rootView.getWindowVisibleDisplayFrame(r); |
|||
|
|||
int screenHeight = getUsableScreenHeight(); |
|||
int heightDifference = screenHeight - (r.bottom - r.top); |
|||
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); |
|||
if (resourceId > 0) { |
|||
heightDifference -= context.getResources() |
|||
.getDimensionPixelSize(resourceId); |
|||
} |
|||
if (heightDifference > 100) { |
|||
keyBoardHeight = heightDifference; |
|||
setSize(MATCH_PARENT, keyBoardHeight); |
|||
if (!isOpened) { |
|||
if (onSoftKeyboardOpenCloseListener != null) |
|||
onSoftKeyboardOpenCloseListener.onKeyboardOpen(keyBoardHeight); |
|||
} |
|||
isOpened = true; |
|||
if (pendingOpen) { |
|||
showAtBottom(); |
|||
pendingOpen = false; |
|||
} |
|||
} else { |
|||
isOpened = false; |
|||
if (onSoftKeyboardOpenCloseListener != null) |
|||
onSoftKeyboardOpenCloseListener.onKeyboardClose(); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private int getUsableScreenHeight() { |
|||
return Utils.displayMetrics.heightPixels; |
|||
} |
|||
|
|||
/** |
|||
* Manually set the popup window size |
|||
* |
|||
* @param width Width of the popup |
|||
* @param height Height of the popup |
|||
*/ |
|||
public void setSize(int width, int height) { |
|||
setWidth(width); |
|||
setHeight(height); |
|||
} |
|||
|
|||
private View createCustomView() { |
|||
final EmojiPicker emojiPicker = new EmojiPicker(context); |
|||
final LayoutParams layoutParams = new LayoutParams(MATCH_PARENT, MATCH_PARENT); |
|||
emojiPicker.setLayoutParams(layoutParams); |
|||
emojiPicker.init(rootView, onEmojiClickListener, onBackspaceClickListener); |
|||
return emojiPicker; |
|||
} |
|||
|
|||
|
|||
public interface OnSoftKeyboardOpenCloseListener { |
|||
void onKeyboardOpen(int keyBoardHeight); |
|||
|
|||
void onKeyboardClose(); |
|||
} |
|||
} |
|||
|
@ -0,0 +1,320 @@ |
|||
package awais.instagrabber.customviews.helpers; |
|||
|
|||
/* |
|||
* Copyright (C) 2013 The Android Open Source Project |
|||
* |
|||
* 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. |
|||
*/ |
|||
|
|||
import android.animation.Animator; |
|||
import android.animation.AnimatorListenerAdapter; |
|||
import android.animation.AnimatorSet; |
|||
import android.animation.ValueAnimator; |
|||
import android.graphics.Color; |
|||
import android.util.Log; |
|||
import android.view.ViewGroup; |
|||
import android.widget.EditText; |
|||
import android.widget.TextView; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.transition.Transition; |
|||
import androidx.transition.TransitionListenerAdapter; |
|||
import androidx.transition.TransitionValues; |
|||
|
|||
import java.util.Map; |
|||
import java.util.Objects; |
|||
|
|||
import awais.instagrabber.BuildConfig; |
|||
|
|||
/** |
|||
* This transition tracks changes to the text in TextView targets. If the text |
|||
* changes between the start and end scenes, the transition ensures that the |
|||
* starting text stays until the transition ends, at which point it changes |
|||
* to the end text. This is useful in situations where you want to resize a |
|||
* text view to its new size before displaying the text that goes there. |
|||
*/ |
|||
public class ChangeText extends Transition { |
|||
private static final String LOG_TAG = "TextChange"; |
|||
private static final String PROPNAME_TEXT = "android:textchange:text"; |
|||
private static final String PROPNAME_TEXT_SELECTION_START = |
|||
"android:textchange:textSelectionStart"; |
|||
private static final String PROPNAME_TEXT_SELECTION_END = |
|||
"android:textchange:textSelectionEnd"; |
|||
private static final String PROPNAME_TEXT_COLOR = "android:textchange:textColor"; |
|||
private int mChangeBehavior = CHANGE_BEHAVIOR_KEEP; |
|||
private boolean crossFade; |
|||
/** |
|||
* Flag specifying that the text in affected/changing TextView targets will keep |
|||
* their original text during the transition, setting it to the final text when |
|||
* the transition ends. This is the default behavior. |
|||
* |
|||
* @see #setChangeBehavior(int) |
|||
*/ |
|||
public static final int CHANGE_BEHAVIOR_KEEP = 0; |
|||
/** |
|||
* Flag specifying that the text changing animation should first fade |
|||
* out the original text completely. The new text is set on the target |
|||
* view at the end of the fade-out animation. This transition is typically |
|||
* used with a later {@link #CHANGE_BEHAVIOR_IN} transition, allowing more |
|||
* flexibility than the {@link #CHANGE_BEHAVIOR_OUT_IN} by allowing other |
|||
* transitions to be run sequentially or in parallel with these fades. |
|||
* |
|||
* @see #setChangeBehavior(int) |
|||
*/ |
|||
public static final int CHANGE_BEHAVIOR_OUT = 1; |
|||
/** |
|||
* Flag specifying that the text changing animation should fade in the |
|||
* end text into the affected target view(s). This transition is typically |
|||
* used in conjunction with an earlier {@link #CHANGE_BEHAVIOR_OUT} |
|||
* transition, possibly with other transitions running as well, such as |
|||
* a sequence to fade out, then resize the view, then fade in. |
|||
* |
|||
* @see #setChangeBehavior(int) |
|||
*/ |
|||
public static final int CHANGE_BEHAVIOR_IN = 2; |
|||
/** |
|||
* Flag specifying that the text changing animation should first fade |
|||
* out the original text completely and then fade in the |
|||
* new text. |
|||
* |
|||
* @see #setChangeBehavior(int) |
|||
*/ |
|||
public static final int CHANGE_BEHAVIOR_OUT_IN = 3; |
|||
private static final String[] sTransitionProperties = { |
|||
PROPNAME_TEXT, |
|||
PROPNAME_TEXT_SELECTION_START, |
|||
PROPNAME_TEXT_SELECTION_END |
|||
}; |
|||
|
|||
/** |
|||
* Sets the type of changing animation that will be run, one of |
|||
* {@link #CHANGE_BEHAVIOR_KEEP}, {@link #CHANGE_BEHAVIOR_OUT}, |
|||
* {@link #CHANGE_BEHAVIOR_IN}, and {@link #CHANGE_BEHAVIOR_OUT_IN}. |
|||
* |
|||
* @param changeBehavior The type of fading animation to use when this |
|||
* transition is run. |
|||
* @return this textChange object. |
|||
*/ |
|||
public ChangeText setChangeBehavior(int changeBehavior) { |
|||
if (changeBehavior >= CHANGE_BEHAVIOR_KEEP && changeBehavior <= CHANGE_BEHAVIOR_OUT_IN) { |
|||
mChangeBehavior = changeBehavior; |
|||
} |
|||
return this; |
|||
} |
|||
|
|||
public ChangeText setCrossFade(final boolean crossFade) { |
|||
this.crossFade = crossFade; |
|||
return this; |
|||
} |
|||
|
|||
@Override |
|||
public String[] getTransitionProperties() { |
|||
return sTransitionProperties; |
|||
} |
|||
|
|||
/** |
|||
* Returns the type of changing animation that will be run. |
|||
* |
|||
* @return either {@link #CHANGE_BEHAVIOR_KEEP}, {@link #CHANGE_BEHAVIOR_OUT}, |
|||
* {@link #CHANGE_BEHAVIOR_IN}, or {@link #CHANGE_BEHAVIOR_OUT_IN}. |
|||
*/ |
|||
public int getChangeBehavior() { |
|||
return mChangeBehavior; |
|||
} |
|||
|
|||
private void captureValues(TransitionValues transitionValues) { |
|||
if (transitionValues.view instanceof TextView) { |
|||
TextView textview = (TextView) transitionValues.view; |
|||
transitionValues.values.put(PROPNAME_TEXT, textview.getText()); |
|||
if (textview instanceof EditText) { |
|||
transitionValues.values.put(PROPNAME_TEXT_SELECTION_START, |
|||
textview.getSelectionStart()); |
|||
transitionValues.values.put(PROPNAME_TEXT_SELECTION_END, |
|||
textview.getSelectionEnd()); |
|||
} |
|||
if (mChangeBehavior > CHANGE_BEHAVIOR_KEEP) { |
|||
transitionValues.values.put(PROPNAME_TEXT_COLOR, textview.getCurrentTextColor()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void captureStartValues(@NonNull TransitionValues transitionValues) { |
|||
captureValues(transitionValues); |
|||
} |
|||
|
|||
@Override |
|||
public void captureEndValues(@NonNull TransitionValues transitionValues) { |
|||
captureValues(transitionValues); |
|||
} |
|||
|
|||
@Override |
|||
public Animator createAnimator(@NonNull ViewGroup sceneRoot, TransitionValues startValues, |
|||
TransitionValues endValues) { |
|||
if (startValues == null || endValues == null || |
|||
!(startValues.view instanceof TextView) || !(endValues.view instanceof TextView)) { |
|||
return null; |
|||
} |
|||
final TextView view = (TextView) endValues.view; |
|||
Map<String, Object> startVals = startValues.values; |
|||
Map<String, Object> endVals = endValues.values; |
|||
final CharSequence startText = startVals.get(PROPNAME_TEXT) != null ? |
|||
(CharSequence) startVals.get(PROPNAME_TEXT) : ""; |
|||
final CharSequence endText = endVals.get(PROPNAME_TEXT) != null ? |
|||
(CharSequence) endVals.get(PROPNAME_TEXT) : ""; |
|||
final int startSelectionStart, startSelectionEnd, endSelectionStart, endSelectionEnd; |
|||
if (view instanceof EditText) { |
|||
startSelectionStart = startVals.get(PROPNAME_TEXT_SELECTION_START) != null ? |
|||
(Integer) startVals.get(PROPNAME_TEXT_SELECTION_START) : -1; |
|||
startSelectionEnd = startVals.get(PROPNAME_TEXT_SELECTION_END) != null ? |
|||
(Integer) startVals.get(PROPNAME_TEXT_SELECTION_END) : startSelectionStart; |
|||
endSelectionStart = endVals.get(PROPNAME_TEXT_SELECTION_START) != null ? |
|||
(Integer) endVals.get(PROPNAME_TEXT_SELECTION_START) : -1; |
|||
endSelectionEnd = endVals.get(PROPNAME_TEXT_SELECTION_END) != null ? |
|||
(Integer) endVals.get(PROPNAME_TEXT_SELECTION_END) : endSelectionStart; |
|||
} else { |
|||
startSelectionStart = startSelectionEnd = endSelectionStart = endSelectionEnd = -1; |
|||
} |
|||
if (!Objects.equals(startText, endText)) { |
|||
final int startColor; |
|||
final int endColor; |
|||
if (mChangeBehavior != CHANGE_BEHAVIOR_IN) { |
|||
view.setText(startText); |
|||
if (view instanceof EditText) { |
|||
setSelection(((EditText) view), startSelectionStart, startSelectionEnd); |
|||
} |
|||
} |
|||
Animator anim; |
|||
if (mChangeBehavior == CHANGE_BEHAVIOR_KEEP) { |
|||
startColor = endColor = 0; |
|||
anim = ValueAnimator.ofFloat(0, 1); |
|||
anim.addListener(new AnimatorListenerAdapter() { |
|||
@Override |
|||
public void onAnimationEnd(Animator animation) { |
|||
if (Objects.equals(startText, view.getText())) { |
|||
// Only set if it hasn't been changed since anim started |
|||
view.setText(endText); |
|||
if (view instanceof EditText) { |
|||
setSelection(((EditText) view), endSelectionStart, endSelectionEnd); |
|||
} |
|||
} |
|||
} |
|||
}); |
|||
} else { |
|||
startColor = (Integer) startVals.get(PROPNAME_TEXT_COLOR); |
|||
endColor = (Integer) endVals.get(PROPNAME_TEXT_COLOR); |
|||
// Fade out start text |
|||
ValueAnimator outAnim = null, inAnim = null; |
|||
if (mChangeBehavior == CHANGE_BEHAVIOR_OUT_IN || |
|||
mChangeBehavior == CHANGE_BEHAVIOR_OUT) { |
|||
outAnim = ValueAnimator.ofInt(Color.alpha(startColor), 0); |
|||
outAnim.addUpdateListener(animation -> { |
|||
int currAlpha = (Integer) animation.getAnimatedValue(); |
|||
view.setTextColor(currAlpha << 24 | startColor & 0xffffff); |
|||
}); |
|||
outAnim.addListener(new AnimatorListenerAdapter() { |
|||
@Override |
|||
public void onAnimationEnd(Animator animation) { |
|||
if (Objects.equals(startText, view.getText())) { |
|||
// Only set if it hasn't been changed since anim started |
|||
view.setText(endText); |
|||
if (view instanceof EditText) { |
|||
setSelection(((EditText) view), endSelectionStart, |
|||
endSelectionEnd); |
|||
} |
|||
} |
|||
// restore opaque alpha and correct end color |
|||
view.setTextColor(endColor); |
|||
} |
|||
}); |
|||
} |
|||
if (mChangeBehavior == CHANGE_BEHAVIOR_OUT_IN || |
|||
mChangeBehavior == CHANGE_BEHAVIOR_IN) { |
|||
inAnim = ValueAnimator.ofInt(0, Color.alpha(endColor)); |
|||
inAnim.addUpdateListener(animation -> { |
|||
int currAlpha = (Integer) animation.getAnimatedValue(); |
|||
view.setTextColor(currAlpha << 24 | endColor & 0xffffff); |
|||
}); |
|||
inAnim.addListener(new AnimatorListenerAdapter() { |
|||
@Override |
|||
public void onAnimationCancel(Animator animation) { |
|||
// restore opaque alpha and correct end color |
|||
view.setTextColor(endColor); |
|||
} |
|||
}); |
|||
} |
|||
if (outAnim != null && inAnim != null) { |
|||
anim = new AnimatorSet(); |
|||
final AnimatorSet animatorSet = (AnimatorSet) anim; |
|||
if (crossFade) { |
|||
animatorSet.playTogether(outAnim, inAnim); |
|||
} else { |
|||
animatorSet.playSequentially(outAnim, inAnim); |
|||
} |
|||
} else if (outAnim != null) { |
|||
anim = outAnim; |
|||
} else { |
|||
// Must be an in-only animation |
|||
anim = inAnim; |
|||
} |
|||
} |
|||
TransitionListener transitionListener = new TransitionListenerAdapter() { |
|||
int mPausedColor = 0; |
|||
|
|||
@Override |
|||
public void onTransitionPause(@NonNull Transition transition) { |
|||
if (mChangeBehavior != CHANGE_BEHAVIOR_IN) { |
|||
view.setText(endText); |
|||
if (view instanceof EditText) { |
|||
setSelection(((EditText) view), endSelectionStart, endSelectionEnd); |
|||
} |
|||
} |
|||
if (mChangeBehavior > CHANGE_BEHAVIOR_KEEP) { |
|||
mPausedColor = view.getCurrentTextColor(); |
|||
view.setTextColor(endColor); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onTransitionResume(@NonNull Transition transition) { |
|||
if (mChangeBehavior != CHANGE_BEHAVIOR_IN) { |
|||
view.setText(startText); |
|||
if (view instanceof EditText) { |
|||
setSelection(((EditText) view), startSelectionStart, startSelectionEnd); |
|||
} |
|||
} |
|||
if (mChangeBehavior > CHANGE_BEHAVIOR_KEEP) { |
|||
view.setTextColor(mPausedColor); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onTransitionEnd(Transition transition) { |
|||
transition.removeListener(this); |
|||
} |
|||
}; |
|||
addListener(transitionListener); |
|||
if (BuildConfig.DEBUG) { |
|||
Log.d(LOG_TAG, "createAnimator returning " + anim); |
|||
} |
|||
return anim; |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
private void setSelection(EditText editText, int start, int end) { |
|||
if (start >= 0 && end >= 0) { |
|||
editText.setSelection(start, end); |
|||
} |
|||
} |
|||
} |
1123
app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,91 @@ |
|||
package awais.instagrabber.utils; |
|||
|
|||
/* |
|||
* Copyright (C) 2009 The Android Open Source Project |
|||
* |
|||
* 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. |
|||
*/ |
|||
|
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.core.util.ObjectsCompat; |
|||
|
|||
/** |
|||
* Container to ease passing around a tuple of two objects. This object provides a sensible |
|||
* implementation of equals(), returning true if equals() is true on each of the contained |
|||
* objects. |
|||
*/ |
|||
public class NullSafePair<F, S> { |
|||
public final @NonNull |
|||
F first; |
|||
public final @NonNull |
|||
S second; |
|||
|
|||
/** |
|||
* Constructor for a Pair. |
|||
* |
|||
* @param first the first object in the Pair |
|||
* @param second the second object in the pair |
|||
*/ |
|||
public NullSafePair(@NonNull F first, @NonNull S second) { |
|||
this.first = first; |
|||
this.second = second; |
|||
} |
|||
|
|||
/** |
|||
* Checks the two objects for equality by delegating to their respective |
|||
* {@link Object#equals(Object)} methods. |
|||
* |
|||
* @param o the {@link androidx.core.util.Pair} to which this one is to be checked for equality |
|||
* @return true if the underlying objects of the Pair are both considered |
|||
* equal |
|||
*/ |
|||
@Override |
|||
public boolean equals(Object o) { |
|||
if (!(o instanceof androidx.core.util.Pair)) { |
|||
return false; |
|||
} |
|||
androidx.core.util.Pair<?, ?> p = (androidx.core.util.Pair<?, ?>) o; |
|||
return ObjectsCompat.equals(p.first, first) && ObjectsCompat.equals(p.second, second); |
|||
} |
|||
|
|||
/** |
|||
* Compute a hash code using the hash codes of the underlying objects |
|||
* |
|||
* @return a hashcode of the Pair |
|||
*/ |
|||
@Override |
|||
public int hashCode() { |
|||
return first.hashCode() ^ second.hashCode(); |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public String toString() { |
|||
return "Pair{" + first + " " + second + "}"; |
|||
} |
|||
|
|||
/** |
|||
* Convenience method for creating an appropriately typed pair. |
|||
* |
|||
* @param a the first object in the Pair |
|||
* @param b the second object in the pair |
|||
* @return a Pair that is templatized with the types of a and b |
|||
*/ |
|||
@NonNull |
|||
public static <A, B> androidx.core.util.Pair<A, B> create(@Nullable A a, @Nullable B b) { |
|||
return new androidx.core.util.Pair<A, B>(a, b); |
|||
} |
|||
} |
|||
|
@ -0,0 +1,11 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<set xmlns:android="http://schemas.android.com/apk/res/android"> |
|||
<translate |
|||
android:duration="@android:integer/config_mediumAnimTime" |
|||
android:fromXDelta="50%p" |
|||
android:toXDelta="0" /> |
|||
<alpha |
|||
android:duration="@android:integer/config_mediumAnimTime" |
|||
android:fromAlpha="0.0" |
|||
android:toAlpha="1.0" /> |
|||
</set> |
@ -1,10 +1,5 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<set xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:shareInterpolator="false"> |
|||
<translate |
|||
<translate xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:duration="300" |
|||
android:fromXDelta="100%" |
|||
android:fromYDelta="0%" |
|||
android:toXDelta="0%" |
|||
android:toYDelta="0%" /> |
|||
</set> |
|||
android:toXDelta="0%" /> |
@ -0,0 +1,11 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<set xmlns:android="http://schemas.android.com/apk/res/android"> |
|||
<translate |
|||
android:duration="@android:integer/config_mediumAnimTime" |
|||
android:fromXDelta="0" |
|||
android:toXDelta="-50%p" /> |
|||
<alpha |
|||
android:duration="@android:integer/config_mediumAnimTime" |
|||
android:fromAlpha="1.0" |
|||
android:toAlpha="0.0" /> |
|||
</set> |
@ -1,10 +1,5 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<set xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:shareInterpolator="false"> |
|||
<translate |
|||
<translate xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:duration="300" |
|||
android:fromXDelta="0%" |
|||
android:fromYDelta="0%" |
|||
android:toXDelta="100%" |
|||
android:toYDelta="0%" /> |
|||
</set> |
|||
android:toXDelta="100%" /> |
@ -1,10 +1,10 @@ |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:tint="?colorControlNormal" |
|||
android:viewportWidth="24" |
|||
android:viewportHeight="24" |
|||
android:tint="#333333"> |
|||
android:viewportHeight="24"> |
|||
<path |
|||
android:fillColor="@android:color/white" |
|||
android:pathData="M17,3H7c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3V5c0,-1.1 -0.9,-2 -2,-2z"/> |
|||
android:pathData="M17,3H7c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3V5c0,-1.1 -0.9,-2 -2,-2z" /> |
|||
</vector> |
@ -0,0 +1,10 @@ |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:viewportWidth="24" |
|||
android:viewportHeight="24" |
|||
android:tint="?attr/colorControlNormal"> |
|||
<path |
|||
android:fillColor="@android:color/white" |
|||
android:pathData="M17,3L7,3c-1.1,0 -2,0.9 -2,2v16l7,-3 7,3L19,5c0,-1.1 -0.9,-2 -2,-2zM17,18l-5,-2.18L7,18L7,6c0,-0.55 0.45,-1 1,-1h8c0.55,0 1,0.45 1,1v12z"/> |
|||
</vector> |
@ -0,0 +1,10 @@ |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:viewportWidth="24" |
|||
android:viewportHeight="24" |
|||
android:tint="?attr/colorControlNormal"> |
|||
<path |
|||
android:fillColor="@android:color/white" |
|||
android:pathData="M3,17.46v3.04c0,0.28 0.22,0.5 0.5,0.5h3.04c0.13,0 0.26,-0.05 0.35,-0.15L17.81,9.94l-3.75,-3.75L3.15,17.1c-0.1,0.1 -0.15,0.22 -0.15,0.36zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/> |
|||
</vector> |
@ -1,5 +1,5 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<shape xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:shape="oval"> |
|||
<solid android:color="@color/black" /> |
|||
<solid android:color="@android:color/transparent" /> |
|||
</shape> |
@ -1,40 +1,39 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" |
|||
<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android" |
|||
xmlns:app="http://schemas.android.com/apk/res-auto" |
|||
xmlns:tools="http://schemas.android.com/tools" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:background="?attr/colorSurface"> |
|||
android:background="?attr/colorSurface" |
|||
app:layoutDescription="@xml/header_list_scene"> |
|||
|
|||
<!--<com.google.android.material.appbar.AppBarLayout--> |
|||
<!-- android:id="@+id/stories_container"--> |
|||
<!-- android:layout_width="match_parent"--> |
|||
<!-- android:layout_height="wrap_content">--> |
|||
|
|||
<!-- <com.google.android.material.appbar.CollapsingToolbarLayout--> |
|||
<!-- android:layout_width="match_parent"--> |
|||
<!-- android:layout_height="wrap_content"--> |
|||
<!-- app:layout_scrollFlags="scroll|snap">--> |
|||
|
|||
<!--<androidx.recyclerview.widget.RecyclerView--> |
|||
<!-- android:id="@+id/feed_stories_recycler_view"--> |
|||
<!-- android:layout_width="match_parent"--> |
|||
<!-- android:layout_height="wrap_content"--> |
|||
<!-- android:clipToPadding="false" />--> |
|||
<!-- </com.google.android.material.appbar.CollapsingToolbarLayout>--> |
|||
<!--</com.google.android.material.appbar.AppBarLayout>--> |
|||
<androidx.recyclerview.widget.RecyclerView |
|||
android:id="@+id/header" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:background="?attr/toolbarColor" |
|||
android:clipToPadding="false" |
|||
android:orientation="horizontal" |
|||
app:layout_constraintBottom_toTopOf="@id/feed_swipe_refresh_layout" |
|||
app:layout_constraintTop_toTopOf="parent" |
|||
tools:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" |
|||
tools:listitem="@layout/item_highlight" /> |
|||
|
|||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout |
|||
android:id="@+id/feed_swipe_refresh_layout" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
app:layout_behavior="@string/appbar_scrolling_view_behavior"> |
|||
android:layout_height="0dp" |
|||
android:clipToPadding="false" |
|||
android:paddingBottom="?attr/actionBarSize" |
|||
app:layout_behavior="@string/appbar_scrolling_view_behavior" |
|||
app:layout_constraintBottom_toBottomOf="parent" |
|||
app:layout_constraintTop_toBottomOf="@id/header"> |
|||
|
|||
<awais.instagrabber.customviews.PostsRecyclerView |
|||
android:id="@+id/feed_recycler_view" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="match_parent" |
|||
android:clipToPadding="false" |
|||
tools:listitem="@layout/item_feed_photo" /> |
|||
tools:listitem="@layout/item_feed_grid" /> |
|||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> |
|||
</androidx.coordinatorlayout.widget.CoordinatorLayout> |
|||
</androidx.constraintlayout.motion.widget.MotionLayout> |
@ -0,0 +1,208 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<merge xmlns:android="http://schemas.android.com/apk/res/android" |
|||
xmlns:app="http://schemas.android.com/apk/res-auto" |
|||
xmlns:tools="http://schemas.android.com/tools" |
|||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout"> |
|||
|
|||
<androidx.constraintlayout.widget.Barrier |
|||
android:id="@+id/buttons_top_barrier" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
app:barrierAllowsGoneWidgets="true" |
|||
app:barrierDirection="bottom" /> |
|||
|
|||
<com.google.android.material.button.MaterialButton |
|||
android:id="@+id/like" |
|||
style="@style/Widget.MaterialComponents.Button.TextButton" |
|||
android:layout_width="36dp" |
|||
android:layout_height="40dp" |
|||
android:background="@drawable/background_grey_ripple" |
|||
android:visibility="visible" |
|||
app:icon="@drawable/ic_not_liked" |
|||
app:iconGravity="textStart" |
|||
app:iconPadding="0dp" |
|||
app:iconSize="22dp" |
|||
app:layout_constraintBottom_toTopOf="@id/buttons_bottom_barrier" |
|||
app:layout_constraintEnd_toStartOf="@id/likes_count" |
|||
app:layout_constraintStart_toStartOf="parent" |
|||
app:layout_constraintTop_toBottomOf="@id/buttons_top_barrier" |
|||
app:layout_constraintVertical_bias="0" |
|||
app:layout_constraintVertical_chainStyle="packed" |
|||
tools:visibility="visible" /> |
|||
|
|||
<awais.instagrabber.customviews.FormattedNumberTextView |
|||
android:id="@+id/likes_count" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="0dp" |
|||
android:layout_marginEnd="0dp" |
|||
android:background="@drawable/background_grey_ripple" |
|||
android:gravity="center_vertical" |
|||
android:maxWidth="100dp" |
|||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2" |
|||
android:textColor="?android:attr/textColorSecondary" |
|||
app:layout_constraintBottom_toBottomOf="@id/like" |
|||
app:layout_constraintEnd_toStartOf="@id/comment" |
|||
app:layout_constraintStart_toEndOf="@id/like" |
|||
app:layout_constraintTop_toTopOf="@id/like" |
|||
tools:text="9.9k" /> |
|||
|
|||
<com.google.android.material.button.MaterialButton |
|||
android:id="@+id/comment" |
|||
style="@style/Widget.MaterialComponents.Button.TextButton" |
|||
android:layout_width="36dp" |
|||
android:layout_height="40dp" |
|||
android:layout_marginStart="8dp" |
|||
android:background="@drawable/background_grey_ripple" |
|||
app:icon="@drawable/ic_outline_comments_24" |
|||
app:iconPadding="0dp" |
|||
app:iconSize="22dp" |
|||
app:layout_constraintBottom_toTopOf="@id/buttons_bottom_barrier" |
|||
app:layout_constraintEnd_toStartOf="@id/comments_count" |
|||
app:layout_constraintStart_toEndOf="@id/likes_count" |
|||
app:layout_constraintTop_toBottomOf="@id/buttons_top_barrier" /> |
|||
|
|||
<awais.instagrabber.customviews.FormattedNumberTextView |
|||
android:id="@+id/comments_count" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="0dp" |
|||
android:background="@drawable/background_grey_ripple" |
|||
android:ellipsize="end" |
|||
android:gravity="center_vertical" |
|||
android:maxWidth="100dp" |
|||
android:maxLines="1" |
|||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2" |
|||
android:textColor="?android:attr/textColorSecondary" |
|||
app:layout_constraintBottom_toTopOf="@id/buttons_bottom_barrier" |
|||
app:layout_constraintEnd_toStartOf="@id/space_1" |
|||
app:layout_constraintStart_toEndOf="@id/comment" |
|||
app:layout_constraintTop_toBottomOf="@id/buttons_top_barrier" |
|||
tools:text="9999999999999999999999999999999999999999" /> |
|||
|
|||
<Space |
|||
android:id="@+id/space_1" |
|||
android:layout_width="0dp" |
|||
android:layout_height="0dp" |
|||
app:layout_constraintBottom_toTopOf="@id/buttons_bottom_barrier" |
|||
app:layout_constraintEnd_toStartOf="@id/share" |
|||
app:layout_constraintStart_toEndOf="@id/comments_count" |
|||
app:layout_constraintTop_toBottomOf="@id/buttons_top_barrier" /> |
|||
|
|||
<com.google.android.material.button.MaterialButton |
|||
android:id="@+id/share" |
|||
style="@style/Widget.MaterialComponents.Button.TextButton" |
|||
android:layout_width="48dp" |
|||
android:layout_height="40dp" |
|||
android:background="@drawable/background_grey_ripple" |
|||
app:icon="?attr/actionModeShareDrawable" |
|||
app:iconGravity="textStart" |
|||
app:iconPadding="0dp" |
|||
app:iconSize="18dp" |
|||
app:layout_constraintBottom_toTopOf="@id/buttons_bottom_barrier" |
|||
app:layout_constraintEnd_toStartOf="@id/save" |
|||
app:layout_constraintStart_toEndOf="@id/space_1" |
|||
app:layout_constraintTop_toBottomOf="@id/buttons_top_barrier" /> |
|||
|
|||
<com.google.android.material.button.MaterialButton |
|||
android:id="@+id/save" |
|||
style="@style/Widget.MaterialComponents.Button.TextButton" |
|||
android:layout_width="48dp" |
|||
android:layout_height="40dp" |
|||
android:background="@drawable/background_grey_ripple" |
|||
android:visibility="visible" |
|||
app:icon="@drawable/ic_round_bookmark_border_24" |
|||
app:iconGravity="textStart" |
|||
app:iconPadding="0dp" |
|||
app:iconSize="18dp" |
|||
app:layout_constraintBottom_toTopOf="@id/buttons_bottom_barrier" |
|||
app:layout_constraintEnd_toStartOf="@id/download" |
|||
app:layout_constraintStart_toEndOf="@id/share" |
|||
app:layout_constraintTop_toBottomOf="@id/buttons_top_barrier" |
|||
tools:visibility="visible" /> |
|||
|
|||
<com.google.android.material.button.MaterialButton |
|||
android:id="@+id/download" |
|||
style="@style/Widget.MaterialComponents.Button.TextButton" |
|||
android:layout_width="48dp" |
|||
android:layout_height="40dp" |
|||
android:background="@drawable/background_grey_ripple" |
|||
android:visibility="visible" |
|||
app:icon="@drawable/ic_download" |
|||
app:iconGravity="textStart" |
|||
app:iconPadding="0dp" |
|||
app:iconSize="18dp" |
|||
app:layout_constraintBottom_toTopOf="@id/buttons_bottom_barrier" |
|||
app:layout_constraintEnd_toEndOf="parent" |
|||
app:layout_constraintStart_toEndOf="@id/save" |
|||
app:layout_constraintTop_toBottomOf="@id/buttons_top_barrier" |
|||
tools:visibility="visible" /> |
|||
|
|||
<androidx.constraintlayout.widget.Barrier |
|||
android:id="@+id/buttons_bottom_barrier" |
|||
android:layout_width="0dp" |
|||
android:layout_height="0dp" |
|||
app:barrierAllowsGoneWidgets="true" |
|||
app:barrierDirection="top" /> |
|||
|
|||
<androidx.appcompat.widget.AppCompatTextView |
|||
android:id="@+id/date" |
|||
android:layout_width="0dp" |
|||
android:layout_height="wrap_content" |
|||
android:padding="8dp" |
|||
app:layout_constraintBottom_toTopOf="@id/caption_barrier" |
|||
app:layout_constraintEnd_toStartOf="@id/views_count" |
|||
app:layout_constraintStart_toStartOf="parent" |
|||
app:layout_constraintTop_toBottomOf="@id/buttons_bottom_barrier" |
|||
tools:layout_constraintVertical_chainStyle="packed" |
|||
tools:text="2020-11-07 11:18:55" |
|||
tools:visibility="visible" /> |
|||
|
|||
<androidx.appcompat.widget.AppCompatTextView |
|||
android:id="@+id/views_count" |
|||
android:layout_width="wrap_content" |
|||
android:layout_height="wrap_content" |
|||
android:padding="8dp" |
|||
app:layout_constraintBottom_toBottomOf="@id/date" |
|||
app:layout_constraintEnd_toEndOf="parent" |
|||
app:layout_constraintStart_toEndOf="@id/date" |
|||
app:layout_constraintTop_toTopOf="@id/date" |
|||
tools:text="9999999999 views" |
|||
tools:visibility="visible" /> |
|||
|
|||
<androidx.constraintlayout.widget.Barrier |
|||
android:id="@+id/caption_barrier" |
|||
android:layout_width="0dp" |
|||
android:layout_height="0dp" |
|||
app:barrierAllowsGoneWidgets="true" |
|||
app:barrierDirection="bottom" /> |
|||
|
|||
<awais.instagrabber.customviews.RamboTextViewV2 |
|||
android:id="@+id/caption" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:layout_gravity="bottom" |
|||
android:background="@null" |
|||
android:clickable="true" |
|||
android:focusable="true" |
|||
android:padding="8dp" |
|||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" |
|||
app:layout_constraintBottom_toTopOf="@id/translate" |
|||
app:layout_constraintTop_toBottomOf="@id/caption_barrier" |
|||
app:layout_constraintVertical_bias="0" |
|||
app:layout_constraintVertical_chainStyle="packed" |
|||
tools:text="Text text text Text text text Text text text Text text text Text text text" |
|||
tools:visibility="visible" /> |
|||
|
|||
<androidx.appcompat.widget.AppCompatTextView |
|||
android:id="@+id/translate" |
|||
android:layout_width="match_parent" |
|||
android:layout_height="wrap_content" |
|||
android:background="@null" |
|||
android:gravity="center_vertical" |
|||
android:padding="8dp" |
|||
android:text="@string/translate_caption" |
|||
android:textColor="@color/blue_600" |
|||
android:textSize="16sp" |
|||
android:visibility="visible" |
|||
app:layout_constraintBottom_toBottomOf="parent" |
|||
app:layout_constraintTop_toBottomOf="@id/caption" /> |
|||
</merge> |
@ -0,0 +1,5 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<resources> |
|||
<!--<drawable name="exo_styled_controls_play">@drawable/ic_play_arrow_24</drawable>--> |
|||
<!--<drawable name="exo_styled_controls_pause">@drawable/ic_pause_24</drawable>--> |
|||
</resources> |
Some files were not shown because too many files changed in this diff
Write
Preview
Loading…
Cancel
Save
Reference in new issue