1
0
mirror of https://github.com/KokaKiwi/BarInsta synced 2024-12-23 13:26:59 +00:00

Merge branch 'austinhuang0131:master' into stamatiap/development

This commit is contained in:
Stamatia Papageorgiou 2021-05-16 10:09:08 +03:00
commit cdb566a160
48 changed files with 2135 additions and 721 deletions

View File

@ -79,6 +79,25 @@
"code"
]
},
{
"login": "stamatiap",
"name": "Stamatia Papageorgiou",
"avatar_url": "https://avatars.githubusercontent.com/u/57223967?v=4",
"profile": "https://github.com/stamatiap",
"contributions": [
"code",
"translation"
]
},
{
"login": "The-EDev",
"name": "Farook Al-Sammarraie",
"avatar_url": "https://avatars.githubusercontent.com/u/60552923?v=4",
"profile": "https://github.com/The-EDev",
"contributions": [
"code"
]
},
{
"login": "Zopieux",
"name": "Alexandre Macabies",

View File

@ -15,10 +15,11 @@ jobs:
uses: actions/checkout@v2
- name: set up JDK 1.8
uses: actions/setup-java@v1
uses: actions/setup-java@v2
with:
java-version: 1.8
distribution: 'zulu'
java-version: '8'
- name: Grant execute permission for gradlew
run: chmod +x gradlew

View File

@ -16,9 +16,10 @@ jobs:
uses: actions/checkout@v2
- name: set up JDK 1.8
uses: actions/setup-java@v1
uses: actions/setup-java@v2
with:
java-version: 1.8
distribution: 'zulu'
java-version: '8'
- name: Grant execute permission for gradlew
run: chmod +x gradlew

View File

@ -9,7 +9,7 @@
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com)
[![GPLv3 license](https://img.shields.io/badge/License-GPLv3-blue.svg)](./LICENSE)
[![GitHub stars](https://img.shields.io/github/stars/austinhuang0131/instagrabber.svg?style=social&label=Star)](https://GitHub.com/austinhuang0131/barinsta/stargazers/)<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-42-orange.svg)](#contributors)
[![All Contributors](https://img.shields.io/badge/all_contributors-44-orange.svg)](#contributors)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
Instagram client; previously known as InstaGrabber.
@ -63,49 +63,53 @@ Prominent contributors are listed here in the [all-contributors](https://allcont
</tr>
<tr>
<td align="center"><a href="https://github.com/MeLlamoPablo"><img src="https://avatars.githubusercontent.com/u/11708035?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Pablo Rodríguez</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=MeLlamoPablo" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/stamatiap"><img src="https://avatars.githubusercontent.com/u/57223967?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Stamatia Papageorgiou</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=stamatiap" title="Code">💻</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/The-EDev"><img src="https://avatars.githubusercontent.com/u/60552923?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Farook Al-Sammarraie</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=The-EDev" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/Zopieux"><img src="https://avatars.githubusercontent.com/u/81353?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alexandre Macabies</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=Zopieux" title="Code">💻</a></td>
<td align="center"><a href="https://snajdovski.github.io"><img src="https://avatars2.githubusercontent.com/u/42580385?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Stefan Najdovski</b></sub></a><br /><a href="#design-snajdovski" title="Design">🎨</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/CrazyMarvin"><img src="https://avatars3.githubusercontent.com/u/15004217?v=4?s=100" width="100px;" alt=""/><br /><sub><b>CrazyMarvin</b></sub></a><br /><a href="#financial-CrazyMarvin" title="Financial">💵</a></td>
<td align="center"><a href="http://kevinthomas.dev"><img src="https://avatars2.githubusercontent.com/u/15370181?s=100" width="100px;" alt=""/><br /><sub><b>Kevin Thomas</b></sub></a><br /><a href="#financial-KevinNThomas" title="Financial">💵</a></td>
<td align="center"><a href="https://github.com/Shadowspear123"><img src="https://avatars1.githubusercontent.com/u/50462281?s=100" width="100px;" alt=""/><br /><sub><b>Shadowspear123</b></sub></a><br /><a href="#blog-Shadowspear123" title="Blogposts">📝</a> <a href="https://github.com/austinhuang0131/barinsta/issues?q=author%3AShadowspear123" title="Bug reports">🐛</a> <a href="#ideas-Shadowspear123" title="Ideas, Planning, & Feedback">🤔</a> <a href="#question-Shadowspear123" title="Answering Questions">💬</a></td>
</tr>
<tr>
<td align="center"><a href="http://kevinthomas.dev"><img src="https://avatars2.githubusercontent.com/u/15370181?s=100" width="100px;" alt=""/><br /><sub><b>Kevin Thomas</b></sub></a><br /><a href="#financial-KevinNThomas" title="Financial">💵</a></td>
<td align="center"><a href="https://github.com/Shadowspear123"><img src="https://avatars1.githubusercontent.com/u/50462281?s=100" width="100px;" alt=""/><br /><sub><b>Shadowspear123</b></sub></a><br /><a href="#blog-Shadowspear123" title="Blogposts">📝</a> <a href="https://github.com/austinhuang0131/barinsta/issues?q=author%3AShadowspear123" title="Bug reports">🐛</a> <a href="#ideas-Shadowspear123" title="Ideas, Planning, & Feedback">🤔</a> <a href="#question-Shadowspear123" title="Answering Questions">💬</a></td>
<td align="center"><a href="https://github.com/RickyM7"><img src="https://avatars3.githubusercontent.com/u/24703825?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ricardo</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/issues?q=author%3ARickyM7" title="Bug reports">🐛</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/Akrai"><img src="https://avatars1.githubusercontent.com/u/5624597?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Akrai</b></sub></a><br /><a href="#ideas-Akrai" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/avtkal"><img src="https://avatars.githubusercontent.com/u/63205014?v=4?s=100" width="100px;" alt=""/><br /><sub><b>avtkal</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/cizordj"><img src="https://avatars2.githubusercontent.com/u/32869222?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cézar Augusto</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/dimitrist19"><img src="https://avatars.githubusercontent.com/u/56406468?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dimitris T</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/farzadx"><img src="https://avatars2.githubusercontent.com/u/70059397?v=4?s=100" width="100px;" alt=""/><br /><sub><b>farzadx</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/dimitrist19"><img src="https://avatars.githubusercontent.com/u/56406468?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dimitris T</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/farzadx"><img src="https://avatars2.githubusercontent.com/u/70059397?v=4?s=100" width="100px;" alt=""/><br /><sub><b>farzadx</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/faydin"><img src="https://avatars2.githubusercontent.com/u/22706676?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Fatih Aydın</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/fouze555"><img src="https://avatars3.githubusercontent.com/u/71935341?v=4?s=100" width="100px;" alt=""/><br /><sub><b>fouze555</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/Galang23"><img src="https://avatars3.githubusercontent.com/u/13700948?s=100" width="100px;" alt=""/><br /><sub><b>Galang23</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/initdebugs"><img src="https://avatars0.githubusercontent.com/u/75781464?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Initdebugs</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://janek.xyz/"><img src="https://avatars3.githubusercontent.com/u/8365659?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jakub Janek</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/GenosseFlosse"><img src="https://avatars.githubusercontent.com/u/59205524?v=4?s=100" width="100px;" alt=""/><br /><sub><b>GenosseFlosse</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center"><a href="https://janek.xyz/"><img src="https://avatars3.githubusercontent.com/u/8365659?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jakub Janek</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/GenosseFlosse"><img src="https://avatars.githubusercontent.com/u/59205524?v=4?s=100" width="100px;" alt=""/><br /><sub><b>GenosseFlosse</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://becauseofprog.fr/"><img src="https://avatars3.githubusercontent.com/u/24623168?s=100" width="100px;" alt=""/><br /><sub><b>kernoeb</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/MoaufmKlo"><img src="https://avatars1.githubusercontent.com/u/45636897?s=100" width="100px;" alt=""/><br /><sub><b>MoaufmKlo</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/nalinalini"><img src="https://avatars0.githubusercontent.com/u/65640431?v=4?s=100" width="100px;" alt=""/><br /><sub><b>nalinalini</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/peterge1998"><img src="https://avatars2.githubusercontent.com/u/47355238?s=100" width="100px;" alt=""/><br /><sub><b>peterge1998</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/PierreM0"><img src="https://avatars3.githubusercontent.com/u/71077853?v=4?s=100" width="100px;" alt=""/><br /><sub><b>PierreM0</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/Pyrobauve"><img src="https://avatars.githubusercontent.com/u/48654473?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Pyrobauve</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/PierreM0"><img src="https://avatars3.githubusercontent.com/u/71077853?v=4?s=100" width="100px;" alt=""/><br /><sub><b>PierreM0</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/Pyrobauve"><img src="https://avatars.githubusercontent.com/u/48654473?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Pyrobauve</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/RAMAR-RAR"><img src="https://avatars3.githubusercontent.com/u/47423745?s=100" width="100px;" alt=""/><br /><sub><b>RAMAR-RAR</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/rohang02"><img src="https://avatars3.githubusercontent.com/u/47921164?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rohang02</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/retiolus"><img src="https://avatars1.githubusercontent.com/u/65604466?v=4?s=100" width="100px;" alt=""/><br /><sub><b>retiolus</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/rikishi0071"><img src="https://avatars3.githubusercontent.com/u/18183855?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rikishi0071</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://gitlab.com/sandboiii"><img src="https://avatars.githubusercontent.com/u/17468894?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alexey Peschany</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/Sitavi"><img src="https://avatars.githubusercontent.com/u/80586127?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sitavi</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center"><a href="https://gitlab.com/sandboiii"><img src="https://avatars.githubusercontent.com/u/17468894?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alexey Peschany</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/Sitavi"><img src="https://avatars.githubusercontent.com/u/80586127?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sitavi</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://stillu.cc/"><img src="https://avatars2.githubusercontent.com/u/5843208?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Still Hsu</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/Lego8486"><img src="https://avatars1.githubusercontent.com/u/47414485?s=100" width="100px;" alt=""/><br /><sub><b>Ten_Lego</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/wagnim"><img src="https://avatars0.githubusercontent.com/u/30241419?s=100" width="100px;" alt=""/><br /><sub><b>wagnim</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/wokija"><img src="https://avatars.githubusercontent.com/u/14982166?v=4?s=100" width="100px;" alt=""/><br /><sub><b>wokija</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/ysakamoto"><img src="https://avatars3.githubusercontent.com/u/1331642?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ysakamoto</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/ZDVokoun"><img src="https://avatars.githubusercontent.com/u/76393152?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ZDVokoun</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr>
@ -121,7 +125,7 @@ This app's predecessor, InstaGrabber, was originally made by [@AwaisKing](https:
Barinsta
Copyright (C) 2020-2021 Austin Huang <im@austinhuang.me>
Ammar Githam <ammargitham786@gmail.com>
Ammar Githam <ammar.githam@outlook.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@ -12,7 +12,7 @@ def getGitHash = { ->
}
android {
compileSdkVersion 29
compileSdkVersion 30
defaultConfig {
applicationId 'me.austinhuang.instagrabber'
@ -165,8 +165,6 @@ dependencies {
implementation "com.google.android.exoplayer:exoplayer-dash:$exoplayer_version"
implementation "com.google.android.exoplayer:exoplayer-ui:$exoplayer_version"
implementation "androidx.appcompat:appcompat:$appcompat_version"
implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
implementation "androidx.recyclerview:recyclerview:1.2.0"
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation "androidx.viewpager2:viewpager2:1.0.0"
@ -180,6 +178,9 @@ dependencies {
implementation 'com.google.guava:guava:27.0.1-android'
def core_version = "1.6.0-alpha03"
implementation "androidx.core:core:$core_version"
// Room
def room_version = "2.2.6"
implementation "androidx.room:room-runtime:$room_version"

View File

@ -27,8 +27,7 @@
<activity
android:name=".activities.MainActivity"
android:launchMode="singleTop"
android:taskAffinity=".Main"
android:windowSoftInputMode="adjustResize">
android:taskAffinity=".Main">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.VIEW" />

View File

@ -31,6 +31,9 @@ import androidx.appcompat.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.provider.FontRequest;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.emoji.text.EmojiCompat;
import androidx.emoji.text.FontRequestEmojiCompatConfig;
import androidx.fragment.app.FragmentManager;
@ -61,6 +64,7 @@ import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.customviews.emoji.EmojiVariantManager;
import awais.instagrabber.customviews.helpers.RootViewDeferringInsetsCallback;
import awais.instagrabber.customviews.helpers.TextWatcherAdapter;
import awais.instagrabber.databinding.ActivityMainBinding;
import awais.instagrabber.fragments.PostViewV2Fragment;
@ -137,11 +141,19 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
instance = this;
binding = ActivityMainBinding.inflate(getLayoutInflater());
setupCookie();
if (settingsHelper.getBoolean(Constants.FLAG_SECURE))
if (settingsHelper.getBoolean(Constants.FLAG_SECURE)) {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
}
setContentView(binding.getRoot());
final Toolbar toolbar = binding.toolbar;
setSupportActionBar(toolbar);
final RootViewDeferringInsetsCallback deferringInsetsCallback = new RootViewDeferringInsetsCallback(
WindowInsetsCompat.Type.systemBars(),
WindowInsetsCompat.Type.ime()
);
ViewCompat.setWindowInsetsAnimationCallback(binding.getRoot(), deferringInsetsCallback);
ViewCompat.setOnApplyWindowInsetsListener(binding.getRoot(), deferringInsetsCallback);
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
createNotificationChannels();
try {
final CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) binding.bottomNavView.getLayoutParams();

View File

@ -23,23 +23,29 @@ public final class FeedStoriesListAdapter extends ListAdapter<FeedStoryModel, St
private List<FeedStoryModel> list;
private final Filter filter = new Filter() {
@Nullable
@NonNull
@Override
protected FilterResults performFiltering(final CharSequence filter) {
final boolean isFilterEmpty = TextUtils.isEmpty(filter);
final String query = isFilterEmpty ? null : filter.toString().toLowerCase();
for (FeedStoryModel item : list) {
if (isFilterEmpty) item.setShown(true);
else item.setShown(item.getProfileModel().getUsername().toLowerCase().contains(query));
final String query = TextUtils.isEmpty(filter) ? null : filter.toString().toLowerCase();
List<FeedStoryModel> filteredList = list;
if (list != null && query != null) {
filteredList = list.stream()
.filter(feedStoryModel -> feedStoryModel.getProfileModel()
.getUsername()
.toLowerCase()
.contains(query))
.collect(Collectors.toList());
}
return null;
final FilterResults filterResults = new FilterResults();
filterResults.count = filteredList != null ? filteredList.size() : 0;
filterResults.values = filteredList;
return filterResults;
}
@Override
protected void publishResults(final CharSequence constraint, final FilterResults results) {
submitList(list);
notifyDataSetChanged();
//noinspection unchecked
submitList((List<FeedStoryModel>) results.values, true);
}
};
@ -65,10 +71,16 @@ public final class FeedStoriesListAdapter extends ListAdapter<FeedStoryModel, St
return filter;
}
private void submitList(@Nullable final List<FeedStoryModel> list, final boolean isFiltered) {
if (!isFiltered) {
this.list = list;
}
super.submitList(list);
}
@Override
public void submitList(final List<FeedStoryModel> list) {
super.submitList(list.stream().filter(i -> i.isShown()).collect(Collectors.toList()));
this.list = list;
submitList(list, false);
}
@NonNull
@ -82,11 +94,11 @@ public final class FeedStoriesListAdapter extends ListAdapter<FeedStoryModel, St
@Override
public void onBindViewHolder(@NonNull final StoryListViewHolder holder, final int position) {
final FeedStoryModel model = getItem(position);
holder.bind(model, position, listener);
holder.bind(model, listener);
}
public interface OnFeedStoryClickListener {
void onFeedStoryClick(final FeedStoryModel model, final int position);
void onFeedStoryClick(final FeedStoryModel model);
void onProfileClick(final String username);
}

View File

@ -20,7 +20,6 @@ public final class StoryListViewHolder extends RecyclerView.ViewHolder {
}
public void bind(final FeedStoryModel model,
final int position,
final OnFeedStoryClickListener notificationClickListener) {
if (model == null) return;
@ -53,7 +52,7 @@ public final class StoryListViewHolder extends RecyclerView.ViewHolder {
itemView.setOnClickListener(v -> {
if (notificationClickListener == null) return;
notificationClickListener.onFeedStoryClick(model, position);
notificationClickListener.onFeedStoryClick(model);
});
}

View File

@ -0,0 +1,246 @@
package awais.instagrabber.customviews;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.WindowInsetsAnimation;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.NestedScrollingParent3;
import androidx.core.view.NestedScrollingParentHelper;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import java.util.Arrays;
import awais.instagrabber.customviews.helpers.SimpleImeAnimationController;
import awais.instagrabber.utils.ViewUtils;
import static androidx.core.view.ViewCompat.TYPE_TOUCH;
public final class InsetsAnimationLinearLayout extends LinearLayout implements NestedScrollingParent3 {
private final NestedScrollingParentHelper nestedScrollingParentHelper = new NestedScrollingParentHelper(this);
private final SimpleImeAnimationController imeAnimController = new SimpleImeAnimationController();
private final int[] tempIntArray2 = new int[2];
private final int[] startViewLocation = new int[2];
private View currentNestedScrollingChild;
private int dropNextY;
private boolean scrollImeOffScreenWhenVisible = true;
private boolean scrollImeOnScreenWhenNotVisible = true;
private boolean scrollImeOffScreenWhenVisibleOnFling = false;
private boolean scrollImeOnScreenWhenNotVisibleOnFling = false;
public InsetsAnimationLinearLayout(final Context context, @Nullable final AttributeSet attrs) {
super(context, attrs);
}
public InsetsAnimationLinearLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public final boolean getScrollImeOffScreenWhenVisible() {
return scrollImeOffScreenWhenVisible;
}
public final void setScrollImeOffScreenWhenVisible(boolean scrollImeOffScreenWhenVisible) {
this.scrollImeOffScreenWhenVisible = scrollImeOffScreenWhenVisible;
}
public final boolean getScrollImeOnScreenWhenNotVisible() {
return scrollImeOnScreenWhenNotVisible;
}
public final void setScrollImeOnScreenWhenNotVisible(boolean scrollImeOnScreenWhenNotVisible) {
this.scrollImeOnScreenWhenNotVisible = scrollImeOnScreenWhenNotVisible;
}
public boolean getScrollImeOffScreenWhenVisibleOnFling() {
return scrollImeOffScreenWhenVisibleOnFling;
}
public void setScrollImeOffScreenWhenVisibleOnFling(final boolean scrollImeOffScreenWhenVisibleOnFling) {
this.scrollImeOffScreenWhenVisibleOnFling = scrollImeOffScreenWhenVisibleOnFling;
}
public boolean getScrollImeOnScreenWhenNotVisibleOnFling() {
return scrollImeOnScreenWhenNotVisibleOnFling;
}
public void setScrollImeOnScreenWhenNotVisibleOnFling(final boolean scrollImeOnScreenWhenNotVisibleOnFling) {
this.scrollImeOnScreenWhenNotVisibleOnFling = scrollImeOnScreenWhenNotVisibleOnFling;
}
public SimpleImeAnimationController getImeAnimController() {
return imeAnimController;
}
@Override
public boolean onStartNestedScroll(@NonNull final View child,
@NonNull final View target,
final int axes,
final int type) {
return (axes & SCROLL_AXIS_VERTICAL) != 0 && type == TYPE_TOUCH;
}
@Override
public void onNestedScrollAccepted(@NonNull final View child,
@NonNull final View target,
final int axes,
final int type) {
nestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes, type);
currentNestedScrollingChild = child;
}
@Override
public void onNestedPreScroll(@NonNull final View target,
final int dx,
final int dy,
@NonNull final int[] consumed,
final int type) {
if (imeAnimController.isInsetAnimationRequestPending()) {
consumed[0] = dx;
consumed[1] = dy;
} else {
int deltaY = dy;
if (dropNextY != 0) {
consumed[1] = dropNextY;
deltaY = dy - dropNextY;
dropNextY = 0;
}
if (deltaY < 0) {
if (imeAnimController.isInsetAnimationInProgress()) {
consumed[1] -= imeAnimController.insetBy(-deltaY);
} else if (scrollImeOffScreenWhenVisible && !imeAnimController.isInsetAnimationRequestPending()) {
WindowInsetsCompat rootWindowInsets = ViewCompat.getRootWindowInsets(this);
if (rootWindowInsets != null) {
if (rootWindowInsets.isVisible(WindowInsetsCompat.Type.ime())) {
startControlRequest();
consumed[1] = deltaY;
}
}
}
}
}
}
@Override
public void onNestedScroll(@NonNull final View target,
final int dxConsumed,
final int dyConsumed,
final int dxUnconsumed,
final int dyUnconsumed,
final int type,
@NonNull final int[] consumed) {
if (dyUnconsumed > 0) {
if (imeAnimController.isInsetAnimationInProgress()) {
consumed[1] = -imeAnimController.insetBy(-dyUnconsumed);
} else if (scrollImeOnScreenWhenNotVisible && !imeAnimController.isInsetAnimationRequestPending()) {
WindowInsetsCompat rootWindowInsets = ViewCompat.getRootWindowInsets(this);
if (rootWindowInsets != null) {
if (!rootWindowInsets.isVisible(WindowInsetsCompat.Type.ime())) {
startControlRequest();
consumed[1] = dyUnconsumed;
}
}
}
}
}
@Override
public boolean onNestedFling(@NonNull final View target,
final float velocityX,
final float velocityY,
final boolean consumed) {
if (imeAnimController.isInsetAnimationInProgress()) {
imeAnimController.animateToFinish(velocityY);
return true;
} else {
boolean imeVisible = false;
final WindowInsetsCompat rootWindowInsets = ViewCompat.getRootWindowInsets(this);
if (rootWindowInsets != null && rootWindowInsets.isVisible(WindowInsetsCompat.Type.ime())) {
imeVisible = true;
}
if (velocityY > 0 && scrollImeOnScreenWhenNotVisibleOnFling && !imeVisible) {
imeAnimController.startAndFling(this, velocityY);
return true;
} else if (velocityY < 0 && scrollImeOffScreenWhenVisibleOnFling && imeVisible) {
imeAnimController.startAndFling(this, velocityY);
return true;
} else {
return false;
}
}
}
@Override
public void onStopNestedScroll(@NonNull final View target, final int type) {
nestedScrollingParentHelper.onStopNestedScroll(target, type);
if (imeAnimController.isInsetAnimationInProgress() && !imeAnimController.isInsetAnimationFinishing()) {
imeAnimController.animateToFinish(null);
}
reset();
}
@Override
public void dispatchWindowInsetsAnimationPrepare(@NonNull final WindowInsetsAnimation animation) {
super.dispatchWindowInsetsAnimationPrepare(animation);
ViewUtils.suppressLayoutCompat(this, false);
}
private void startControlRequest() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
return;
}
ViewUtils.suppressLayoutCompat(this, true);
if (currentNestedScrollingChild != null) {
currentNestedScrollingChild.getLocationInWindow(startViewLocation);
}
imeAnimController.startControlRequest(this, windowInsetsAnimationControllerCompat -> onControllerReady());
}
private void onControllerReady() {
if (currentNestedScrollingChild != null) {
imeAnimController.insetBy(0);
int[] location = tempIntArray2;
currentNestedScrollingChild.getLocationInWindow(location);
dropNextY = location[1] - startViewLocation[1];
}
}
private void reset() {
dropNextY = 0;
Arrays.fill(startViewLocation, 0);
ViewUtils.suppressLayoutCompat(this, false);
}
@Override
public void onNestedScrollAccepted(@NonNull final View child,
@NonNull final View target,
final int axes) {
onNestedScrollAccepted(child, target, axes, TYPE_TOUCH);
}
@Override
public void onNestedScroll(@NonNull final View target,
final int dxConsumed,
final int dyConsumed,
final int dxUnconsumed,
final int dyUnconsumed,
final int type) {
onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, tempIntArray2);
}
@Override
public void onStopNestedScroll(@NonNull final View target) {
onStopNestedScroll(target, TYPE_TOUCH);
}
}

View File

@ -0,0 +1,33 @@
package awais.instagrabber.customviews;
import android.content.Context;
import android.util.AttributeSet;
import android.view.WindowInsets;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
public class InsetsNotifyingCoordinatorLayout extends CoordinatorLayout {
public InsetsNotifyingCoordinatorLayout(@NonNull final Context context) {
super(context);
}
public InsetsNotifyingCoordinatorLayout(@NonNull final Context context, @Nullable final AttributeSet attrs) {
super(context, attrs);
}
public InsetsNotifyingCoordinatorLayout(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
int childCount = getChildCount();
for (int index = 0; index < childCount; index++) {
getChildAt(index).dispatchApplyWindowInsets(insets);
}
return insets;
}
}

View File

@ -0,0 +1,35 @@
package awais.instagrabber.customviews;
import android.content.Context;
import android.util.AttributeSet;
import android.view.WindowInsets;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
public class InsetsNotifyingLinearLayout extends LinearLayout {
public InsetsNotifyingLinearLayout(final Context context) {
super(context);
}
public InsetsNotifyingLinearLayout(final Context context, @Nullable final AttributeSet attrs) {
super(context, attrs);
}
public InsetsNotifyingLinearLayout(final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public InsetsNotifyingLinearLayout(final Context context, final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
int childCount = getChildCount();
for (int index = 0; index < childCount; index++) {
getChildAt(index).dispatchApplyWindowInsets(insets);
}
return insets;
}
}

View File

@ -0,0 +1,87 @@
/*
* Copyright 2020 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.
*/
package awais.instagrabber.customviews.helpers;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsAnimationCompat;
import androidx.core.view.WindowInsetsCompat;
import java.util.List;
/**
* A [WindowInsetsAnimationCompat.Callback] which will request and clear focus on the given view,
* depending on the [WindowInsetsCompat.Type.ime] visibility state when an IME
* [WindowInsetsAnimationCompat] has finished.
* <p>
* This is primarily used when animating the [WindowInsetsCompat.Type.ime], so that the
* appropriate view is focused for accepting input from the IME.
*/
public class ControlFocusInsetsAnimationCallback extends WindowInsetsAnimationCompat.Callback {
private final View view;
public ControlFocusInsetsAnimationCallback(@NonNull final View view) {
this(view, DISPATCH_MODE_STOP);
}
/**
* @param view the view to request/clear focus
* @param dispatchMode The dispatch mode for this callback.
* @see WindowInsetsAnimationCompat.Callback.DispatchMode
*/
public ControlFocusInsetsAnimationCallback(@NonNull final View view, final int dispatchMode) {
super(dispatchMode);
this.view = view;
}
@NonNull
@Override
public WindowInsetsCompat onProgress(@NonNull final WindowInsetsCompat insets,
@NonNull final List<WindowInsetsAnimationCompat> runningAnimations) {
// no-op and return the insets
return insets;
}
@Override
public void onEnd(final WindowInsetsAnimationCompat animation) {
if ((animation.getTypeMask() & WindowInsetsCompat.Type.ime()) != 0) {
// The animation has now finished, so we can check the view's focus state.
// We post the check because the rootWindowInsets has not yet been updated, but will
// be in the next message traversal
view.post(this::checkFocus);
}
}
private void checkFocus() {
final WindowInsetsCompat rootWindowInsets = ViewCompat.getRootWindowInsets(view);
boolean imeVisible = false;
if (rootWindowInsets != null) {
imeVisible = rootWindowInsets.isVisible(WindowInsetsCompat.Type.ime());
}
if (imeVisible && view.getRootView().findFocus() == null) {
// If the IME will be visible, and there is not a currently focused view in
// the hierarchy, request focus on our view
view.requestFocus();
} else if (!imeVisible && view.isFocused()) {
// If the IME will not be visible and our view is currently focused, clear the focus
view.clearFocus();
}
}
}

View File

@ -0,0 +1,117 @@
package awais.instagrabber.customviews.helpers;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsAnimationCompat;
import androidx.core.view.WindowInsetsCompat;
import java.util.List;
/**
* A customized {@link TranslateDeferringInsetsAnimationCallback} for the emoji picker
*/
public class EmojiPickerInsetsAnimationCallback extends WindowInsetsAnimationCompat.Callback {
private static final String TAG = EmojiPickerInsetsAnimationCallback.class.getSimpleName();
private final View view;
private final int persistentInsetTypes;
private final int deferredInsetTypes;
private int kbHeight;
private onKbVisibilityChangeListener listener;
private boolean shouldTranslate;
public EmojiPickerInsetsAnimationCallback(final View view,
final int persistentInsetTypes,
final int deferredInsetTypes) {
this(view, persistentInsetTypes, deferredInsetTypes, DISPATCH_MODE_STOP);
}
public EmojiPickerInsetsAnimationCallback(final View view,
final int persistentInsetTypes,
final int deferredInsetTypes,
final int dispatchMode) {
super(dispatchMode);
if ((persistentInsetTypes & deferredInsetTypes) != 0) {
throw new IllegalArgumentException("persistentInsetTypes and deferredInsetTypes can not contain " +
"any of same WindowInsetsCompat.Type values");
}
this.view = view;
this.persistentInsetTypes = persistentInsetTypes;
this.deferredInsetTypes = deferredInsetTypes;
}
@NonNull
@Override
public WindowInsetsCompat onProgress(@NonNull final WindowInsetsCompat insets,
@NonNull final List<WindowInsetsAnimationCompat> runningAnimations) {
// onProgress() is called when any of the running animations progress...
// First we get the insets which are potentially deferred
final Insets typesInset = insets.getInsets(deferredInsetTypes);
// Then we get the persistent inset types which are applied as padding during layout
final Insets otherInset = insets.getInsets(persistentInsetTypes);
// Now that we subtract the two insets, to calculate the difference. We also coerce
// the insets to be >= 0, to make sure we don't use negative insets.
final Insets subtract = Insets.subtract(typesInset, otherInset);
final Insets diff = Insets.max(subtract, Insets.NONE);
// The resulting `diff` insets contain the values for us to apply as a translation
// to the view
view.setTranslationX(diff.left - diff.right);
view.setTranslationY(shouldTranslate ? diff.top - diff.bottom : -kbHeight);
return insets;
}
@Override
public void onEnd(@NonNull final WindowInsetsAnimationCompat animation) {
try {
final WindowInsetsCompat rootWindowInsets = ViewCompat.getRootWindowInsets(view);
if (kbHeight == 0) {
if (rootWindowInsets == null) return;
final Insets imeInsets = rootWindowInsets.getInsets(WindowInsetsCompat.Type.ime());
final Insets navBarInsets = rootWindowInsets.getInsets(WindowInsetsCompat.Type.navigationBars());
kbHeight = imeInsets.bottom - navBarInsets.bottom;
final ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
if (layoutParams != null) {
layoutParams.height = kbHeight;
layoutParams.setMargins(layoutParams.leftMargin, layoutParams.topMargin, layoutParams.rightMargin, -kbHeight);
}
}
view.setTranslationX(0f);
final boolean visible = rootWindowInsets != null && rootWindowInsets.isVisible(WindowInsetsCompat.Type.ime());
float translationY = 0;
if (!shouldTranslate) {
translationY = -kbHeight;
if (visible) {
translationY = 0;
}
}
view.setTranslationY(translationY);
if (listener != null && rootWindowInsets != null) {
listener.onChange(visible);
}
} finally {
shouldTranslate = true;
}
}
public void setShouldTranslate(final boolean shouldTranslate) {
this.shouldTranslate = shouldTranslate;
}
public void setKbVisibilityListener(final onKbVisibilityChangeListener listener) {
this.listener = listener;
}
public interface onKbVisibilityChangeListener {
void onChange(boolean isVisible);
}
}

View File

@ -0,0 +1,139 @@
package awais.instagrabber.customviews.helpers;/*
* Copyright 2020 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.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.graphics.Insets;
import androidx.core.view.OnApplyWindowInsetsListener;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsAnimationCompat;
import androidx.core.view.WindowInsetsCompat;
import java.util.List;
/**
* A class which extends/implements both [WindowInsetsAnimationCompat.Callback] and
* [View.OnApplyWindowInsetsListener], which should be set on the root view in your layout.
* <p>
* This class enables the root view is selectively defer handling any insets which match
* [deferredInsetTypes], to enable better looking [WindowInsetsAnimationCompat]s.
* <p>
* An example is the following: when a [WindowInsetsAnimationCompat] is started, the system will dispatch
* a [WindowInsetsCompat] instance which contains the end state of the animation. For the scenario of
* the IME being animated in, that means that the insets contains the IME height. If the view's
* [View.OnApplyWindowInsetsListener] simply always applied the combination of
* [WindowInsetsCompat.Type.ime] and [WindowInsetsCompat.Type.systemBars] using padding, the viewport of any
* child views would then be smaller. This results in us animating a smaller (padded-in) view into
* a larger viewport. Visually, this results in the views looking clipped.
* <p>
* This class allows us to implement a different strategy for the above scenario, by selectively
* deferring the [WindowInsetsCompat.Type.ime] insets until the [WindowInsetsAnimationCompat] is ended.
* For the above example, you would create a [RootViewDeferringInsetsCallback] like so:
* <p>
* ```
* val callback = RootViewDeferringInsetsCallback(
* persistentInsetTypes = WindowInsetsCompat.Type.systemBars(),
* deferredInsetTypes = WindowInsetsCompat.Type.ime()
* )
* ```
* <p>
* This class is not limited to just IME animations, and can work with any [WindowInsetsCompat.Type]s.
*/
public class RootViewDeferringInsetsCallback extends WindowInsetsAnimationCompat.Callback implements OnApplyWindowInsetsListener {
private final int persistentInsetTypes;
private final int deferredInsetTypes;
@Nullable
private View view = null;
@Nullable
private WindowInsetsCompat lastWindowInsets = null;
private boolean deferredInsets = false;
/**
* @param persistentInsetTypes the bitmask of any inset types which should always be handled
* through padding the attached view
* @param deferredInsetTypes the bitmask of insets types which should be deferred until after
* any related [WindowInsetsAnimationCompat]s have ended
*/
public RootViewDeferringInsetsCallback(final int persistentInsetTypes, final int deferredInsetTypes) {
super(DISPATCH_MODE_CONTINUE_ON_SUBTREE);
if ((persistentInsetTypes & deferredInsetTypes) != 0) {
throw new IllegalArgumentException("persistentInsetTypes and deferredInsetTypes can not contain " +
"any of same WindowInsetsCompat.Type values");
}
this.persistentInsetTypes = persistentInsetTypes;
this.deferredInsetTypes = deferredInsetTypes;
}
@Override
public WindowInsetsCompat onApplyWindowInsets(@NonNull final View v, @NonNull final WindowInsetsCompat windowInsets) {
// Store the view and insets for us in onEnd() below
view = v;
lastWindowInsets = windowInsets;
final int types = deferredInsets
// When the deferred flag is enabled, we only use the systemBars() insets
? persistentInsetTypes
// Otherwise we handle the combination of the the systemBars() and ime() insets
: persistentInsetTypes | deferredInsetTypes;
// Finally we apply the resolved insets by setting them as padding
final Insets typeInsets = windowInsets.getInsets(types);
v.setPadding(typeInsets.left, typeInsets.top, typeInsets.right, typeInsets.bottom);
// We return the new WindowInsetsCompat.CONSUMED to stop the insets being dispatched any
// further into the view hierarchy. This replaces the deprecated
// WindowInsetsCompat.consumeSystemWindowInsets() and related functions.
return WindowInsetsCompat.CONSUMED;
}
@Override
public void onPrepare(WindowInsetsAnimationCompat animation) {
if ((animation.getTypeMask() & deferredInsetTypes) != 0) {
// We defer the WindowInsetsCompat.Type.ime() insets if the IME is currently not visible.
// This results in only the WindowInsetsCompat.Type.systemBars() being applied, allowing
// the scrolling view to remain at it's larger size.
deferredInsets = true;
}
}
@NonNull
@Override
public WindowInsetsCompat onProgress(@NonNull final WindowInsetsCompat insets,
@NonNull final List<WindowInsetsAnimationCompat> runningAnims) {
// This is a no-op. We don't actually want to handle any WindowInsetsAnimations
return insets;
}
@Override
public void onEnd(@NonNull final WindowInsetsAnimationCompat animation) {
if (deferredInsets && (animation.getTypeMask() & deferredInsetTypes) != 0) {
// If we deferred the IME insets and an IME animation has finished, we need to reset
// the flag
deferredInsets = false;
// And finally dispatch the deferred insets to the view now.
// Ideally we would just call view.requestApplyInsets() and let the normal dispatch
// cycle happen, but this happens too late resulting in a visual flicker.
// Instead we manually dispatch the most recent WindowInsets to the view.
if (lastWindowInsets != null && view != null) {
ViewCompat.dispatchApplyWindowInsets(view, lastWindowInsets);
}
}
}
}

View File

@ -0,0 +1,443 @@
/*
* Copyright 2020 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.
*/
package awais.instagrabber.customviews.helpers;
import android.os.CancellationSignal;
import android.util.Log;
import android.view.View;
import android.view.animation.LinearInterpolator;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsAnimationControlListenerCompat;
import androidx.core.view.WindowInsetsAnimationControllerCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.WindowInsetsControllerCompat;
import androidx.dynamicanimation.animation.FloatPropertyCompat;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
import awais.instagrabber.utils.ViewUtils;
/**
* A wrapper around the [WindowInsetsAnimationControllerCompat] APIs in AndroidX Core, to simplify
* the implementation of common use-cases around the IME.
* <p>
* See [InsetsAnimationLinearLayout] and [InsetsAnimationTouchListener] for examples of how
* to use this class.
*/
public class SimpleImeAnimationController {
private static final String TAG = SimpleImeAnimationController.class.getSimpleName();
/**
* Scroll threshold for determining whether to animating to the end state, or to the start state.
* Currently 15% of the total swipe distance distance
*/
private static final float SCROLL_THRESHOLD = 0.15f;
@Nullable
private WindowInsetsAnimationControllerCompat insetsAnimationController = null;
@Nullable
private CancellationSignal pendingRequestCancellationSignal = null;
@Nullable
private OnRequestReadyListener pendingRequestOnReadyListener;
/**
* True if the IME was shown at the start of the current animation.
*/
private boolean isImeShownAtStart = false;
@Nullable
private SpringAnimation currentSpringAnimation = null;
private WindowInsetsAnimationControlListenerCompat fwdListener;
/**
* A LinearInterpolator instance we can re-use across listeners.
*/
private final LinearInterpolator linearInterpolator = new LinearInterpolator();
/* To take control of the an WindowInsetsAnimation, we need to pass in a listener to
controlWindowInsetsAnimation() in startControlRequest(). The listener created here
keeps track of the current WindowInsetsAnimationController and resets our state. */
private final WindowInsetsAnimationControlListenerCompat animationControlListener = new WindowInsetsAnimationControlListenerCompat() {
/**
* Once the request is ready, call our [onRequestReady] function
*/
@Override
public void onReady(@NonNull final WindowInsetsAnimationControllerCompat controller, final int types) {
onRequestReady(controller);
if (fwdListener != null) {
fwdListener.onReady(controller, types);
}
}
/**
* If the request is finished, we should reset our internal state
*/
@Override
public void onFinished(@NonNull final WindowInsetsAnimationControllerCompat controller) {
reset();
if (fwdListener != null) {
fwdListener.onFinished(controller);
}
}
/**
* If the request is cancelled, we should reset our internal state
*/
@Override
public void onCancelled(@Nullable final WindowInsetsAnimationControllerCompat controller) {
reset();
if (fwdListener != null) {
fwdListener.onCancelled(controller);
}
}
};
/**
* Start a control request to the [view]s [android.view.WindowInsetsController]. This should
* be called once the view is in a position to take control over the position of the IME.
*
* @param view The view which is triggering this request
* @param onRequestReadyListener optional listener which will be called when the request is ready and
* the animation can proceed
*/
public void startControlRequest(@NonNull final View view,
@Nullable final OnRequestReadyListener onRequestReadyListener) {
if (isInsetAnimationInProgress()) {
Log.w(TAG, "startControlRequest: Animation in progress. Can not start a new request to controlWindowInsetsAnimation()");
return;
}
// Keep track of the IME insets, and the IME visibility, at the start of the request
final WindowInsetsCompat rootWindowInsets = ViewCompat.getRootWindowInsets(view);
if (rootWindowInsets != null) {
isImeShownAtStart = rootWindowInsets.isVisible(WindowInsetsCompat.Type.ime());
}
// Create a cancellation signal, which we pass to controlWindowInsetsAnimation() below
pendingRequestCancellationSignal = new CancellationSignal();
// Keep reference to the onReady callback
pendingRequestOnReadyListener = onRequestReadyListener;
// Finally we make a controlWindowInsetsAnimation() request:
final WindowInsetsControllerCompat windowInsetsController = ViewCompat.getWindowInsetsController(view);
if (windowInsetsController != null) {
windowInsetsController.controlWindowInsetsAnimation(
// We're only catering for IME animations in this listener
WindowInsetsCompat.Type.ime(),
// Animation duration. This is not used by the system, and is only passed to any
// WindowInsetsAnimation.Callback set on views. We pass in -1 to indicate that we're
// not starting a finite animation, and that this is completely controlled by
// the user's touch.
-1,
// The time interpolator used in calculating the animation progress. The fraction value
// we passed into setInsetsAndAlpha() which be passed into this interpolator before
// being used by the system to inset the IME. LinearInterpolator is a good type
// to use for scrolling gestures.
linearInterpolator,
// A cancellation signal, which allows us to cancel the request to control
pendingRequestCancellationSignal,
// The WindowInsetsAnimationControlListener
animationControlListener
);
}
}
/**
* Start a control request to the [view]s [android.view.WindowInsetsController], similar to
* [startControlRequest], but immediately fling to a finish using [velocityY] once ready.
* <p>
* This function is useful for fire-and-forget operations to animate the IME.
*
* @param view The view which is triggering this request
* @param velocityY the velocity of the touch gesture which caused this call
*/
public void startAndFling(@NonNull final View view, final float velocityY) {
startControlRequest(view, null);
animateToFinish(velocityY);
}
/**
* Update the inset position of the IME by the given [dy] value. This value will be coerced
* into the hidden and shown inset values.
* <p>
* This function should only be called if [isInsetAnimationInProgress] returns true.
*
* @return the amount of [dy] consumed by the inset animation, in pixels
*/
public int insetBy(final int dy) {
if (insetsAnimationController == null) {
throw new IllegalStateException("Current WindowInsetsAnimationController is null." +
"This should only be called if isAnimationInProgress() returns true");
}
final WindowInsetsAnimationControllerCompat controller = insetsAnimationController;
// Call updateInsetTo() with the new inset value
return insetTo(controller.getCurrentInsets().bottom - dy);
}
/**
* Update the inset position of the IME to be the given [inset] value. This value will be
* coerced into the hidden and shown inset values.
* <p>
* This function should only be called if [isInsetAnimationInProgress] returns true.
*
* @return the distance moved by the inset animation, in pixels
*/
public int insetTo(final int inset) {
if (insetsAnimationController == null) {
throw new IllegalStateException("Current WindowInsetsAnimationController is null." +
"This should only be called if isAnimationInProgress() returns true");
}
final WindowInsetsAnimationControllerCompat controller = insetsAnimationController;
final int hiddenBottom = controller.getHiddenStateInsets().bottom;
final int shownBottom = controller.getShownStateInsets().bottom;
final int startBottom = isImeShownAtStart ? shownBottom : hiddenBottom;
final int endBottom = isImeShownAtStart ? hiddenBottom : shownBottom;
// We coerce the given inset within the limits of the hidden and shown insets
final int coercedBottom = coerceIn(inset, hiddenBottom, shownBottom);
final int consumedDy = controller.getCurrentInsets().bottom - coercedBottom;
// Finally update the insets in the WindowInsetsAnimationController using
// setInsetsAndAlpha().
controller.setInsetsAndAlpha(
// Here we update the animating insets. This is what controls where the IME is displayed.
// It is also passed through to views via their WindowInsetsAnimation.Callback.
Insets.of(0, 0, 0, coercedBottom),
// This controls the alpha value. We don't want to alter the alpha so use 1f
1f,
// Finally we calculate the animation progress fraction. This value is passed through
// to any WindowInsetsAnimation.Callbacks, but it is not used by the system.
(coercedBottom - startBottom) / (float) (endBottom - startBottom)
);
return consumedDy;
}
/**
* Return `true` if an inset animation is in progress.
*/
public boolean isInsetAnimationInProgress() {
return insetsAnimationController != null;
}
/**
* Return `true` if an inset animation is currently finishing.
*/
public boolean isInsetAnimationFinishing() {
return currentSpringAnimation != null;
}
/**
* Return `true` if a request to control an inset animation is in progress.
*/
public boolean isInsetAnimationRequestPending() {
return pendingRequestCancellationSignal != null;
}
/**
* Cancel the current [WindowInsetsAnimationControllerCompat]. We immediately finish
* the animation, reverting back to the state at the start of the gesture.
*/
public void cancel() {
if (insetsAnimationController != null) {
insetsAnimationController.finish(isImeShownAtStart);
}
if (pendingRequestCancellationSignal != null) {
pendingRequestCancellationSignal.cancel();
}
if (currentSpringAnimation != null) {
// Cancel the current spring animation
currentSpringAnimation.cancel();
}
reset();
}
/**
* Finish the current [WindowInsetsAnimationControllerCompat] immediately.
*/
public void finish() {
final WindowInsetsAnimationControllerCompat controller = insetsAnimationController;
if (controller == null) {
// If we don't currently have a controller, cancel any pending request and return
if (pendingRequestCancellationSignal != null) {
pendingRequestCancellationSignal.cancel();
}
return;
}
final int current = controller.getCurrentInsets().bottom;
final int shown = controller.getShownStateInsets().bottom;
final int hidden = controller.getHiddenStateInsets().bottom;
// The current inset matches either the shown/hidden inset, finish() immediately
if (current == shown) {
controller.finish(true);
} else if (current == hidden) {
controller.finish(false);
} else {
// Otherwise, we'll look at the current position...
if (controller.getCurrentFraction() >= SCROLL_THRESHOLD) {
// If the IME is past the 'threshold' we snap to the toggled state
controller.finish(!isImeShownAtStart);
} else {
// ...otherwise, we snap back to the original visibility
controller.finish(isImeShownAtStart);
}
}
}
/**
* Finish the current [WindowInsetsAnimationControllerCompat]. We finish the animation,
* animating to the end state if necessary.
*
* @param velocityY the velocity of the touch gesture which caused this call to [animateToFinish].
* Can be `null` if velocity is not available.
*/
public void animateToFinish(@Nullable final Float velocityY) {
final WindowInsetsAnimationControllerCompat controller = insetsAnimationController;
if (controller == null) {
// If we don't currently have a controller, cancel any pending request and return
if (pendingRequestCancellationSignal != null) {
pendingRequestCancellationSignal.cancel();
}
return;
}
final int current = controller.getCurrentInsets().bottom;
final int shown = controller.getShownStateInsets().bottom;
final int hidden = controller.getHiddenStateInsets().bottom;
if (velocityY != null) {
// If we have a velocity, we can use it's direction to determine
// the visibility. Upwards == visible
animateImeToVisibility(velocityY > 0, velocityY);
} else if (current == shown) {
// The current inset matches either the shown/hidden inset, finish() immediately
controller.finish(true);
} else if (current == hidden) {
controller.finish(false);
} else {
// Otherwise, we'll look at the current position...
if (controller.getCurrentFraction() >= SCROLL_THRESHOLD) {
// If the IME is past the 'threshold' we animate it to the toggled state
animateImeToVisibility(!isImeShownAtStart, null);
} else {
// ...otherwise, we animate it back to the original visibility
animateImeToVisibility(isImeShownAtStart, null);
}
}
}
private void onRequestReady(@NonNull final WindowInsetsAnimationControllerCompat controller) {
// The request is ready, so clear out the pending cancellation signal
pendingRequestCancellationSignal = null;
// Store the current WindowInsetsAnimationController
insetsAnimationController = controller;
// Call any pending callback
if (pendingRequestOnReadyListener != null) {
pendingRequestOnReadyListener.onRequestReady(controller);
}
pendingRequestOnReadyListener = null;
}
/**
* Resets all of our internal state.
*/
private void reset() {
// Clear all of our internal state
insetsAnimationController = null;
pendingRequestCancellationSignal = null;
isImeShownAtStart = false;
if (currentSpringAnimation != null) {
currentSpringAnimation.cancel();
}
currentSpringAnimation = null;
pendingRequestOnReadyListener = null;
}
/**
* Animate the IME to a given visibility.
*
* @param visible `true` to animate the IME to it's fully shown state, `false` to it's
* fully hidden state.
* @param velocityY the velocity of the touch gesture which caused this call. Can be `null`
* if velocity is not available.
*/
private void animateImeToVisibility(final boolean visible, @Nullable final Float velocityY) {
if (insetsAnimationController == null) {
throw new IllegalStateException("Controller should not be null");
}
final WindowInsetsAnimationControllerCompat controller = insetsAnimationController;
final FloatPropertyCompat<Object> property = new FloatPropertyCompat<Object>("property") {
@Override
public float getValue(final Object object) {
return controller.getCurrentInsets().bottom;
}
@Override
public void setValue(final Object object, final float value) {
if (insetsAnimationController == null) {
return;
}
insetTo((int) value);
}
};
final float finalPosition = visible ? controller.getShownStateInsets().bottom
: controller.getHiddenStateInsets().bottom;
final SpringForce force = new SpringForce(finalPosition)
// Tweak the damping value, to remove any bounciness.
.setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)
// The stiffness value controls the strength of the spring animation, which
// controls the speed. Medium (the default) is a good value, but feel free to
// play around with this value.
.setStiffness(SpringForce.STIFFNESS_MEDIUM);
ViewUtils.springAnimationOf(this, property, finalPosition)
.setSpring(force)
.setStartVelocity(velocityY != null ? velocityY : 0)
.addEndListener((animation, canceled, value, velocity) -> {
if (animation == currentSpringAnimation) {
currentSpringAnimation = null;
}
// Once the animation has ended, finish the controller
finish();
}).start();
}
private int coerceIn(final int v, final int min, final int max) {
if (v >= min && v <= max) {
return v;
}
if (v < min) {
return min;
}
return max;
}
public void setAnimationControlListener(final WindowInsetsAnimationControlListenerCompat listener) {
fwdListener = listener;
}
public interface OnRequestReadyListener {
void onRequestReady(WindowInsetsAnimationControllerCompat windowInsetsAnimationControllerCompat);
}
}

View File

@ -0,0 +1,128 @@
/*
* Copyright 2020 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.
*/
package awais.instagrabber.customviews.helpers;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsAnimationCompat;
import androidx.core.view.WindowInsetsCompat;
import java.util.List;
/**
* A [WindowInsetsAnimationCompat.Callback] which will translate/move the given view during any
* inset animations of the given inset type.
* <p>
* This class works in tandem with [RootViewDeferringInsetsCallback] to support the deferring of
* certain [WindowInsetsCompat.Type] values during a [WindowInsetsAnimationCompat], provided in
* [deferredInsetTypes]. The values passed into this constructor should match those which
* the [RootViewDeferringInsetsCallback] is created with.
*/
public class TranslateDeferringInsetsAnimationCallback extends WindowInsetsAnimationCompat.Callback {
private final View view;
private final int persistentInsetTypes;
private final int deferredInsetTypes;
private boolean shouldTranslate = true;
private int kbHeight;
public TranslateDeferringInsetsAnimationCallback(final View view,
final int persistentInsetTypes,
final int deferredInsetTypes) {
this(view, persistentInsetTypes, deferredInsetTypes, DISPATCH_MODE_STOP);
}
/**
* @param view the view to translate from it's start to end state
* @param persistentInsetTypes the bitmask of any inset types which were handled as part of the
* layout
* @param deferredInsetTypes the bitmask of insets types which should be deferred until after
* any [WindowInsetsAnimationCompat]s have ended
* @param dispatchMode The dispatch mode for this callback.
* See [WindowInsetsAnimationCompat.Callback.getDispatchMode].
*/
public TranslateDeferringInsetsAnimationCallback(final View view,
final int persistentInsetTypes,
final int deferredInsetTypes,
final int dispatchMode) {
super(dispatchMode);
if ((persistentInsetTypes & deferredInsetTypes) != 0) {
throw new IllegalArgumentException("persistentInsetTypes and deferredInsetTypes can not contain " +
"any of same WindowInsetsCompat.Type values");
}
this.view = view;
this.persistentInsetTypes = persistentInsetTypes;
this.deferredInsetTypes = deferredInsetTypes;
}
@NonNull
@Override
public WindowInsetsCompat onProgress(@NonNull final WindowInsetsCompat insets,
@NonNull final List<WindowInsetsAnimationCompat> runningAnimations) {
// onProgress() is called when any of the running animations progress...
// First we get the insets which are potentially deferred
final Insets typesInset = insets.getInsets(deferredInsetTypes);
// Then we get the persistent inset types which are applied as padding during layout
final Insets otherInset = insets.getInsets(persistentInsetTypes);
// Now that we subtract the two insets, to calculate the difference. We also coerce
// the insets to be >= 0, to make sure we don't use negative insets.
final Insets subtract = Insets.subtract(typesInset, otherInset);
final Insets diff = Insets.max(subtract, Insets.NONE);
// The resulting `diff` insets contain the values for us to apply as a translation
// to the view
view.setTranslationX(diff.left - diff.right);
view.setTranslationY(shouldTranslate ? diff.top - diff.bottom : -kbHeight);
return insets;
}
@Override
public void onEnd(@NonNull final WindowInsetsAnimationCompat animation) {
try {
final WindowInsetsCompat rootWindowInsets = ViewCompat.getRootWindowInsets(view);
if (kbHeight == 0) {
if (rootWindowInsets == null) return;
final Insets imeInsets = rootWindowInsets.getInsets(WindowInsetsCompat.Type.ime());
final Insets navBarInsets = rootWindowInsets.getInsets(WindowInsetsCompat.Type.navigationBars());
kbHeight = imeInsets.bottom - navBarInsets.bottom;
}
// Once the animation has ended, reset the translation values
view.setTranslationX(0f);
final boolean visible = rootWindowInsets != null && rootWindowInsets.isVisible(WindowInsetsCompat.Type.ime());
float translationY = 0;
if (!shouldTranslate) {
translationY = -kbHeight;
if (visible) {
translationY = 0;
}
}
view.setTranslationY(translationY);
} finally {
shouldTranslate = true;
}
}
public void setShouldTranslate(final boolean shouldTranslate) {
this.shouldTranslate = shouldTranslate;
}
}

View File

@ -2,6 +2,7 @@ package awais.instagrabber.fragments;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -102,8 +103,13 @@ public class FavoritesFragment extends Fragment {
// Log.d(TAG, "locationId: " + locationId);
final NavController navController = NavHostFragment.findNavController(this);
final Bundle bundle = new Bundle();
bundle.putString("locationId", locationId);
navController.navigate(R.id.action_global_locationFragment, bundle);
try {
bundle.putLong("locationId", Long.parseLong(locationId));
navController.navigate(R.id.action_global_locationFragment, bundle);
} catch (Exception e) {
Log.e(TAG, "init: ", e);
return;
}
break;
}
case HASHTAG: {

View File

@ -18,6 +18,7 @@ import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.Toast;
import androidx.annotation.NonNull;
@ -29,6 +30,7 @@ import androidx.appcompat.widget.PopupMenu;
import androidx.appcompat.widget.Toolbar;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.PermissionChecker;
import androidx.core.view.WindowCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.LiveData;
@ -100,7 +102,6 @@ import static androidx.core.content.PermissionChecker.checkSelfPermission;
import static awais.instagrabber.fragments.HashTagFragment.ARG_HASHTAG;
import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_SHOWN_COUNT_TOOLTIP;
import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
import static awais.instagrabber.utils.Utils.getAttrValue;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class PostViewV2Fragment extends Fragment implements EditTextDialogFragment.EditTextDialogFragmentCallback {
@ -131,6 +132,9 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme
private boolean isInFullScreenMode;
private StyledPlayerView playerView;
private int playerViewOriginalHeight;
private Drawable originalRootBackground;
private ColorStateList originalLikeColorStateList;
private ColorStateList originalSaveColorStateList;
private final Observer<Object> backStackSavedStateObserver = result -> {
if (result == null) return;
@ -141,7 +145,6 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme
// clear result
backStackSavedStateResultLiveData.postValue(null);
};
private Drawable originalRootBackground;
public void setOnDeleteListener(final OnDeleteListener onDeleteListener) {
if (onDeleteListener == null) return;
@ -441,6 +444,7 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme
}
private void setupLike() {
originalLikeColorStateList = bottom.like.getIconTint();
final boolean likableMedia = viewModel.hasPk() /*&& viewModel.getMedia().isCommentLikesEnabled()*/;
if (!likableMedia) {
bottom.like.setVisibility(View.GONE);
@ -503,25 +507,25 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme
private void setLikedResources(final boolean liked) {
final int iconResource;
final int tintResource;
final ColorStateList tintColorStateList;
final Context context = getContext();
if (context == null) return;
final Resources resources = context.getResources();
if (resources == null) return;
if (liked) {
iconResource = R.drawable.ic_like;
tintResource = resources.getColor(R.color.red_600);
// textResId = R.string.unlike_without_count;
tintColorStateList = ColorStateList.valueOf(resources.getColor(R.color.red_600));
} else {
iconResource = R.drawable.ic_not_liked;
tintResource = getAttrValue(context, R.attr.colorPrimary);
// textResId = R.string.like_without_count;
tintColorStateList = originalLikeColorStateList != null ? originalLikeColorStateList
: ColorStateList.valueOf(resources.getColor(R.color.white));
}
bottom.like.setIconResource(iconResource);
bottom.like.setIconTint(ColorStateList.valueOf(tintResource));
bottom.like.setIconTint(tintColorStateList);
}
private void setupSave() {
originalSaveColorStateList = bottom.save.getIconTint();
if (!viewModel.isLoggedIn() || !viewModel.hasPk() || !viewModel.getMedia().canViewerSave()) {
bottom.save.setVisibility(View.GONE);
return;
@ -574,22 +578,21 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme
private void setSavedResources(final boolean saved) {
final int iconResource;
final int tintResource;
final ColorStateList tintColorStateList;
final Context context = getContext();
if (context == null) return;
final Resources resources = context.getResources();
if (resources == null) return;
if (saved) {
iconResource = R.drawable.ic_bookmark;
tintResource = resources.getColor(R.color.blue_700);
// textResId = R.string.saved;
tintColorStateList = ColorStateList.valueOf(resources.getColor(R.color.blue_700));
} else {
iconResource = R.drawable.ic_round_bookmark_border_24;
tintResource = getAttrValue(context, R.attr.colorPrimary);
// textResId = R.string.save;
tintColorStateList = originalSaveColorStateList != null ? originalSaveColorStateList
: ColorStateList.valueOf(resources.getColor(R.color.white));
}
bottom.save.setIconResource(iconResource);
bottom.save.setIconTint(ColorStateList.valueOf(tintResource));
bottom.save.setIconTint(tintColorStateList);
}
private void setupProfilePic(final User user) {
@ -1427,8 +1430,10 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme
if (toolbar != null) {
toolbar.setVisibility(View.VISIBLE);
}
final View decorView = activity.getWindow().getDecorView();
final Window window = activity.getWindow();
final View decorView = window.getDecorView();
decorView.setSystemUiVisibility(originalSystemUi);
WindowCompat.setDecorFitsSystemWindows(window, false);
isInFullScreenMode = false;
}

View File

@ -23,9 +23,12 @@ import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import awais.instagrabber.R;
import awais.instagrabber.adapters.FeedStoriesListAdapter;
@ -58,15 +61,17 @@ public final class StoryListViewerFragment extends Fragment implements SwipeRefr
private StoriesService storiesService;
private Context context;
private String type;
private String currentQuery;
private String endCursor = null;
private FeedStoriesListAdapter adapter;
private MenuItem menuSearch;
private final OnFeedStoryClickListener clickListener = new OnFeedStoryClickListener() {
@Override
public void onFeedStoryClick(final FeedStoryModel model, final int position) {
public void onFeedStoryClick(final FeedStoryModel model) {
if (model == null) return;
final List<FeedStoryModel> feedStoryModels = feedStoriesViewModel.getList().getValue();
if (feedStoryModels == null) return;
final int position = Iterables.indexOf(feedStoryModels, feedStoryModel -> feedStoryModel != null
&& Objects.equals(feedStoryModel.getStoryMediaId(), model.getStoryMediaId()));
final NavDirections action = StoryListViewerFragmentDirections
.actionStoryListFragmentToStoryViewerFragment(StoryViewerOptions.forFeedStoryPosition(position));
NavHostFragment.findNavController(StoryListViewerFragment.this).navigate(action);
@ -153,7 +158,7 @@ public final class StoryListViewerFragment extends Fragment implements SwipeRefr
@Override
public void onCreateOptionsMenu(@NonNull final Menu menu, final MenuInflater inflater) {
inflater.inflate(R.menu.search, menu);
menuSearch = menu.findItem(R.id.action_search);
final MenuItem menuSearch = menu.findItem(R.id.action_search);
final SearchView searchView = (SearchView) menuSearch.getActionView();
searchView.setQueryHint(getResources().getString(R.string.action_search));
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@ -166,7 +171,6 @@ public final class StoryListViewerFragment extends Fragment implements SwipeRefr
@Override
public boolean onQueryTextChange(final String query) {
if (adapter != null) {
currentQuery = query;
adapter.getFilter().filter(query);
}
return true;

View File

@ -120,6 +120,7 @@ public class StoryViewerFragment extends Fragment {
private View root;
private FragmentStoryViewerBinding binding;
private String currentStoryUsername;
private String highlightTitle;
private StoriesAdapter storiesAdapter;
private SwipeEvent swipeEvent;
private GestureDetectorCompat gestureDetector;
@ -275,7 +276,9 @@ public class StoryViewerFragment extends Fragment {
@Override
public void onPause() {
super.onPause();
releasePlayer();
if (player != null) {
player.pause();
}
}
@Override
@ -749,7 +752,7 @@ public class StoryViewerFragment extends Fragment {
final HighlightModel model = models.get(currentFeedStoryIndex);
currentStoryMediaId = model.getId();
fetchOptions = StoryViewerOptions.forHighlight(model.getId());
currentStoryUsername = model.getTitle();
highlightTitle = model.getTitle();
break;
}
case FEED_STORY_POSITION: {
@ -849,8 +852,8 @@ public class StoryViewerFragment extends Fragment {
if (type == Type.HIGHLIGHT) {
final ActionBar actionBar = fragmentActivity.getSupportActionBar();
if (actionBar != null) {
actionBarTitle = options.getName();
actionBar.setTitle(options.getName());
actionBarTitle = highlightTitle;
actionBar.setTitle(highlightTitle);
}
} else if (hasUsername) {
currentStoryUsername = currentStoryUsername.replace("@", "");

View File

@ -16,6 +16,7 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.view.menu.ActionMenuItemView;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
@ -27,6 +28,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.badge.BadgeDrawable;
import com.google.android.material.badge.BadgeUtils;
import com.google.android.material.internal.ToolbarUtils;
import com.google.android.material.snackbar.Snackbar;
import java.util.List;
@ -102,7 +104,9 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
super.onPause();
unregisterReceiver();
isPendingRequestTotalBadgeAttached = false;
if (pendingRequestTotalBadgeDrawable != null) {
@SuppressLint("RestrictedApi") final ActionMenuItemView menuItemView = ToolbarUtils
.getActionMenuItemView(fragmentActivity.getToolbar(), pendingRequestsMenuItem.getItemId());
if (pendingRequestTotalBadgeDrawable != null && menuItemView != null) {
BadgeUtils.detachBadgeDrawable(pendingRequestTotalBadgeDrawable, fragmentActivity.getToolbar(), pendingRequestsMenuItem.getItemId());
pendingRequestTotalBadgeDrawable = null;
}
@ -217,7 +221,11 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
pendingRequestTotalBadgeDrawable = BadgeDrawable.create(context);
}
if (count == null || count == 0) {
BadgeUtils.detachBadgeDrawable(pendingRequestTotalBadgeDrawable, fragmentActivity.getToolbar(), pendingRequestsMenuItem.getItemId());
@SuppressLint("RestrictedApi") final ActionMenuItemView menuItemView = ToolbarUtils
.getActionMenuItemView(fragmentActivity.getToolbar(), pendingRequestsMenuItem.getItemId());
if (menuItemView != null) {
BadgeUtils.detachBadgeDrawable(pendingRequestTotalBadgeDrawable, fragmentActivity.getToolbar(), pendingRequestsMenuItem.getItemId());
}
isPendingRequestTotalBadgeAttached = false;
pendingRequestTotalBadgeDrawable.setNumber(0);
pendingRequestsMenuItem.setVisible(false);

View File

@ -15,6 +15,7 @@ import android.net.Uri;
import android.os.Bundle;
import android.text.Editable;
import android.util.Log;
import android.util.Pair;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
@ -25,11 +26,18 @@ import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.appcompat.view.menu.ActionMenuItemView;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsAnimationCompat;
import androidx.core.view.WindowInsetsAnimationControlListenerCompat;
import androidx.core.view.WindowInsetsAnimationControllerCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
@ -49,6 +57,7 @@ import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
import com.google.android.material.badge.BadgeDrawable;
import com.google.android.material.badge.BadgeUtils;
import com.google.android.material.internal.ToolbarUtils;
import com.google.android.material.snackbar.Snackbar;
import com.google.common.collect.ImmutableList;
@ -70,16 +79,21 @@ import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemOrHeader;
import awais.instagrabber.adapters.DirectReactionsAdapter;
import awais.instagrabber.adapters.viewholder.directmessages.DirectItemViewHolder;
import awais.instagrabber.animations.CubicBezierInterpolator;
import awais.instagrabber.customviews.InsetsAnimationLinearLayout;
import awais.instagrabber.customviews.KeyNotifyingEmojiEditText;
import awais.instagrabber.customviews.RecordView;
import awais.instagrabber.customviews.Tooltip;
import awais.instagrabber.customviews.emoji.Emoji;
import awais.instagrabber.customviews.emoji.EmojiBottomSheetDialog;
import awais.instagrabber.customviews.emoji.EmojiPicker;
import awais.instagrabber.customviews.helpers.ControlFocusInsetsAnimationCallback;
import awais.instagrabber.customviews.helpers.EmojiPickerInsetsAnimationCallback;
import awais.instagrabber.customviews.helpers.HeaderItemDecoration;
import awais.instagrabber.customviews.helpers.HeightProvider;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoaderAtEdge;
import awais.instagrabber.customviews.helpers.SimpleImeAnimationController;
import awais.instagrabber.customviews.helpers.SwipeAndRestoreItemTouchHelperCallback;
import awais.instagrabber.customviews.helpers.TextWatcherAdapter;
import awais.instagrabber.customviews.helpers.TranslateDeferringInsetsAnimationCallback;
import awais.instagrabber.databinding.FragmentDirectMessagesThreadBinding;
import awais.instagrabber.dialogs.DirectItemReactionDialogFragment;
import awais.instagrabber.dialogs.GifPickerBottomDialogFragment;
@ -111,9 +125,6 @@ import awais.instagrabber.viewmodels.AppStateViewModel;
import awais.instagrabber.viewmodels.DirectThreadViewModel;
import awais.instagrabber.viewmodels.factories.DirectThreadViewModelFactory;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
public class DirectMessageThreadFragment extends Fragment implements DirectReactionsAdapter.OnReactionClickListener,
EmojiPicker.OnEmojiClickListener {
private static final String TAG = DirectMessageThreadFragment.class.getSimpleName();
@ -125,7 +136,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
private DirectItemsAdapter itemsAdapter;
private MainActivity fragmentActivity;
private DirectThreadViewModel viewModel;
private ConstraintLayout root;
private InsetsAnimationLinearLayout root;
private boolean shouldRefresh = true;
private List<DirectItemOrHeader> itemOrHeaders;
private List<User> users;
@ -135,14 +146,8 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
private ActionBar actionBar;
private AppStateViewModel appStateViewModel;
private Runnable prevTitleRunnable;
private int originalSoftInputMode;
private AnimatorSet animatorSet;
private boolean isEmojiPickerShown;
private boolean isKbShown;
private HeightProvider heightProvider;
private boolean isRecording;
private boolean wasKbShowing;
private int keyboardHeight = Utils.convertDpToPx(250);
private DirectItemReactionDialogFragment reactionDialogFragment;
private DirectItem itemToForward;
private MutableLiveData<Object> backStackSavedStateResultLiveData;
@ -163,6 +168,11 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
private MenuItem markAsSeenMenuItem;
private Media tempMedia;
private DirectItem addReactionItem;
private TranslateDeferringInsetsAnimationCallback inputHolderAnimationCallback;
private TranslateDeferringInsetsAnimationCallback chatsAnimationCallback;
private EmojiPickerInsetsAnimationCallback emojiPickerAnimationCallback;
private boolean hasKbOpenedOnce;
private boolean wasToggled;
private final AppExecutors appExecutors = AppExecutors.getInstance();
private final Animatable2Compat.AnimationCallback micToSendAnimationCallback = new Animatable2Compat.AnimationCallback() {
@ -304,7 +314,6 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
emojiBottomSheetDialog.show(getChildFragmentManager(), EmojiBottomSheetDialog.TAG);
}
};
private final DirectItemLongClickListener directItemLongClickListener = position -> {
// viewModel.setSelectedPosition(position);
};
@ -333,6 +342,14 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
backStackSavedStateResultLiveData.postValue(null);
};
private final MutableLiveData<Integer> inputLength = new MutableLiveData<>(0);
private final MutableLiveData<Boolean> emojiPickerVisible = new MutableLiveData<>(false);
private final MutableLiveData<Boolean> kbVisible = new MutableLiveData<>(false);
private final OnBackPressedCallback onEmojiPickerBackPressedCallback = new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
emojiPickerVisible.postValue(false);
}
};
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
@ -371,13 +388,13 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
return root;
}
tooltip = new Tooltip(context, root, getResources().getColor(R.color.grey_400), getResources().getColor(R.color.black));
originalSoftInputMode = fragmentActivity.getWindow().getAttributes().softInputMode;
// todo check has camera and remove view
return root;
}
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
// WindowCompat.setDecorFitsSystemWindows(fragmentActivity.getWindow(), false);
if (!shouldRefresh) return;
init();
binding.send.post(() -> initialSendX = binding.send.getX());
@ -490,10 +507,11 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
if (isRecording) {
binding.recordView.cancelRecording(binding.send);
}
if (isKbShown) {
wasKbShowing = true;
binding.emojiPicker.setAlpha(0);
}
emojiPickerVisible.postValue(false);
kbVisible.postValue(false);
binding.inputHolder.setTranslationY(0);
binding.chats.setTranslationY(0);
binding.emojiPicker.setTranslationY(0);
removeObservers();
super.onPause();
}
@ -501,16 +519,12 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
@Override
public void onResume() {
super.onResume();
fragmentActivity.getWindow().setSoftInputMode(SOFT_INPUT_ADJUST_NOTHING | SOFT_INPUT_STATE_HIDDEN);
if (wasKbShowing) {
binding.input.requestFocus();
binding.input.post(this::showKeyboard);
wasKbShowing = false;
}
if (initialSendX != 0) {
binding.send.setX(initialSendX);
}
binding.send.stopScale();
final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher();
onBackPressedDispatcher.addCallback(onEmojiPickerBackPressedCallback);
setupBackStackResultObserver();
setObservers();
// attachPendingRequestsBadge(viewModel.getPendingRequestsCount().getValue());
@ -533,13 +547,6 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
if (prevTitleRunnable != null) {
appExecutors.mainThread().cancel(prevTitleRunnable);
}
if (heightProvider != null) {
// need to close the height provider popup before navigating back to prevent leak
heightProvider.dismiss();
}
if (originalSoftInputMode != 0) {
fragmentActivity.getWindow().setSoftInputMode(originalSoftInputMode);
}
for (int childCount = binding.chats.getChildCount(), i = 0; i < childCount; ++i) {
final RecyclerView.ViewHolder holder = binding.chats.getChildViewHolder(binding.chats.getChildAt(i));
if (holder == null) continue;
@ -549,7 +556,11 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
}
isPendingRequestCountBadgeAttached = false;
if (pendingRequestCountBadgeDrawable != null) {
BadgeUtils.detachBadgeDrawable(pendingRequestCountBadgeDrawable, fragmentActivity.getToolbar(), R.id.info);
@SuppressLint("RestrictedApi") final ActionMenuItemView menuItemView = ToolbarUtils
.getActionMenuItemView(fragmentActivity.getToolbar(), R.id.info);
if (menuItemView != null) {
BadgeUtils.detachBadgeDrawable(pendingRequestCountBadgeDrawable, fragmentActivity.getToolbar(), R.id.info);
}
pendingRequestCountBadgeDrawable = null;
}
}
@ -561,37 +572,8 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
actionBar = fragmentActivity.getSupportActionBar();
setupList();
root.post(this::setupInput);
// root.post(this::getInitialData);
}
// private void getInitialData() {
// final Bundle arguments = getArguments();
// if (arguments == null) return;
// final DirectMessageThreadFragmentArgs args = DirectMessageThreadFragmentArgs.fromBundle(arguments);
// final boolean pending = args.getPending();
// final NavController navController = NavHostFragment.findNavController(this);
// final ViewModelStoreOwner viewModelStoreOwner = navController.getViewModelStoreOwner(R.id.direct_messages_nav_graph);
// final List<DirectThread> threads;
// if (!pending) {
// final DirectInboxViewModel threadListViewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectInboxViewModel.class);
// threads = threadListViewModel.getThreads().getValue();
// } else {
// final DirectPendingInboxViewModel threadListViewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectPendingInboxViewModel.class);
// threads = threadListViewModel.getThreads().getValue();
// }
// final Optional<DirectThread> first = threads != null
// ? threads.stream()
// .filter(thread -> thread.getThreadId().equals(viewModel.getThreadId()))
// .findFirst()
// : Optional.empty();
// if (first.isPresent()) {
// final DirectThread thread = first.get();
// viewModel.setThread(thread);
// return;
// }
// viewModel.fetchChats();
// }
private void setupList() {
final Context context = getContext();
if (context == null) return;
@ -862,7 +844,11 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
pendingRequestCountBadgeDrawable = BadgeDrawable.create(context);
}
if (count == null || count == 0) {
BadgeUtils.detachBadgeDrawable(pendingRequestCountBadgeDrawable, fragmentActivity.getToolbar(), R.id.info);
@SuppressLint("RestrictedApi") final ActionMenuItemView menuItemView = ToolbarUtils
.getActionMenuItemView(fragmentActivity.getToolbar(), R.id.info);
if (menuItemView != null) {
BadgeUtils.detachBadgeDrawable(pendingRequestCountBadgeDrawable, fragmentActivity.getToolbar(), R.id.info);
}
isPendingRequestCountBadgeAttached = false;
pendingRequestCountBadgeDrawable.setNumber(0);
return;
@ -1100,23 +1086,6 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
final int length = s.length();
inputLength.postValue(length);
// boolean showExtraInputOptionsChanged = false;
// if (prevLength != 0 && length == 0) {
// inputLength.postValue(true);
// showExtraInputOptionsChanged = true;
// binding.send.setListenForRecord(true);
// startIconAnimation();
// }
// if (prevLength == 0 && length != 0) {
// inputLength.postValue(false);
// showExtraInputOptionsChanged = true;
// binding.send.setListenForRecord(false);
// startIconAnimation();
// }
// if (!showExtraInputOptionsChanged) {
// showExtraInputOptions.postValue(length == 0);
// }
// prevLength = length;
}
});
binding.send.setOnRecordClickListener(v -> {
@ -1131,30 +1100,15 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
Log.d(TAG, "setOnRecordLongClickListener");
return true;
});
binding.input.setShowSoftInputOnFocus(false);
binding.input.requestFocus();
binding.input.setOnKeyEventListener((keyCode, keyEvent) -> {
if (keyCode != KeyEvent.KEYCODE_BACK) return false;
// We'll close the keyboard/emoji picker only when user releases the back button
// return true so that system doesn't handle the event
if (keyEvent.getAction() != KeyEvent.ACTION_UP) return true;
if (!isKbShown && !isEmojiPickerShown) {
// if both keyboard and emoji picker are hidden, navigate back
if (heightProvider != null) {
// need to close the height provider popup before navigating back to prevent leak
heightProvider.dismiss();
}
NavHostFragment.findNavController(this).navigateUp();
return true;
}
binding.emojiToggle.setIconResource(R.drawable.ic_face_24);
hideKeyboard(true);
return true;
});
binding.input.setOnClickListener(v -> {
if (isKbShown) return;
showKeyboard();
binding.input.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) return;
final Boolean emojiPickerVisibleValue = emojiPickerVisible.getValue();
if (emojiPickerVisibleValue == null || !emojiPickerVisibleValue) return;
inputHolderAnimationCallback.setShouldTranslate(false);
chatsAnimationCallback.setShouldTranslate(false);
emojiPickerAnimationCallback.setShouldTranslate(false);
});
setupInsetsCallback();
setupEmojiPicker();
binding.gallery.setOnClickListener(v -> {
final MediaPickerBottomDialogFragment mediaPicker = MediaPickerBottomDialogFragment.newInstance();
@ -1163,10 +1117,11 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
if (!isAdded()) return;
if (!entry.isVideo) {
navigateToImageEditFragment(entry.path);
return;
}
handleSentMessage(viewModel.sendUri(entry));
});
mediaPicker.show(getChildFragmentManager(), "MediaPicker");
hideKeyboard(true);
});
binding.gif.setOnClickListener(v -> {
final GifPickerBottomDialogFragment gifPicker = GifPickerBottomDialogFragment.newInstance();
@ -1176,7 +1131,6 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
handleSentMessage(viewModel.sendAnimatedMedia(giphyGif));
});
gifPicker.show(getChildFragmentManager(), "GifPicker");
hideKeyboard(true);
});
binding.camera.setOnClickListener(v -> {
final Intent intent = new Intent(context, CameraActivity.class);
@ -1184,6 +1138,73 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
});
}
private void setupInsetsCallback() {
inputHolderAnimationCallback = new TranslateDeferringInsetsAnimationCallback(
binding.inputHolder,
WindowInsetsCompat.Type.systemBars(),
WindowInsetsCompat.Type.ime(),
WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE
);
ViewCompat.setWindowInsetsAnimationCallback(binding.inputHolder, inputHolderAnimationCallback);
chatsAnimationCallback = new TranslateDeferringInsetsAnimationCallback(
binding.chats,
WindowInsetsCompat.Type.systemBars(),
WindowInsetsCompat.Type.ime()
);
ViewCompat.setWindowInsetsAnimationCallback(binding.chats, chatsAnimationCallback);
emojiPickerAnimationCallback = new EmojiPickerInsetsAnimationCallback(
binding.emojiPicker,
WindowInsetsCompat.Type.systemBars(),
WindowInsetsCompat.Type.ime()
);
emojiPickerAnimationCallback.setKbVisibilityListener(this::onKbVisibilityChange);
ViewCompat.setWindowInsetsAnimationCallback(binding.emojiPicker, emojiPickerAnimationCallback);
ViewCompat.setWindowInsetsAnimationCallback(
binding.input,
new ControlFocusInsetsAnimationCallback(binding.input)
);
final SimpleImeAnimationController imeAnimController = root.getImeAnimController();
if (imeAnimController != null) {
imeAnimController.setAnimationControlListener(new WindowInsetsAnimationControlListenerCompat() {
@Override
public void onReady(@NonNull final WindowInsetsAnimationControllerCompat controller, final int types) {}
@Override
public void onFinished(@NonNull final WindowInsetsAnimationControllerCompat controller) {
checkKbVisibility();
}
@Override
public void onCancelled(@Nullable final WindowInsetsAnimationControllerCompat controller) {
checkKbVisibility();
}
private void checkKbVisibility() {
final WindowInsetsCompat rootWindowInsets = ViewCompat.getRootWindowInsets(binding.getRoot());
final boolean visible = rootWindowInsets != null && rootWindowInsets.isVisible(WindowInsetsCompat.Type.ime());
onKbVisibilityChange(visible);
}
});
}
}
private void onKbVisibilityChange(final boolean kbVisible) {
this.kbVisible.postValue(kbVisible);
if (wasToggled) {
emojiPickerVisible.postValue(!kbVisible);
wasToggled = false;
return;
}
final Boolean emojiPickerVisibleValue = emojiPickerVisible.getValue();
if (kbVisible && emojiPickerVisibleValue != null && emojiPickerVisibleValue) {
emojiPickerVisible.postValue(false);
return;
}
if (!kbVisible) {
emojiPickerVisible.postValue(false);
}
}
private void startIconAnimation() {
final Drawable icon = binding.send.getIcon();
if (icon instanceof Animatable) {
@ -1230,15 +1251,87 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
private void setupEmojiPicker() {
root.post(() -> binding.emojiPicker.init(
root,
(view, emoji) -> binding.input.append(emoji.getUnicode()),
(view, emoji) -> {
final KeyNotifyingEmojiEditText input = binding.input;
final int start = input.getSelectionStart();
final int end = input.getSelectionEnd();
if (start < 0) {
input.append(emoji.getUnicode());
return;
}
input.getText().replace(
Math.min(start, end),
Math.max(start, end),
emoji.getUnicode(),
0,
emoji.getUnicode().length()
);
},
() -> binding.input.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
));
setupKbHeightProvider();
if (keyboardHeight == 0) {
keyboardHeight = Utils.convertDpToPx(250);
}
setEmojiPickerBounds();
binding.emojiToggle.setOnClickListener(v -> toggleEmojiPicker());
binding.emojiToggle.setOnClickListener(v -> {
Boolean isEmojiPickerVisible = emojiPickerVisible.getValue();
if (isEmojiPickerVisible == null) isEmojiPickerVisible = false;
Boolean isKbVisible = kbVisible.getValue();
if (isKbVisible == null) isKbVisible = false;
wasToggled = isEmojiPickerVisible || isKbVisible;
if (isEmojiPickerVisible) {
if (hasKbOpenedOnce && binding.emojiPicker.getTranslationY() != 0) {
inputHolderAnimationCallback.setShouldTranslate(false);
chatsAnimationCallback.setShouldTranslate(false);
emojiPickerAnimationCallback.setShouldTranslate(false);
}
// trigger ime.
// Since the kb visibility listener will toggle the emojiPickerVisible live data, we do not explicitly toggle it here
showKeyboard();
return;
}
if (isKbVisible) {
// hide the keyboard, but don't translate the views
// Since the kb visibility listener will toggle the emojiPickerVisible live data, we do not explicitly toggle it here
inputHolderAnimationCallback.setShouldTranslate(false);
chatsAnimationCallback.setShouldTranslate(false);
emojiPickerAnimationCallback.setShouldTranslate(false);
hideKeyboard();
}
emojiPickerVisible.postValue(true);
});
final LiveData<Pair<Boolean, Boolean>> emojiKbVisibilityLD = Utils.zipLiveData(emojiPickerVisible, kbVisible);
emojiKbVisibilityLD.observe(getViewLifecycleOwner(), pair -> {
Boolean isEmojiPickerVisible = pair.first;
Boolean isKbVisible = pair.second;
if (isEmojiPickerVisible == null) isEmojiPickerVisible = false;
if (isKbVisible == null) isKbVisible = false;
root.setScrollImeOffScreenWhenVisible(!isEmojiPickerVisible);
root.setScrollImeOnScreenWhenNotVisible(!isEmojiPickerVisible);
onEmojiPickerBackPressedCallback.setEnabled(isEmojiPickerVisible && !isKbVisible);
if (isEmojiPickerVisible && !isKbVisible) {
animatePan(binding.emojiPicker.getMeasuredHeight(), unused -> {
binding.emojiPicker.setAlpha(1);
binding.emojiToggle.setIconResource(R.drawable.ic_keyboard_24);
return null;
}, null);
return;
}
if (!isEmojiPickerVisible && !isKbVisible) {
animatePan(0, null, unused -> {
binding.emojiPicker.setAlpha(0);
binding.emojiToggle.setIconResource(R.drawable.ic_face_24);
return null;
});
return;
}
// isKbVisible will always be true going forward
hasKbOpenedOnce = true;
if (!isEmojiPickerVisible) {
binding.emojiToggle.setIconResource(R.drawable.ic_face_24);
binding.emojiPicker.setAlpha(0);
return;
}
binding.emojiPicker.setAlpha(1);
});
}
public void showKeyboard() {
@ -1246,67 +1339,21 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
if (context == null) return;
final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm == null) return;
if (!isEmojiPickerShown) {
binding.emojiPicker.setAlpha(0);
if (!binding.input.isFocused()) {
binding.input.requestFocus();
}
final boolean shown = imm.showSoftInput(binding.input, InputMethodManager.SHOW_IMPLICIT);
if (!shown) {
Log.e(TAG, "showKeyboard: System did not display the keyboard");
}
if (!isEmojiPickerShown) {
animatePan(keyboardHeight);
}
isKbShown = true;
}
public void hideKeyboard(final boolean shouldPan) {
public void hideKeyboard() {
final Context context = getContext();
if (context == null) return;
final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm == null) return;
if (shouldPan) {
binding.emojiPicker.setAlpha(0);
}
imm.hideSoftInputFromWindow(binding.input.getWindowToken(), InputMethodManager.RESULT_UNCHANGED_SHOWN);
if (shouldPan) {
animatePan(0);
isEmojiPickerShown = false;
binding.emojiToggle.setIconResource(R.drawable.ic_face_24);
}
isKbShown = false;
}
/**
* Toggle between emoji picker and keyboard
* If both are hidden, the emoji picker is shown first
*/
private void toggleEmojiPicker() {
if (isKbShown) {
binding.emojiToggle.setIconResource(R.drawable.ic_keyboard_24);
hideKeyboard(false);
return;
}
if (isEmojiPickerShown) {
binding.emojiToggle.setIconResource(R.drawable.ic_face_24);
showKeyboard();
return;
}
binding.emojiToggle.setIconResource(R.drawable.ic_keyboard_24);
animatePan(keyboardHeight);
isEmojiPickerShown = true;
}
/**
* Set height of the emoji picker
*/
private void setEmojiPickerBounds() {
final ViewGroup.LayoutParams layoutParams = binding.emojiPicker.getLayoutParams();
layoutParams.height = keyboardHeight;
if (!isEmojiPickerShown) {
// If emoji picker is hidden reset the translationY so that it doesn't peek from bottom
binding.emojiPicker.setTranslationY(keyboardHeight);
}
binding.emojiPicker.requestLayout();
}
private void setSendToMicIcon() {
@ -1375,40 +1422,18 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
return null;
}
private void setupKbHeightProvider() {
if (heightProvider != null) return;
heightProvider = new HeightProvider(fragmentActivity).init().setHeightListener(height -> {
if (height > 100 && keyboardHeight != height) {
// save the current keyboard height to settings to use later
keyboardHeight = height;
setEmojiPickerBounds();
animatePan(keyboardHeight);
}
});
}
// Sets the translationY of views to height with animation
private void animatePan(final int height) {
private void animatePan(final int height,
@Nullable final Function<Void, Void> onAnimationStart,
@Nullable final Function<Void, Void> onAnimationEnd) {
if (animatorSet != null && animatorSet.isStarted()) {
animatorSet.cancel();
}
final ImmutableList.Builder<Animator> builder = ImmutableList.builder();
builder.add(
ObjectAnimator.ofFloat(binding.chats, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.input, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.inputBg, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.recordView, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.emojiToggle, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.gif, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.gallery, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.camera, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.send, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.replyBg, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.replyInfo, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.replyCancel, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.replyPreviewImage, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.replyPreviewText, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.emojiPicker, TRANSLATION_Y, keyboardHeight - height)
ObjectAnimator.ofFloat(binding.inputHolder, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.emojiPicker, TRANSLATION_Y, -height)
);
// if (headerItemDecoration != null && headerItemDecoration.getCurrentHeader() != null) {
// builder.add(ObjectAnimator.ofFloat(headerItemDecoration.getCurrentHeader(), TRANSLATION_Y, height));
@ -1418,10 +1443,21 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
animatorSet.setDuration(200);
animatorSet.setInterpolator(CubicBezierInterpolator.EASE_IN);
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(final Animator animation) {
super.onAnimationStart(animation);
if (onAnimationStart != null) {
onAnimationStart.apply(null);
}
}
@Override
public void onAnimationEnd(final Animator animation) {
binding.emojiPicker.setAlpha(1);
super.onAnimationEnd(animation);
animatorSet = null;
if (onAnimationEnd != null) {
onAnimationEnd.apply(null);
}
}
});
animatorSet.start();

View File

@ -337,6 +337,12 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
return super.onOptionsItemSelected(item);
}
@Override
public void onResume() {
super.onResume();
binding.getRoot().postDelayed(feedStoriesAdapter::notifyDataSetChanged, 1000);
}
@Override
public void onRefresh() {
binding.feedRecyclerView.refresh();
@ -418,15 +424,16 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
}
private void fetchStories() {
if (storiesFetching) return;
// final String cookie = settingsHelper.getString(Constants.COOKIE);
storiesFetching = true;
updateSwipeRefreshState();
storiesService.getFeedStories(new ServiceCallback<List<FeedStoryModel>>() {
@Override
public void onSuccess(final List<FeedStoryModel> result) {
storiesFetching = false;
feedStoriesViewModel.getList().postValue(result);
feedStoriesAdapter.submitList(result);
storiesFetching = false;
if (storyListMenu != null) storyListMenu.setVisible(true);
updateSwipeRefreshState();
}
@ -451,8 +458,10 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
}
public void scrollToTop() {
binding.feedRecyclerView.smoothScrollToPosition(0);
// binding.storiesContainer.setExpanded(true);
if (binding != null) {
binding.feedRecyclerView.smoothScrollToPosition(0);
// binding.storiesContainer.setExpanded(true);
}
}
private boolean isSafeToNavigate(final NavController navController) {

View File

@ -407,7 +407,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
}
chainingMenuItem = menu.findItem(R.id.chaining);
if (chainingMenuItem != null) {
chainingMenuItem.setVisible(isNotMe);
chainingMenuItem.setVisible(isNotMe && profileModel.hasChaining());
}
}
@ -527,7 +527,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
@Override
public void onRefresh() {
profileDetailsBinding.countsBarrier.getRoot().setVisibility(View.GONE);
profileDetailsBinding.countsDivider.getRoot().setVisibility(View.GONE);
profileDetailsBinding.mainProfileImage.setVisibility(View.INVISIBLE);
fetchProfileDetails();
}
@ -662,17 +662,21 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
Toast.makeText(context, R.string.error_loading_profile, Toast.LENGTH_SHORT).show();
return;
}
if (!postsSetupDone) {
setupPosts();
} else {
binding.postsRecyclerView.refresh();
final long profileId = profileModel.getPk();
if (!isReallyPrivate()) {
if (!postsSetupDone) {
setupPosts();
}
else {
binding.postsRecyclerView.refresh();
}
if (isLoggedIn) {
fetchStoryAndHighlights(profileId);
}
}
profileDetailsBinding.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE);
profileDetailsBinding.isPrivate.setVisibility(profileModel.isPrivate() ? View.VISIBLE : View.GONE);
final long profileId = profileModel.getPk();
if (isLoggedIn) {
fetchStoryAndHighlights(profileId);
}
setupButtons(profileId);
final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(getContext()));
favoriteRepository.getFavorite(profileModel.getUsername(), FavoriteType.USER, new RepositoryCallback<Favorite>() {
@ -744,7 +748,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
profileDetailsBinding.mainProfileImage.setImageURI(profileModel.getProfilePicUrl());
profileDetailsBinding.mainProfileImage.setVisibility(View.VISIBLE);
profileDetailsBinding.countsBarrier.getRoot().setVisibility(View.VISIBLE);
profileDetailsBinding.countsDivider.getRoot().setVisibility(View.VISIBLE);
final long followersCount = profileModel.getFollowerCount();
final long followingCount = profileModel.getFollowingCount();
@ -905,6 +909,8 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
binding.privatePage1.setImageResource(R.drawable.lock);
binding.privatePage2.setText(R.string.priv_acc);
binding.privatePage.setVisibility(View.VISIBLE);
binding.privatePage1.setVisibility(View.VISIBLE);
binding.privatePage2.setVisibility(View.VISIBLE);
binding.postsRecyclerView.setVisibility(View.GONE);
binding.swipeRefreshLayout.setRefreshing(false);
}
@ -970,7 +976,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
mutePostsMenuItem.setTitle(profileModel.getFriendshipStatus().isMuting() ? R.string.unmute_posts : R.string.mute_posts);
}
if (chainingMenuItem != null) {
chainingMenuItem.setVisible(true);
chainingMenuItem.setVisible(profileModel.hasChaining());
}
}
}

View File

@ -4,8 +4,11 @@ import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
@ -19,6 +22,7 @@ import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
import androidx.preference.PreferenceViewHolder;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
@ -53,6 +57,20 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
public MorePreferencesFragment() {
}
@Override
public RecyclerView onCreateRecyclerView(final LayoutInflater inflater, final ViewGroup parent, final Bundle savedInstanceState) {
final RecyclerView recyclerView = super.onCreateRecyclerView(inflater, parent, savedInstanceState);
final Context context = getContext();
if (recyclerView != null && context != null) {
recyclerView.setClipToPadding(false);
recyclerView.setPadding(recyclerView.getPaddingLeft(),
recyclerView.getPaddingTop(),
recyclerView.getPaddingRight(),
Utils.getActionBarHeight(context));
}
return recyclerView;
}
@Override
void setupPreferenceScreen(final PreferenceScreen screen) {
final String cookie = settingsHelper.getString(Constants.COOKIE);

View File

@ -16,11 +16,15 @@ public final class FeedStoryModel implements Serializable {
private final boolean isLive, isBestie;
private final long timestamp;
private final int mediaCount;
private boolean isShown = true;
public FeedStoryModel(final String storyMediaId, final User profileModel, final boolean fullyRead,
final long timestamp, final StoryModel firstStoryModel, final int mediaCount,
final boolean isLive, final boolean isBestie) {
public FeedStoryModel(final String storyMediaId,
final User profileModel,
final boolean fullyRead,
final long timestamp,
final StoryModel firstStoryModel,
final int mediaCount,
final boolean isLive,
final boolean isBestie) {
this.storyMediaId = storyMediaId;
this.profileModel = profileModel;
this.fullyRead = fullyRead;
@ -52,10 +56,6 @@ public final class FeedStoryModel implements Serializable {
return profileModel;
}
// public void setFirstStoryModel(final StoryModel firstStoryModel) {
// this.firstStoryModel = firstStoryModel;
// }
public StoryModel getFirstStoryModel() {
return firstStoryModel;
}
@ -75,12 +75,4 @@ public final class FeedStoryModel implements Serializable {
public boolean isBestie() {
return isBestie;
}
public boolean isShown() {
return isShown;
}
public void setShown(final boolean shown) {
isShown = shown;
}
}

View File

@ -17,6 +17,7 @@ public class User implements Serializable {
private final boolean isUnpublished;
private final boolean isFavorite;
private final boolean isDirectappInstalled;
private final boolean hasChaining;
private final String reelAutoArchive;
private final String allowedCommenterType;
private final long mediaCount;
@ -28,11 +29,10 @@ public class User implements Serializable {
private final long usertagsCount;
private final String publicEmail;
private final HdProfilePicUrlInfo hdProfilePicUrlInfo;
private final String profileContext;
private final List<UserProfileContextLink> profileContextLinksWithUserIds;
private final String socialContext;
// if a DM member is a Facebook user, this is present
private final String interopMessagingUserFbid;
private final String profileContext; // "also followed by" your friends
private final List<UserProfileContextLink> profileContextLinksWithUserIds; // ^
private final String socialContext; // AYML
private final String interopMessagingUserFbid; // in DMs only: Facebook user ID
public User(final long pk,
final String username,
@ -46,6 +46,7 @@ public class User implements Serializable {
final boolean isUnpublished,
final boolean isFavorite,
final boolean isDirectappInstalled,
final boolean hasChaining,
final String reelAutoArchive,
final String allowedCommenterType,
final long mediaCount,
@ -73,6 +74,7 @@ public class User implements Serializable {
this.isUnpublished = isUnpublished;
this.isFavorite = isFavorite;
this.isDirectappInstalled = isDirectappInstalled;
this.hasChaining = hasChaining;
this.reelAutoArchive = reelAutoArchive;
this.allowedCommenterType = allowedCommenterType;
this.mediaCount = mediaCount;
@ -90,6 +92,53 @@ public class User implements Serializable {
this.interopMessagingUserFbid = interopMessagingUserFbid;
}
public User(final long pk,
final String username,
final String fullName,
final boolean isPrivate,
final String profilePicUrl,
final boolean isVerified) {
this.pk = pk;
this.username = username;
this.fullName = fullName;
this.isPrivate = isPrivate;
this.profilePicUrl = profilePicUrl;
this.profilePicId = null;
this.friendshipStatus = new FriendshipStatus(
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
);
this.isVerified = isVerified;
this.hasAnonymousProfilePicture = false;
this.isUnpublished = false;
this.isFavorite = false;
this.isDirectappInstalled = false;
this.hasChaining = false;
this.reelAutoArchive = null;
this.allowedCommenterType = null;
this.mediaCount = 0;
this.followerCount = 0;
this.followingCount = 0;
this.followingTagCount = 0;
this.biography = null;
this.externalUrl = null;
this.usertagsCount = 0;
this.publicEmail = null;
this.hdProfilePicUrlInfo = null;
this.profileContext = null;
this.profileContextLinksWithUserIds = null;
this.socialContext = null;
this.interopMessagingUserFbid = null;
}
public long getPk() {
return pk;
}
@ -149,6 +198,10 @@ public class User implements Serializable {
return isDirectappInstalled;
}
public boolean hasChaining() {
return hasChaining;
}
public String getReelAutoArchive() {
return reelAutoArchive;
}
@ -237,7 +290,7 @@ public class User implements Serializable {
@Override
public int hashCode() {
return Objects.hash(pk, username, fullName, isPrivate, profilePicUrl, profilePicId, friendshipStatus, isVerified, hasAnonymousProfilePicture,
isUnpublished, isFavorite, isDirectappInstalled, reelAutoArchive, allowedCommenterType, mediaCount, followerCount,
followingCount, followingTagCount, biography, externalUrl, usertagsCount, publicEmail);
isUnpublished, isFavorite, isDirectappInstalled, hasChaining, reelAutoArchive, allowedCommenterType, mediaCount,
followerCount, followingCount, followingTagCount, biography, externalUrl, usertagsCount, publicEmail);
}
}

View File

@ -191,9 +191,7 @@ public class SearchItem {
recentSearch.getName(),
false,
recentSearch.getPicUrl(),
null, null, false, false, false, false, false,
null, null, 0, 0, 0, 0, null, null,
0, null, null, null, null, null, null
false
);
}
@ -205,9 +203,7 @@ public class SearchItem {
favorite.getDisplayName(),
false,
favorite.getPicUrl(),
null, null, false, false, false, false, false,
null, null, 0, 0, 0, 0, null, null,
0, null, null, null, null, null, null
false
);
}

View File

@ -63,7 +63,7 @@ public class NavigationExtensions {
selectedItemTag = graphIdToTagMap.get(bottomNavigationView.getSelectedItemId());
final String firstFragmentTag = graphIdToTagMap.get(firstFragmentGraphId);
isOnFirstFragment = selectedItemTag != null && selectedItemTag.equals(firstFragmentTag);
bottomNavigationView.setOnNavigationItemSelectedListener(item -> {
bottomNavigationView.setOnItemSelectedListener(item -> {
if (fragmentManager.isStateSaved()) {
return false;
}
@ -169,7 +169,7 @@ public class NavigationExtensions {
private static void setupItemReselected(final BottomNavigationView bottomNavigationView,
final SparseArray<String> graphIdToTagMap,
final FragmentManager fragmentManager) {
bottomNavigationView.setOnNavigationItemReselectedListener(item -> {
bottomNavigationView.setOnItemReselectedListener(item -> {
final String newlySelectedItemTag = graphIdToTagMap.get(item.getItemId());
final Fragment fragmentByTag = fragmentManager.findFragmentByTag(newlySelectedItemTag);
if (fragmentByTag == null) {

View File

@ -767,11 +767,7 @@ public final class ResponseBodyUtils {
owner.optString("full_name"),
false,
owner.optString("profile_pic_url"),
null,
friendshipStatus,
owner.optBoolean("is_verified"),
false, false, false, false, null, null, 0, 0, 0, 0, null, null, 0, null, null,
null, null, null, null);
owner.optBoolean("is_verified"));
}
final String id = feedItem.getString(Constants.EXTRAS_ID);
VideoVersion videoVersion = null;

View File

@ -38,6 +38,8 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MediatorLiveData;
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
import com.google.android.exoplayer2.database.ExoDatabaseProvider;
@ -562,4 +564,39 @@ public final class Utils {
display.getRealSize(size);
return size;
}
public static <F, S> LiveData<Pair<F, S>> zipLiveData(@NonNull final LiveData<F> firstLiveData,
@NonNull final LiveData<S> secondLiveData) {
final ZippedLiveData<F, S> zippedLiveData = new ZippedLiveData<>();
zippedLiveData.addFirstSource(firstLiveData);
zippedLiveData.addSecondSource(secondLiveData);
return zippedLiveData;
}
public static class ZippedLiveData<F, S> extends MediatorLiveData<Pair<F, S>> {
private F lastF;
private S lastS;
private void update() {
F localLastF = lastF;
S localLastS = lastS;
if (localLastF != null && localLastS != null) {
setValue(new Pair<>(localLastF, localLastS));
}
}
public void addFirstSource(@NonNull final LiveData<F> firstLiveData) {
addSource(firstLiveData, f -> {
lastF = f;
update();
});
}
public void addSecondSource(@NonNull final LiveData<S> secondLiveData) {
addSource(secondLiveData, s -> {
lastS = s;
update();
});
}
}
}

View File

@ -1,18 +1,28 @@
package awais.instagrabber.utils;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
import android.os.Build;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.res.ResourcesCompat;
import androidx.core.util.Pair;
import androidx.dynamicanimation.animation.FloatPropertyCompat;
import androidx.dynamicanimation.animation.SpringAnimation;
import org.jetbrains.annotations.NotNull;
import kotlin.jvm.internal.Intrinsics;
public final class ViewUtils {
@ -69,4 +79,43 @@ public final class ViewUtils {
public static float getTextViewValueWidth(final TextView textView, final String text) {
return textView.getPaint().measureText(text);
}
/**
* Creates [SpringAnimation] for object.
* If finalPosition is not [Float.NaN] then create [SpringAnimation] with
* [SpringForce.mFinalPosition].
*
* @param object Object
* @param property object's property to be animated.
* @param finalPosition [SpringForce.mFinalPosition] Final position of spring.
* @return [SpringAnimation]
*/
@NonNull
public static SpringAnimation springAnimationOf(final Object object,
final FloatPropertyCompat<Object> property,
@Nullable final Float finalPosition) {
return finalPosition == null ? new SpringAnimation(object, property) : new SpringAnimation(object, property, finalPosition);
}
public static void suppressLayoutCompat(@NotNull ViewGroup $this$suppressLayoutCompat, boolean suppress) {
Intrinsics.checkNotNullParameter($this$suppressLayoutCompat, "$this$suppressLayoutCompat");
if (Build.VERSION.SDK_INT >= 29) {
$this$suppressLayoutCompat.suppressLayout(suppress);
} else {
hiddenSuppressLayout($this$suppressLayoutCompat, suppress);
}
}
private static boolean tryHiddenSuppressLayout = true;
@SuppressLint({"NewApi"})
private static void hiddenSuppressLayout(ViewGroup group, boolean suppress) {
if (tryHiddenSuppressLayout) {
try {
group.suppressLayout(suppress);
} catch (NoSuchMethodError var3) {
tryHiddenSuppressLayout = false;
}
}
}
}

View File

@ -238,11 +238,7 @@ public class CommentsViewerViewModel extends ViewModel {
null,
false,
owner.getString("profile_pic_url"),
null,
new FriendshipStatus(false, false, false, false, false, false, false, false, false, false),
owner.optBoolean("is_verified"),
false, false, false, false, null, null, 0, 0, 0, 0, null, null, 0, null, null, null, null,
null, null);
owner.optBoolean("is_verified"));
final JSONObject likedBy = commentJsonObject.optJSONObject("edge_liked_by");
final String commentId = commentJsonObject.getString("id");
final JSONObject childCommentsJsonObject = commentJsonObject.optJSONObject("edge_threaded_comments");

View File

@ -34,7 +34,6 @@ import retrofit2.Response;
public class GraphQLService extends BaseService {
private static final String TAG = "GraphQLService";
// private static final boolean loadFromMock = false;
private final GraphQLRepository repository;
@ -230,39 +229,7 @@ public class GraphQLService extends BaseService {
userObject.optString("full_name"),
userObject.optBoolean("is_private"),
userObject.getString("profile_pic_url"),
null,
new FriendshipStatus(
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
),
userObject.optBoolean("is_verified"),
false,
false,
false,
false,
null,
null,
0,
0,
0,
0,
null,
null,
0,
null,
null,
null,
null,
null,
null
userObject.optBoolean("is_verified")
));
// userModels.add(new ProfileModel(userObject.optBoolean("is_private"),
// false,
@ -357,6 +324,7 @@ public class GraphQLService extends BaseService {
false,
false,
false,
false,
null,
null,
timelineMedia.getLong("count"),

View File

@ -143,39 +143,7 @@ public class StoriesService extends BaseService {
userJson.optString("full_name"),
userJson.optBoolean("is_private"),
userJson.getString("profile_pic_url"),
null,
new FriendshipStatus(
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
),
userJson.optBoolean("is_verified"),
false,
false,
false,
false,
null,
null,
0,
0,
0,
0,
null,
null,
0,
null,
null,
null,
null,
null,
null
userJson.optBoolean("is_verified")
);
final long timestamp = node.getLong("latest_reel_media");
final boolean fullyRead = !node.isNull("seen") && node.getLong("seen") == timestamp;
@ -210,39 +178,7 @@ public class StoriesService extends BaseService {
userJson.optString("full_name"),
userJson.optBoolean("is_private"),
userJson.getString("profile_pic_url"),
null,
new FriendshipStatus(
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
),
userJson.optBoolean("is_verified"),
false,
false,
false,
false,
null,
null,
0,
0,
0,
0,
null,
null,
0,
null,
null,
null,
null,
null,
null
userJson.optBoolean("is_verified")
);
feedStoryModels.add(new FeedStoryModel(
node.getString("id"),

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
<awais.instagrabber.customviews.InsetsNotifyingCoordinatorLayout 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:id="@+id/main_container"
@ -62,13 +62,14 @@
android:id="@+id/main_nav_host"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<!--app:layout_behavior="@string/hide_bottom_view_on_scroll_behavior"-->
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:labelVisibilityMode="auto"
app:layout_behavior="@string/hide_bottom_view_on_scroll_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
app:labelVisibilityMode="auto" />
</awais.instagrabber.customviews.InsetsNotifyingCoordinatorLayout>

View File

@ -1,312 +1,315 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<awais.instagrabber.customviews.InsetsAnimationLinearLayout 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:clipToPadding="false">
android:clipToPadding="false"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/chats"
android:layout_width="0dp"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:scrollbars="none"
app:layout_constraintBottom_toTopOf="@id/chats_barrier"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/layout_dm_base" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/chats_barrier"
android:layout_width="0dp"
android:layout_height="0dp"
app:barrierDirection="bottom" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/input_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:id="@+id/reply_bg"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@drawable/bg_input"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/input_bg"
app:layout_constraintEnd_toEndOf="@id/input_bg"
app:layout_constraintStart_toStartOf="@id/input_bg"
app:layout_constraintTop_toTopOf="@id/reply_info"
tools:visibility="gone" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/chats_barrier"
android:layout_width="0dp"
android:layout_height="0dp"
app:barrierDirection="bottom" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/reply_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingBottom="4dp"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/reply_preview_text"
app:layout_constraintEnd_toStartOf="@id/reply_preview_image"
app:layout_constraintStart_toStartOf="@id/input_bg"
app:layout_constraintTop_toBottomOf="@id/chats_barrier"
tools:text="Replying to yourself"
tools:visibility="gone" />
<View
android:id="@+id/reply_bg"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@drawable/bg_input"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/input_bg"
app:layout_constraintEnd_toEndOf="@id/input_bg"
app:layout_constraintStart_toStartOf="@id/input_bg"
app:layout_constraintTop_toTopOf="@id/reply_info"
tools:visibility="gone" />
<androidx.emoji.widget.EmojiAppCompatTextView
android:id="@+id/reply_preview_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:paddingStart="16dp"
android:paddingTop="4dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/input_bg"
app:layout_constraintEnd_toStartOf="@id/reply_preview_image"
app:layout_constraintStart_toStartOf="@id/input_bg"
app:layout_constraintTop_toBottomOf="@id/reply_info"
app:layout_goneMarginTop="8dp"
tools:text="Post"
tools:visibility="gone" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/reply_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingBottom="4dp"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/reply_preview_text"
app:layout_constraintEnd_toStartOf="@id/reply_preview_image"
app:layout_constraintStart_toStartOf="@id/input_bg"
app:layout_constraintTop_toBottomOf="@id/chats_barrier"
tools:text="Replying to yourself"
tools:visibility="gone" />
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/reply_preview_image"
android:layout_width="@dimen/dm_inbox_avatar_size_small"
android:layout_height="@dimen/dm_inbox_avatar_size_small"
android:layout_marginEnd="8dp"
android:visibility="gone"
app:actualImageScaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="@id/reply_preview_text"
app:layout_constraintEnd_toStartOf="@id/reply_cancel"
app:layout_constraintStart_toEndOf="@id/reply_preview_text"
app:layout_constraintTop_toTopOf="@id/reply_info"
tools:background="@mipmap/ic_launcher"
tools:visibility="gone" />
<androidx.emoji.widget.EmojiAppCompatTextView
android:id="@+id/reply_preview_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:paddingStart="16dp"
android:paddingTop="4dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/input_bg"
app:layout_constraintEnd_toStartOf="@id/reply_preview_image"
app:layout_constraintStart_toStartOf="@id/input_bg"
app:layout_constraintTop_toBottomOf="@id/reply_info"
app:layout_goneMarginTop="8dp"
tools:text="Post"
tools:visibility="gone" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/reply_cancel"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="12dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/reply_preview_text"
app:layout_constraintEnd_toEndOf="@id/input_bg"
app:layout_constraintStart_toEndOf="@id/reply_preview_image"
app:layout_constraintTop_toTopOf="@id/reply_info"
app:srcCompat="@drawable/ic_close_24"
tools:visibility="gone" />
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/reply_preview_image"
android:layout_width="@dimen/dm_inbox_avatar_size_small"
android:layout_height="@dimen/dm_inbox_avatar_size_small"
android:layout_marginEnd="8dp"
android:visibility="gone"
app:actualImageScaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="@id/reply_preview_text"
app:layout_constraintEnd_toStartOf="@id/reply_cancel"
app:layout_constraintStart_toEndOf="@id/reply_preview_text"
app:layout_constraintTop_toTopOf="@id/reply_info"
tools:background="@mipmap/ic_launcher"
tools:visibility="gone" />
<!--<androidx.constraintlayout.widget.Group-->
<!-- android:id="@+id/reply_group"-->
<!-- android:layout_width="0dp"-->
<!-- android:layout_height="0dp"-->
<!-- android:visibility="gone"-->
<!-- app:constraint_referenced_ids="reply_bg,reply_cancel,reply_info,reply_item_type,reply_preview"-->
<!-- tools:visibility="visible" />-->
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/reply_cancel"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="12dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/reply_preview_text"
app:layout_constraintEnd_toEndOf="@id/input_bg"
app:layout_constraintStart_toEndOf="@id/reply_preview_image"
app:layout_constraintTop_toTopOf="@id/reply_info"
app:srcCompat="@drawable/ic_close_24"
tools:visibility="gone" />
<View
android:id="@+id/input_bg"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:background="@drawable/bg_input"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/input"
app:layout_constraintEnd_toStartOf="@id/send"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/input"
tools:visibility="visible" />
<!--<androidx.constraintlayout.widget.Group-->
<!-- android:id="@+id/reply_group"-->
<!-- android:layout_width="0dp"-->
<!-- android:layout_height="0dp"-->
<!-- android:visibility="gone"-->
<!-- app:constraint_referenced_ids="reply_bg,reply_cancel,reply_info,reply_item_type,reply_preview"-->
<!-- tools:visibility="visible" />-->
<com.google.android.material.button.MaterialButton
android:id="@+id/emoji_toggle"
style="@style/Widget.MaterialComponents.Button.Icon.NoInsets"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="2dp"
android:background="@android:color/transparent"
android:scrollbars="none"
android:visibility="gone"
app:icon="@drawable/ic_face_24"
app:iconGravity="textStart"
app:iconSize="24dp"
app:iconTint="@color/grey_700"
app:layout_constraintBottom_toBottomOf="@id/input_bg"
app:layout_constraintEnd_toStartOf="@id/input"
app:layout_constraintStart_toStartOf="@id/input_bg"
app:layout_constraintTop_toTopOf="@id/input"
app:rippleColor="@color/grey_500"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.App.Button.Circle"
app:strokeColor="@color/black"
app:strokeWidth="1dp"
tools:visibility="visible" />
<View
android:id="@+id/input_bg"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:background="@drawable/bg_input"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/input"
app:layout_constraintEnd_toStartOf="@id/send"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/input"
tools:visibility="visible" />
<awais.instagrabber.customviews.KeyNotifyingEmojiEditText
android:id="@+id/input"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:background="@android:color/transparent"
android:hint="@string/message"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:textColor="?dmInputTextColor"
android:textColorHint="@color/grey_500"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/gif"
app:layout_constraintStart_toEndOf="@id/emoji_toggle"
app:layout_constraintTop_toBottomOf="@id/reply_preview_text"
app:layout_goneMarginBottom="4dp"
app:layout_goneMarginEnd="24dp"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/emoji_toggle"
style="@style/Widget.MaterialComponents.Button.Icon.NoInsets"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="2dp"
android:background="@android:color/transparent"
android:scrollbars="none"
android:visibility="gone"
app:icon="@drawable/ic_face_24"
app:iconGravity="textStart"
app:iconSize="24dp"
app:iconTint="@color/grey_700"
app:layout_constraintBottom_toBottomOf="@id/input_bg"
app:layout_constraintEnd_toStartOf="@id/input"
app:layout_constraintStart_toStartOf="@id/input_bg"
app:layout_constraintTop_toTopOf="@id/input"
app:rippleColor="@color/grey_500"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.App.Button.Circle"
app:strokeColor="@color/black"
app:strokeWidth="1dp"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/gif"
android:layout_width="32dp"
android:layout_height="0dp"
android:background="@android:color/transparent"
android:scaleType="fitCenter"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/input_bg"
app:layout_constraintEnd_toStartOf="@id/camera"
app:layout_constraintStart_toEndOf="@id/input"
app:layout_constraintTop_toTopOf="@id/input"
app:srcCompat="@drawable/ic_round_gif_24"
tools:visibility="visible" />
<awais.instagrabber.customviews.KeyNotifyingEmojiEditText
android:id="@+id/input"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:background="@android:color/transparent"
android:hint="@string/message"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:textColor="?dmInputTextColor"
android:textColorHint="@color/grey_500"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/gif"
app:layout_constraintStart_toEndOf="@id/emoji_toggle"
app:layout_constraintTop_toBottomOf="@id/reply_preview_text"
app:layout_goneMarginBottom="4dp"
app:layout_goneMarginEnd="24dp"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/camera"
android:layout_width="32dp"
android:layout_height="0dp"
android:layout_marginStart="4dp"
android:background="@android:color/transparent"
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:scaleType="fitCenter"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/input_bg"
app:layout_constraintEnd_toStartOf="@id/gallery"
app:layout_constraintStart_toEndOf="@id/gif"
app:layout_constraintTop_toTopOf="@id/input"
app:srcCompat="@drawable/ic_camera_24"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/gif"
android:layout_width="32dp"
android:layout_height="0dp"
android:background="@android:color/transparent"
android:scaleType="fitCenter"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/input_bg"
app:layout_constraintEnd_toStartOf="@id/camera"
app:layout_constraintStart_toEndOf="@id/input"
app:layout_constraintTop_toTopOf="@id/input"
app:srcCompat="@drawable/ic_round_gif_24"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/gallery"
android:layout_width="32dp"
android:layout_height="0dp"
android:layout_marginStart="4dp"
android:layout_marginEnd="16dp"
android:background="@android:color/transparent"
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_image_24"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/input_bg"
app:layout_constraintEnd_toStartOf="@id/send"
app:layout_constraintStart_toEndOf="@id/camera"
app:layout_constraintTop_toTopOf="@id/input"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/camera"
android:layout_width="32dp"
android:layout_height="0dp"
android:layout_marginStart="4dp"
android:background="@android:color/transparent"
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:scaleType="fitCenter"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/input_bg"
app:layout_constraintEnd_toStartOf="@id/gallery"
app:layout_constraintStart_toEndOf="@id/gif"
app:layout_constraintTop_toTopOf="@id/input"
app:srcCompat="@drawable/ic_camera_24"
tools:visibility="visible" />
<awais.instagrabber.customviews.RecordView
android:id="@+id/record_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"
app:counter_time_color="@color/white"
app:layout_constraintBottom_toBottomOf="@id/input_bg"
app:layout_constraintEnd_toEndOf="@id/input_bg"
app:layout_constraintStart_toStartOf="@id/input"
app:layout_constraintTop_toBottomOf="@id/chats_barrier"
app:slide_to_cancel_arrow="@drawable/recv_ic_arrow"
app:slide_to_cancel_arrow_color="@color/white"
app:slide_to_cancel_bounds="0dp"
app:slide_to_cancel_margin_right="16dp"
app:slide_to_cancel_text="Slide To Cancel"
app:slide_to_cancel_text_color="@color/white"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/gallery"
android:layout_width="32dp"
android:layout_height="0dp"
android:layout_marginStart="4dp"
android:layout_marginEnd="16dp"
android:background="@android:color/transparent"
android:paddingStart="4dp"
android:paddingEnd="4dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_image_24"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/input_bg"
app:layout_constraintEnd_toStartOf="@id/send"
app:layout_constraintStart_toEndOf="@id/camera"
app:layout_constraintTop_toTopOf="@id/input"
tools:visibility="visible" />
<awais.instagrabber.customviews.RecordButton
android:id="@+id/send"
style="@style/Widget.MaterialComponents.Button.Icon.NoInsets"
android:layout_width="48dp"
android:layout_height="48dp"
android:visibility="gone"
app:backgroundTint="@color/blue_900"
app:elevation="4dp"
app:icon="@drawable/avd_mic_to_send_anim"
app:iconGravity="textStart"
app:iconSize="24dp"
app:iconTint="@color/white"
app:layout_constraintBottom_toBottomOf="@id/input_bg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/input_bg"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.App.Button.Circle"
tools:visibility="visible" />
<awais.instagrabber.customviews.RecordView
android:id="@+id/record_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"
app:counter_time_color="@color/white"
app:layout_constraintBottom_toBottomOf="@id/input_bg"
app:layout_constraintEnd_toEndOf="@id/input_bg"
app:layout_constraintStart_toStartOf="@id/input"
app:layout_constraintTop_toBottomOf="@id/chats_barrier"
app:slide_to_cancel_arrow="@drawable/recv_ic_arrow"
app:slide_to_cancel_arrow_color="@color/white"
app:slide_to_cancel_bounds="0dp"
app:slide_to_cancel_margin_right="16dp"
app:slide_to_cancel_text="Slide To Cancel"
app:slide_to_cancel_text_color="@color/white"
tools:visibility="visible" />
<awais.instagrabber.customviews.RecordButton
android:id="@+id/send"
style="@style/Widget.MaterialComponents.Button.Icon.NoInsets"
android:layout_width="48dp"
android:layout_height="48dp"
android:visibility="gone"
app:backgroundTint="@color/blue_900"
app:elevation="4dp"
app:icon="@drawable/avd_mic_to_send_anim"
app:iconGravity="textStart"
app:iconSize="24dp"
app:iconTint="@color/white"
app:layout_constraintBottom_toBottomOf="@id/input_bg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/input_bg"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.App.Button.Circle"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/accept_pending_request_question"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="16dp"
android:paddingBottom="8dp"
android:text="@string/accept_request_from_user"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/decline"
app:layout_constraintTop_toBottomOf="@id/chats_barrier"
tools:visibility="gone" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/decline"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:padding="16dp"
android:text="@string/decline"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/red_500"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/accept"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/accept_pending_request_question"
tools:visibility="gone" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/accept"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:padding="16dp"
android:text="@string/accept"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/decline"
app:layout_constraintTop_toBottomOf="@id/accept_pending_request_question"
tools:visibility="gone" />
</androidx.constraintlayout.widget.ConstraintLayout>
<awais.instagrabber.customviews.emoji.EmojiPicker
android:id="@+id/emoji_picker"
android:layout_width="0dp"
android:layout_height="250dp"
android:translationY="250dp"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/accept_pending_request_question"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="16dp"
android:paddingBottom="8dp"
android:text="@string/accept_request_from_user"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/decline"
app:layout_constraintTop_toBottomOf="@id/chats_barrier"
tools:visibility="gone" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/decline"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:padding="16dp"
android:text="@string/decline"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/red_500"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/accept"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/accept_pending_request_question"
tools:visibility="gone" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/accept"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:padding="16dp"
android:text="@string/accept"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:visibility="gone"
android:layout_height="250dp"
android:layout_marginBottom="-250dp"
android:alpha="0"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/decline"
app:layout_constraintTop_toBottomOf="@id/accept_pending_request_question"
tools:visibility="gone" />
</androidx.constraintlayout.widget.ConstraintLayout>
app:layout_constraintStart_toStartOf="parent" />
</awais.instagrabber.customviews.InsetsAnimationLinearLayout>

View File

@ -11,7 +11,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="?attr/actionBarSize"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.recyclerview.widget.RecyclerView
@ -19,6 +18,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="?actionBarSize"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"
tools:itemCount="10"

View File

@ -19,7 +19,6 @@
android:layout_width="match_parent"
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">

View File

@ -19,7 +19,6 @@
android:layout_width="match_parent"
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">

View File

@ -38,7 +38,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_marginTop="@dimen/private_page_margins"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone"
@ -48,6 +47,8 @@
android:id="@+id/privatePage1"
android:layout_width="@dimen/private_page_size"
android:layout_height="@dimen/private_page_size"
android:visibility="gone"
tools:visibility="visible"
app:srcCompat="@drawable/lock" />
<androidx.appcompat.widget.AppCompatTextView
@ -55,6 +56,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:visibility="gone"
tools:visibility="visible"
android:text="@string/priv_acc"
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
</LinearLayout>

View File

@ -43,7 +43,9 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/title"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
android:textSize="15sp"
android:visibility="visible"

View File

@ -15,10 +15,11 @@
android:transitionName="profile_pic"
android:visibility="invisible"
app:actualImageScaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="@id/btnTagged"
app:layout_constraintBottom_toTopOf="@id/top_barrier"
app:layout_constraintEnd_toStartOf="@id/btnFollow"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0"
tools:foreground="@mipmap/ic_launcher"
tools:visibility="visible" />
@ -112,7 +113,7 @@
app:chipBackgroundColor="@null"
app:chipIcon="@drawable/ic_outline_person_pin_24"
app:chipIconTint="@color/deep_orange_800"
app:layout_constraintBottom_toTopOf="@+id/mainFullName"
app:layout_constraintBottom_toTopOf="@+id/top_barrier"
app:layout_constraintStart_toEndOf="@id/mainProfileImage"
app:layout_constraintTop_toBottomOf="@id/fav_chip"
app:rippleColor="@color/deep_orange_400"
@ -128,12 +129,18 @@
app:chipBackgroundColor="@null"
app:chipIcon="@drawable/ic_round_send_24"
app:chipIconTint="@color/green"
app:layout_constraintBottom_toTopOf="@+id/mainFullName"
app:layout_constraintBottom_toTopOf="@+id/top_barrier"
app:layout_constraintStart_toEndOf="@id/btnTagged"
app:layout_constraintTop_toBottomOf="@id/fav_chip"
app:rippleColor="@color/green"
tools:visibility="visible" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/top_barrier"
android:layout_width="0dp"
android:layout_height="0dp"
app:barrierDirection="bottom" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/mainFullName"
android:layout_width="wrap_content"
@ -146,40 +153,37 @@
android:singleLine="true"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@id/mainBiography"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btnTagged"
app:layout_constraintTop_toBottomOf="@id/top_barrier"
tools:text="Austin Huang" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/isVerified"
android:layout_width="30dp"
android:layout_height="match_parent"
android:layout_height="0dp"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:scaleType="fitCenter"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/mainFullName"
app:layout_constraintStart_toEndOf="@id/mainFullName"
app:layout_constraintTop_toBottomOf="@id/btnTagged"
app:layout_constraintTop_toTopOf="@id/mainFullName"
app:srcCompat="@drawable/verified"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/isPrivate"
android:layout_width="25dp"
android:layout_height="match_parent"
android:layout_height="0dp"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:scaleType="fitCenter"
android:tint="@color/red_500"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/mainFullName"
app:layout_constraintStart_toEndOf="@id/isVerified"
app:layout_constraintTop_toBottomOf="@id/btnTagged"
app:layout_constraintTop_toTopOf="@id/mainFullName"
app:srcCompat="@drawable/lock"
tools:visibility="visible" />
@ -233,6 +237,7 @@
android:paddingBottom="4dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/profileContext"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/mainBiography"
@ -254,7 +259,7 @@
android:textSize="12sp"
android:textStyle="italic"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/counts_barrier"
app:layout_constraintBottom_toTopOf="@id/counts_divider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/mainUrl"
@ -262,11 +267,12 @@
tools:visibility="visible" />
<include
android:id="@+id/counts_barrier"
android:id="@+id/counts_divider"
layout="@layout/item_pref_divider"
android:layout_width="0dp"
android:layout_height="1dp"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/mainPostCount"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/profileContext"
@ -285,7 +291,7 @@
app:layout_constraintBottom_toBottomOf="@id/highlights_barrier"
app:layout_constraintEnd_toStartOf="@id/mainFollowers"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/counts_barrier"
app:layout_constraintTop_toBottomOf="@id/counts_divider"
tools:text="35\nPosts" />
<androidx.appcompat.widget.AppCompatTextView
@ -302,7 +308,7 @@
app:layout_constraintBottom_toBottomOf="@id/highlights_barrier"
app:layout_constraintEnd_toStartOf="@id/mainFollowing"
app:layout_constraintStart_toEndOf="@id/mainPostCount"
app:layout_constraintTop_toBottomOf="@id/counts_barrier"
app:layout_constraintTop_toBottomOf="@id/counts_divider"
tools:text="68\nFollowers" />
<androidx.appcompat.widget.AppCompatTextView
@ -319,7 +325,7 @@
app:layout_constraintBottom_toBottomOf="@id/highlights_barrier"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/mainFollowers"
app:layout_constraintTop_toBottomOf="@id/counts_barrier"
app:layout_constraintTop_toBottomOf="@id/counts_divider"
tools:text="64\nFollowing" />
<androidx.constraintlayout.widget.Barrier

View File

@ -19,9 +19,9 @@
<color name="btn_lightpink_text_color">@color/text_color_light</color>
<color name="btn_orange_background">#FF5500</color>
<color name="btn_orange_text_color">#FFFFFFFF</color>
<color name="btn_orange_text_color">@color/white</color>
<color name="btn_lightorange_background">#FFBB00</color>
<color name="btn_lightorange_text_color">#FF000000</color>
<color name="btn_lightorange_text_color">@color/black</color>
<color name="dm_profile_button_color">#efefef</color>

View File

@ -9,6 +9,13 @@
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/privatePage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/private_page_margins"
android:visibility="visible"
motion:layout_constraintTop_toBottomOf="@+id/header" />
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint

View File

@ -5,7 +5,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:4.2.0'
classpath 'com.android.tools.build:gradle:4.2.1'
def nav_version = "2.3.5"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=9af5c8e7e2cd1a3b0f694a4ac262b9f38c75262e74a9e8b5101af302a6beadd7
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip
distributionSha256Sum=13bf8d3cf8eeeb5770d19741a59bde9bd966dd78d17f1bbad787a05ef19d1c2d
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists