kopia lustrzana https://github.com/ryukoposting/Signal-Android
Improvments to MP4 giphy fragment behaviour.
rodzic
ed1be76606
commit
e2e1200c89
|
@ -5,12 +5,13 @@ import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.widget.ContentLoadingProgressBar;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.ViewModelProviders;
|
import androidx.lifecycle.ViewModelProviders;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
|
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
|
||||||
|
|
||||||
|
@ -19,11 +20,9 @@ import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fragment which displays GyphyImages.
|
* Fragment which displays GyphyImages.
|
||||||
|
@ -51,6 +50,8 @@ public class GiphyMp4Fragment extends Fragment {
|
||||||
boolean isForMms = requireArguments().getBoolean(IS_FOR_MMS, false);
|
boolean isForMms = requireArguments().getBoolean(IS_FOR_MMS, false);
|
||||||
FrameLayout frameLayout = view.findViewById(R.id.giphy_parent);
|
FrameLayout frameLayout = view.findViewById(R.id.giphy_parent);
|
||||||
RecyclerView recycler = view.findViewById(R.id.giphy_recycler);
|
RecyclerView recycler = view.findViewById(R.id.giphy_recycler);
|
||||||
|
ContentLoadingProgressBar progressBar = view.findViewById(R.id.content_loading);
|
||||||
|
TextView nothingFound = view.findViewById(R.id.nothing_found);
|
||||||
GiphyMp4ViewModel viewModel = ViewModelProviders.of(requireActivity(), new GiphyMp4ViewModel.Factory(isForMms)).get(GiphyMp4ViewModel.class);
|
GiphyMp4ViewModel viewModel = ViewModelProviders.of(requireActivity(), new GiphyMp4ViewModel.Factory(isForMms)).get(GiphyMp4ViewModel.class);
|
||||||
GiphyMp4MediaSourceFactory mediaSourceFactory = new GiphyMp4MediaSourceFactory(ApplicationDependencies.getOkHttpClient());
|
GiphyMp4MediaSourceFactory mediaSourceFactory = new GiphyMp4MediaSourceFactory(ApplicationDependencies.getOkHttpClient());
|
||||||
GiphyMp4Adapter adapter = new GiphyMp4Adapter(mediaSourceFactory, viewModel::saveToBlob);
|
GiphyMp4Adapter adapter = new GiphyMp4Adapter(mediaSourceFactory, viewModel::saveToBlob);
|
||||||
|
@ -60,11 +61,13 @@ public class GiphyMp4Fragment extends Fragment {
|
||||||
recycler.setLayoutManager(new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL));
|
recycler.setLayoutManager(new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL));
|
||||||
recycler.setAdapter(adapter);
|
recycler.setAdapter(adapter);
|
||||||
recycler.setItemAnimator(null);
|
recycler.setItemAnimator(null);
|
||||||
|
progressBar.show();
|
||||||
|
|
||||||
GiphyMp4AdapterPlaybackController.attach(recycler, callback, GiphyMp4PlaybackPolicy.maxSimultaneousPlaybackInSearchResults());
|
GiphyMp4AdapterPlaybackController.attach(recycler, callback, GiphyMp4PlaybackPolicy.maxSimultaneousPlaybackInSearchResults());
|
||||||
|
viewModel.getImages().observe(getViewLifecycleOwner(), images -> {
|
||||||
viewModel.getImages().observe(getViewLifecycleOwner(), adapter::submitList);
|
nothingFound.setVisibility(images.isEmpty() ? View.VISIBLE : View.INVISIBLE);
|
||||||
|
adapter.submitList(images, progressBar::hide);
|
||||||
|
});
|
||||||
viewModel.getPagingController().observe(getViewLifecycleOwner(), adapter::setPagingController);
|
viewModel.getPagingController().observe(getViewLifecycleOwner(), adapter::setPagingController);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import android.text.TextUtils;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.signal.core.util.ThreadUtil;
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.signal.paging.PagedDataSource;
|
import org.signal.paging.PagedDataSource;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
|
|
@ -1,87 +0,0 @@
|
||||||
package org.thoughtcrime.securesms.giph.net;
|
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import com.annimon.stream.Stream;
|
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
|
||||||
import org.thoughtcrime.securesms.giph.model.GiphyImage;
|
|
||||||
import org.thoughtcrime.securesms.giph.model.GiphyResponse;
|
|
||||||
import org.thoughtcrime.securesms.net.ContentProxySelector;
|
|
||||||
import org.thoughtcrime.securesms.net.StandardUserAgentInterceptor;
|
|
||||||
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
|
||||||
import org.thoughtcrime.securesms.util.AsyncLoader;
|
|
||||||
import org.thoughtcrime.securesms.util.JsonUtils;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.Response;
|
|
||||||
|
|
||||||
public abstract class GiphyLoader extends AsyncLoader<List<GiphyImage>> {
|
|
||||||
|
|
||||||
private static final String TAG = Log.tag(GiphyLoader.class);
|
|
||||||
|
|
||||||
public static int PAGE_SIZE = 100;
|
|
||||||
|
|
||||||
@Nullable private String searchString;
|
|
||||||
|
|
||||||
private final OkHttpClient client;
|
|
||||||
|
|
||||||
protected GiphyLoader(@NonNull Context context, @Nullable String searchString) {
|
|
||||||
super(context);
|
|
||||||
this.searchString = searchString;
|
|
||||||
this.client = new OkHttpClient.Builder()
|
|
||||||
.proxySelector(new ContentProxySelector())
|
|
||||||
.addInterceptor(new StandardUserAgentInterceptor())
|
|
||||||
.dns(SignalServiceNetworkAccess.DNS)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<GiphyImage> loadInBackground() {
|
|
||||||
return loadPage(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NonNull List<GiphyImage> loadPage(int offset) {
|
|
||||||
try {
|
|
||||||
String url;
|
|
||||||
|
|
||||||
if (TextUtils.isEmpty(searchString)) url = String.format(getTrendingUrl(), offset);
|
|
||||||
else url = String.format(getSearchUrl(), offset, Uri.encode(searchString));
|
|
||||||
|
|
||||||
Request request = new Request.Builder().url(url).build();
|
|
||||||
Response response = client.newCall(request).execute();
|
|
||||||
|
|
||||||
if (!response.isSuccessful()) {
|
|
||||||
throw new IOException("Unexpected code " + response);
|
|
||||||
}
|
|
||||||
|
|
||||||
GiphyResponse giphyResponse = JsonUtils.fromJson(response.body().byteStream(), GiphyResponse.class);
|
|
||||||
List<GiphyImage> results = Stream.of(giphyResponse.getData())
|
|
||||||
.filterNot(g -> TextUtils.isEmpty(g.getGifUrl()))
|
|
||||||
.filterNot(g -> TextUtils.isEmpty(g.getGifMmsUrl()))
|
|
||||||
.filterNot(g -> TextUtils.isEmpty(g.getStillUrl()))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
if (results == null) return new LinkedList<>();
|
|
||||||
else return results;
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
return new LinkedList<>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract String getTrendingUrl();
|
|
||||||
protected abstract String getSearchUrl();
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
package org.thoughtcrime.securesms.giph.net;
|
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
public class GiphyStickerLoader extends GiphyLoader {
|
|
||||||
|
|
||||||
public GiphyStickerLoader(@NonNull Context context, @Nullable String searchString) {
|
|
||||||
super(context, searchString);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getTrendingUrl() {
|
|
||||||
return "https://api.giphy.com/v1/stickers/trending?api_key=3o6ZsYH6U6Eri53TXy&offset=%d&limit=" + PAGE_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getSearchUrl() {
|
|
||||||
return "https://api.giphy.com/v1/stickers/search?api_key=3o6ZsYH6U6Eri53TXy&offset=%d&limit=" + PAGE_SIZE + "&q=%s";
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +1,29 @@
|
||||||
package org.thoughtcrime.securesms.giph.ui;
|
package org.thoughtcrime.securesms.giph.ui;
|
||||||
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.ColorInt;
|
import androidx.annotation.ColorInt;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.core.app.ActivityCompat;
|
import androidx.core.app.ActivityCompat;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
|
||||||
import androidx.fragment.app.FragmentPagerAdapter;
|
|
||||||
import androidx.lifecycle.ViewModelProviders;
|
import androidx.lifecycle.ViewModelProviders;
|
||||||
import androidx.viewpager.widget.ViewPager;
|
|
||||||
|
|
||||||
import com.google.android.material.tabs.TabLayout;
|
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActivity;
|
import org.thoughtcrime.securesms.PassphraseRequiredActivity;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4Fragment;
|
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4Fragment;
|
||||||
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4SaveResult;
|
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4SaveResult;
|
||||||
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4ViewModel;
|
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4ViewModel;
|
||||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
|
||||||
import org.thoughtcrime.securesms.util.DynamicDarkToolbarTheme;
|
import org.thoughtcrime.securesms.util.DynamicDarkToolbarTheme;
|
||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
|
||||||
import org.thoughtcrime.securesms.util.WindowUtil;
|
import org.thoughtcrime.securesms.util.WindowUtil;
|
||||||
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
|
||||||
|
|
||||||
import java.io.IOException;
|
public class GiphyActivity extends PassphraseRequiredActivity implements GiphyActivityToolbar.OnFilterChangedListener {
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
|
|
||||||
public class GiphyActivity extends PassphraseRequiredActivity
|
|
||||||
implements GiphyActivityToolbar.OnFilterChangedListener,
|
|
||||||
GiphyAdapter.OnItemClickListener
|
|
||||||
{
|
|
||||||
|
|
||||||
private static final String TAG = Log.tag(GiphyActivity.class);
|
|
||||||
|
|
||||||
public static final String EXTRA_IS_MMS = "extra_is_mms";
|
public static final String EXTRA_IS_MMS = "extra_is_mms";
|
||||||
public static final String EXTRA_WIDTH = "extra_width";
|
public static final String EXTRA_WIDTH = "extra_width";
|
||||||
|
@ -56,12 +34,6 @@ public class GiphyActivity extends PassphraseRequiredActivity
|
||||||
private final DynamicTheme dynamicTheme = new DynamicDarkToolbarTheme();
|
private final DynamicTheme dynamicTheme = new DynamicDarkToolbarTheme();
|
||||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||||
|
|
||||||
private Fragment gifFragment;
|
|
||||||
private GiphyStickerFragment stickerFragment;
|
|
||||||
private boolean forMms;
|
|
||||||
|
|
||||||
private GiphyAdapter.GiphyViewHolder finishingImage;
|
|
||||||
|
|
||||||
private GiphyMp4ViewModel giphyMp4ViewModel;
|
private GiphyMp4ViewModel giphyMp4ViewModel;
|
||||||
private AlertDialog progressDialog;
|
private AlertDialog progressDialog;
|
||||||
|
|
||||||
|
@ -75,17 +47,20 @@ public class GiphyActivity extends PassphraseRequiredActivity
|
||||||
public void onCreate(Bundle bundle, boolean ready) {
|
public void onCreate(Bundle bundle, boolean ready) {
|
||||||
setContentView(R.layout.giphy_activity);
|
setContentView(R.layout.giphy_activity);
|
||||||
|
|
||||||
forMms = getIntent().getBooleanExtra(EXTRA_IS_MMS, false);
|
final boolean forMms = getIntent().getBooleanExtra(EXTRA_IS_MMS, false);
|
||||||
giphyMp4ViewModel = ViewModelProviders.of(this, new GiphyMp4ViewModel.Factory(forMms)).get(GiphyMp4ViewModel.class);
|
|
||||||
|
|
||||||
|
giphyMp4ViewModel = ViewModelProviders.of(this, new GiphyMp4ViewModel.Factory(forMms)).get(GiphyMp4ViewModel.class);
|
||||||
giphyMp4ViewModel.getSaveResultEvents().observe(this, this::handleGiphyMp4SaveResult);
|
giphyMp4ViewModel.getSaveResultEvents().observe(this, this::handleGiphyMp4SaveResult);
|
||||||
|
|
||||||
initializeToolbar();
|
initializeToolbar();
|
||||||
initializeResources();
|
|
||||||
|
Fragment fragment = GiphyMp4Fragment.create(forMms);
|
||||||
|
getSupportFragmentManager().beginTransaction()
|
||||||
|
.replace(R.id.fragment_container, fragment)
|
||||||
|
.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeToolbar() {
|
private void initializeToolbar() {
|
||||||
|
|
||||||
GiphyActivityToolbar toolbar = findViewById(R.id.giphy_toolbar);
|
GiphyActivityToolbar toolbar = findViewById(R.id.giphy_toolbar);
|
||||||
toolbar.setOnFilterChangedListener(this);
|
toolbar.setOnFilterChangedListener(this);
|
||||||
|
|
||||||
|
@ -99,21 +74,6 @@ public class GiphyActivity extends PassphraseRequiredActivity
|
||||||
getSupportActionBar().setDisplayShowTitleEnabled(false);
|
getSupportActionBar().setDisplayShowTitleEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeResources() {
|
|
||||||
ViewPager viewPager = findViewById(R.id.giphy_pager);
|
|
||||||
TabLayout tabLayout = findViewById(R.id.tab_layout);
|
|
||||||
|
|
||||||
this.gifFragment = GiphyMp4Fragment.create(forMms);
|
|
||||||
this.stickerFragment = new GiphyStickerFragment();
|
|
||||||
|
|
||||||
stickerFragment.setClickListener(this);
|
|
||||||
|
|
||||||
viewPager.setAdapter(new GiphyFragmentPagerAdapter(this, getSupportFragmentManager(),
|
|
||||||
gifFragment, stickerFragment));
|
|
||||||
tabLayout.setupWithViewPager(viewPager);
|
|
||||||
tabLayout.setBackgroundColor(getConversationColor());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleGiphyMp4SaveResult(@NonNull GiphyMp4SaveResult result) {
|
private void handleGiphyMp4SaveResult(@NonNull GiphyMp4SaveResult result) {
|
||||||
if (result instanceof GiphyMp4SaveResult.Success) {
|
if (result instanceof GiphyMp4SaveResult.Success) {
|
||||||
hideProgressDialog();
|
hideProgressDialog();
|
||||||
|
@ -154,83 +114,5 @@ public class GiphyActivity extends PassphraseRequiredActivity
|
||||||
@Override
|
@Override
|
||||||
public void onFilterChanged(String filter) {
|
public void onFilterChanged(String filter) {
|
||||||
giphyMp4ViewModel.updateSearchQuery(filter);
|
giphyMp4ViewModel.updateSearchQuery(filter);
|
||||||
this.stickerFragment.setSearchString(filter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
|
||||||
@Override
|
|
||||||
public void onClick(final GiphyAdapter.GiphyViewHolder viewHolder) {
|
|
||||||
if (finishingImage != null) finishingImage.gifProgress.setVisibility(View.GONE);
|
|
||||||
finishingImage = viewHolder;
|
|
||||||
finishingImage.gifProgress.setVisibility(View.VISIBLE);
|
|
||||||
|
|
||||||
new AsyncTask<Void, Void, Uri>() {
|
|
||||||
@Override
|
|
||||||
protected Uri doInBackground(Void... params) {
|
|
||||||
try {
|
|
||||||
byte[] data = viewHolder.getData(forMms);
|
|
||||||
|
|
||||||
return BlobProvider.getInstance()
|
|
||||||
.forData(data)
|
|
||||||
.withMimeType(MediaUtil.IMAGE_GIF)
|
|
||||||
.createForSingleSessionOnDisk(GiphyActivity.this);
|
|
||||||
} catch (InterruptedException | ExecutionException | IOException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void onPostExecute(@Nullable Uri uri) {
|
|
||||||
if (uri == null) {
|
|
||||||
Toast.makeText(GiphyActivity.this, R.string.GiphyActivity_error_while_retrieving_full_resolution_gif, Toast.LENGTH_LONG).show();
|
|
||||||
} else if (viewHolder == finishingImage) {
|
|
||||||
Intent intent = new Intent();
|
|
||||||
intent.setData(uri);
|
|
||||||
intent.putExtra(EXTRA_WIDTH, viewHolder.image.getGifWidth());
|
|
||||||
intent.putExtra(EXTRA_HEIGHT, viewHolder.image.getGifHeight());
|
|
||||||
intent.putExtra(EXTRA_BORDERLESS, viewHolder.image.isSticker());
|
|
||||||
setResult(RESULT_OK, intent);
|
|
||||||
finish();
|
|
||||||
} else {
|
|
||||||
Log.w(TAG, "Resolved Uri is no longer the selected element...");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class GiphyFragmentPagerAdapter extends FragmentPagerAdapter {
|
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
private final Fragment gifFragment;
|
|
||||||
private final Fragment stickerFragment;
|
|
||||||
|
|
||||||
private GiphyFragmentPagerAdapter(@NonNull Context context,
|
|
||||||
@NonNull FragmentManager fragmentManager,
|
|
||||||
@NonNull Fragment gifFragment,
|
|
||||||
@NonNull Fragment stickerFragment)
|
|
||||||
{
|
|
||||||
super(fragmentManager);
|
|
||||||
this.context = context.getApplicationContext();
|
|
||||||
this.gifFragment = gifFragment;
|
|
||||||
this.stickerFragment = stickerFragment;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Fragment getItem(int position) {
|
|
||||||
if (position == 0) return gifFragment;
|
|
||||||
else return stickerFragment;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getCount() {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CharSequence getPageTitle(int position) {
|
|
||||||
if (position == 0) return context.getString(R.string.GiphyFragmentPagerAdapter_gifs);
|
|
||||||
else return context.getString(R.string.GiphyFragmentPagerAdapter_stickers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,193 +0,0 @@
|
||||||
package org.thoughtcrime.securesms.giph.ui;
|
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.drawable.ColorDrawable;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.ProgressBar;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import com.bumptech.glide.RequestBuilder;
|
|
||||||
import com.bumptech.glide.load.DataSource;
|
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
|
||||||
import com.bumptech.glide.load.engine.GlideException;
|
|
||||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
|
|
||||||
import com.bumptech.glide.load.resource.gif.GifDrawable;
|
|
||||||
import com.bumptech.glide.request.RequestListener;
|
|
||||||
import com.bumptech.glide.request.target.Target;
|
|
||||||
import com.bumptech.glide.util.ByteBufferUtil;
|
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
|
||||||
import org.thoughtcrime.securesms.R;
|
|
||||||
import org.thoughtcrime.securesms.color.MaterialColor;
|
|
||||||
import org.thoughtcrime.securesms.giph.model.ChunkedImageUrl;
|
|
||||||
import org.thoughtcrime.securesms.giph.model.GiphyImage;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
|
|
||||||
|
|
||||||
class GiphyAdapter extends RecyclerView.Adapter<GiphyAdapter.GiphyViewHolder> {
|
|
||||||
|
|
||||||
private static final String TAG = Log.tag(GiphyAdapter.class);
|
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
private final GlideRequests glideRequests;
|
|
||||||
|
|
||||||
private List<GiphyImage> images;
|
|
||||||
private OnItemClickListener listener;
|
|
||||||
|
|
||||||
class GiphyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, RequestListener<Drawable> {
|
|
||||||
|
|
||||||
public AspectRatioImageView thumbnail;
|
|
||||||
public GiphyImage image;
|
|
||||||
public ProgressBar gifProgress;
|
|
||||||
public volatile boolean modelReady;
|
|
||||||
|
|
||||||
GiphyViewHolder(View view) {
|
|
||||||
super(view);
|
|
||||||
thumbnail = view.findViewById(R.id.thumbnail);
|
|
||||||
gifProgress = view.findViewById(R.id.gif_progress);
|
|
||||||
thumbnail.setOnClickListener(this);
|
|
||||||
gifProgress.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
if (listener != null) listener.onClick(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
|
|
||||||
synchronized (this) {
|
|
||||||
if (new ChunkedImageUrl(image.getGifUrl(), image.getGifSize()).equals(model)) {
|
|
||||||
this.modelReady = true;
|
|
||||||
notifyAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
|
|
||||||
synchronized (this) {
|
|
||||||
if (new ChunkedImageUrl(image.getGifUrl(), image.getGifSize()).equals(model)) {
|
|
||||||
this.modelReady = true;
|
|
||||||
notifyAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public byte[] getData(boolean forMms) throws ExecutionException, InterruptedException {
|
|
||||||
synchronized (this) {
|
|
||||||
while (!modelReady) {
|
|
||||||
Util.wait(this, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GifDrawable drawable = glideRequests.asGif()
|
|
||||||
.load(forMms ? new ChunkedImageUrl(image.getGifMmsUrl(), image.getMmsGifSize()) :
|
|
||||||
new ChunkedImageUrl(image.getGifUrl(), image.getGifSize()))
|
|
||||||
.submit(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
|
|
||||||
.get();
|
|
||||||
|
|
||||||
return ByteBufferUtil.toBytes(drawable.getBuffer());
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void setModelReady() {
|
|
||||||
this.modelReady = true;
|
|
||||||
notifyAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GiphyAdapter(@NonNull Context context, @NonNull GlideRequests glideRequests, @NonNull List<GiphyImage> images) {
|
|
||||||
this.context = context.getApplicationContext();
|
|
||||||
this.glideRequests = glideRequests;
|
|
||||||
this.images = images;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setImages(@NonNull List<GiphyImage> images) {
|
|
||||||
this.images = images;
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addImages(List<GiphyImage> images) {
|
|
||||||
this.images.addAll(images);
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NonNull GiphyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
|
||||||
View itemView = LayoutInflater.from(parent.getContext())
|
|
||||||
.inflate(R.layout.giphy_thumbnail, parent, false);
|
|
||||||
|
|
||||||
return new GiphyViewHolder(itemView);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(@NonNull GiphyViewHolder holder, int position) {
|
|
||||||
GiphyImage image = images.get(position);
|
|
||||||
|
|
||||||
holder.modelReady = false;
|
|
||||||
holder.image = image;
|
|
||||||
holder.thumbnail.setAspectRatio(image.getGifAspectRatio());
|
|
||||||
holder.gifProgress.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
RequestBuilder<Drawable> thumbnailRequest = GlideApp.with(context)
|
|
||||||
.load(new ChunkedImageUrl(image.getStillUrl(), image.getStillSize()))
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.ALL);
|
|
||||||
|
|
||||||
if (Util.isLowMemory(context)) {
|
|
||||||
glideRequests.load(new ChunkedImageUrl(image.getStillUrl(), image.getStillSize()))
|
|
||||||
.placeholder(new ColorDrawable(Util.getRandomElement(MaterialColor.values()).toConversationColor(context)))
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
|
||||||
.transition(DrawableTransitionOptions.withCrossFade())
|
|
||||||
.listener(holder)
|
|
||||||
.into(holder.thumbnail);
|
|
||||||
|
|
||||||
holder.setModelReady();
|
|
||||||
} else {
|
|
||||||
glideRequests.load(new ChunkedImageUrl(image.getGifUrl(), image.getGifSize()))
|
|
||||||
.thumbnail(thumbnailRequest)
|
|
||||||
.placeholder(new ColorDrawable(Util.getRandomElement(MaterialColor.values()).toConversationColor(context)))
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
|
||||||
.transition(DrawableTransitionOptions.withCrossFade())
|
|
||||||
.listener(holder)
|
|
||||||
.into(holder.thumbnail);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewRecycled(@NonNull GiphyViewHolder holder) {
|
|
||||||
super.onViewRecycled(holder);
|
|
||||||
glideRequests.clear(holder.thumbnail);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return images.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setListener(OnItemClickListener listener) {
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface OnItemClickListener {
|
|
||||||
void onClick(GiphyViewHolder viewHolder);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,119 +0,0 @@
|
||||||
package org.thoughtcrime.securesms.giph.ui;
|
|
||||||
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.ProgressBar;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.loader.app.LoaderManager;
|
|
||||||
import androidx.loader.content.Loader;
|
|
||||||
import androidx.recyclerview.widget.DefaultItemAnimator;
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
|
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
|
||||||
import org.thoughtcrime.securesms.LoggingFragment;
|
|
||||||
import org.thoughtcrime.securesms.R;
|
|
||||||
import org.thoughtcrime.securesms.giph.model.GiphyImage;
|
|
||||||
import org.thoughtcrime.securesms.giph.net.GiphyLoader;
|
|
||||||
import org.thoughtcrime.securesms.giph.util.InfiniteScrollListener;
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
|
||||||
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public abstract class GiphyFragment extends LoggingFragment implements LoaderManager.LoaderCallbacks<List<GiphyImage>>, GiphyAdapter.OnItemClickListener {
|
|
||||||
|
|
||||||
private static final String TAG = Log.tag(GiphyFragment.class);
|
|
||||||
|
|
||||||
private GiphyAdapter giphyAdapter;
|
|
||||||
private RecyclerView recyclerView;
|
|
||||||
private ProgressBar loadingProgress;
|
|
||||||
private TextView noResultsView;
|
|
||||||
private GiphyAdapter.OnItemClickListener listener;
|
|
||||||
|
|
||||||
protected String searchString;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
|
|
||||||
ViewGroup container = ViewUtil.inflate(inflater, viewGroup, R.layout.giphy_fragment);
|
|
||||||
this.recyclerView = container.findViewById(R.id.giphy_list);
|
|
||||||
this.loadingProgress = container.findViewById(R.id.loading_progress);
|
|
||||||
this.noResultsView = container.findViewById(R.id.no_results);
|
|
||||||
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityCreated(Bundle bundle) {
|
|
||||||
super.onActivityCreated(bundle);
|
|
||||||
|
|
||||||
this.giphyAdapter = new GiphyAdapter(getActivity(), GlideApp.with(this), new LinkedList<>());
|
|
||||||
this.giphyAdapter.setListener(this);
|
|
||||||
|
|
||||||
this.recyclerView.setLayoutManager(new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL));
|
|
||||||
this.recyclerView.setItemAnimator(new DefaultItemAnimator());
|
|
||||||
this.recyclerView.setAdapter(giphyAdapter);
|
|
||||||
this.recyclerView.addOnScrollListener(new GiphyScrollListener());
|
|
||||||
|
|
||||||
getLoaderManager().initLoader(0, null, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadFinished(@NonNull Loader<List<GiphyImage>> loader, @NonNull List<GiphyImage> data) {
|
|
||||||
this.loadingProgress.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
if (data.isEmpty()) noResultsView.setVisibility(View.VISIBLE);
|
|
||||||
else noResultsView.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
this.giphyAdapter.setImages(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoaderReset(@NonNull Loader<List<GiphyImage>> loader) {
|
|
||||||
noResultsView.setVisibility(View.GONE);
|
|
||||||
this.giphyAdapter.setImages(new LinkedList<GiphyImage>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setClickListener(GiphyAdapter.OnItemClickListener listener) {
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSearchString(@Nullable String searchString) {
|
|
||||||
this.searchString = searchString;
|
|
||||||
this.noResultsView.setVisibility(View.GONE);
|
|
||||||
this.getLoaderManager().restartLoader(0, null, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(GiphyAdapter.GiphyViewHolder viewHolder) {
|
|
||||||
if (listener != null) listener.onClick(viewHolder);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class GiphyScrollListener extends InfiniteScrollListener {
|
|
||||||
@Override
|
|
||||||
public void onLoadMore(final int currentPage) {
|
|
||||||
final Loader<List<GiphyImage>> loader = getLoaderManager().getLoader(0);
|
|
||||||
if (loader == null) return;
|
|
||||||
|
|
||||||
new AsyncTask<Void, Void, List<GiphyImage>>() {
|
|
||||||
@Override
|
|
||||||
protected List<GiphyImage> doInBackground(Void... params) {
|
|
||||||
return ((GiphyLoader)loader).loadPage(currentPage * GiphyLoader.PAGE_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void onPostExecute(List<GiphyImage> images) {
|
|
||||||
giphyAdapter.addImages(images);
|
|
||||||
}
|
|
||||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
package org.thoughtcrime.securesms.giph.ui;
|
|
||||||
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.loader.content.Loader;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.giph.model.GiphyImage;
|
|
||||||
import org.thoughtcrime.securesms.giph.net.GiphyStickerLoader;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class GiphyStickerFragment extends GiphyFragment {
|
|
||||||
@Override
|
|
||||||
public @NonNull Loader<List<GiphyImage>> onCreateLoader(int id, Bundle args) {
|
|
||||||
return new GiphyStickerLoader(getActivity(), searchString);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
// From https://gist.github.com/mipreamble/b6d4b3d65b0b4775a22e#file-recyclerviewpositionhelper-java
|
|
||||||
|
|
||||||
package org.thoughtcrime.securesms.giph.util;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
|
||||||
|
|
||||||
public abstract class InfiniteScrollListener extends RecyclerView.OnScrollListener {
|
|
||||||
|
|
||||||
public static String TAG = Log.tag(InfiniteScrollListener.class);
|
|
||||||
|
|
||||||
private int previousTotal = 0; // The total number of items in the dataset after the last load
|
|
||||||
private boolean loading = true; // True if we are still waiting for the last set of data to load.
|
|
||||||
private int visibleThreshold = 5; // The minimum amount of items to have below your current scroll position before loading more.
|
|
||||||
|
|
||||||
int firstVisibleItem, visibleItemCount, totalItemCount;
|
|
||||||
|
|
||||||
private int currentPage = 1;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
|
||||||
super.onScrolled(recyclerView, dx, dy);
|
|
||||||
|
|
||||||
RecyclerViewPositionHelper recyclerViewPositionHelper = RecyclerViewPositionHelper.createHelper(recyclerView);
|
|
||||||
|
|
||||||
visibleItemCount = recyclerView.getChildCount();
|
|
||||||
totalItemCount = recyclerViewPositionHelper.getItemCount();
|
|
||||||
firstVisibleItem = recyclerViewPositionHelper.findFirstVisibleItemPosition();
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
if (totalItemCount > previousTotal) {
|
|
||||||
loading = false;
|
|
||||||
previousTotal = totalItemCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!loading && (totalItemCount - visibleItemCount)
|
|
||||||
<= (firstVisibleItem + visibleThreshold)) {
|
|
||||||
// End has been reached
|
|
||||||
// Do something
|
|
||||||
currentPage++;
|
|
||||||
|
|
||||||
onLoadMore(currentPage);
|
|
||||||
|
|
||||||
loading = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void onLoadMore(int currentPage);
|
|
||||||
}
|
|
|
@ -1,116 +0,0 @@
|
||||||
// From https://gist.github.com/mipreamble/b6d4b3d65b0b4775a22e#file-recyclerviewpositionhelper-java
|
|
||||||
|
|
||||||
package org.thoughtcrime.securesms.giph.util;
|
|
||||||
|
|
||||||
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import androidx.recyclerview.widget.OrientationHelper;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
public class RecyclerViewPositionHelper {
|
|
||||||
|
|
||||||
final RecyclerView recyclerView;
|
|
||||||
final RecyclerView.LayoutManager layoutManager;
|
|
||||||
|
|
||||||
RecyclerViewPositionHelper(RecyclerView recyclerView) {
|
|
||||||
this.recyclerView = recyclerView;
|
|
||||||
this.layoutManager = recyclerView.getLayoutManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RecyclerViewPositionHelper createHelper(RecyclerView recyclerView) {
|
|
||||||
if (recyclerView == null) {
|
|
||||||
throw new NullPointerException("Recycler View is null");
|
|
||||||
}
|
|
||||||
return new RecyclerViewPositionHelper(recyclerView);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the adapter item count.
|
|
||||||
*
|
|
||||||
* @return The total number on items in a layout manager
|
|
||||||
*/
|
|
||||||
public int getItemCount() {
|
|
||||||
return layoutManager == null ? 0 : layoutManager.getItemCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the adapter position of the first visible view. This position does not include
|
|
||||||
* adapter changes that were dispatched after the last layout pass.
|
|
||||||
*
|
|
||||||
* @return The adapter position of the first visible item or {@link RecyclerView#NO_POSITION} if
|
|
||||||
* there aren't any visible items.
|
|
||||||
*/
|
|
||||||
public int findFirstVisibleItemPosition() {
|
|
||||||
final View child = findOneVisibleChild(0, layoutManager.getChildCount(), false, true);
|
|
||||||
return child == null ? RecyclerView.NO_POSITION : recyclerView.getChildAdapterPosition(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the adapter position of the first fully visible view. This position does not include
|
|
||||||
* adapter changes that were dispatched after the last layout pass.
|
|
||||||
*
|
|
||||||
* @return The adapter position of the first fully visible item or
|
|
||||||
* {@link RecyclerView#NO_POSITION} if there aren't any visible items.
|
|
||||||
*/
|
|
||||||
public int findFirstCompletelyVisibleItemPosition() {
|
|
||||||
final View child = findOneVisibleChild(0, layoutManager.getChildCount(), true, false);
|
|
||||||
return child == null ? RecyclerView.NO_POSITION : recyclerView.getChildAdapterPosition(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the adapter position of the last visible view. This position does not include
|
|
||||||
* adapter changes that were dispatched after the last layout pass.
|
|
||||||
*
|
|
||||||
* @return The adapter position of the last visible view or {@link RecyclerView#NO_POSITION} if
|
|
||||||
* there aren't any visible items
|
|
||||||
*/
|
|
||||||
public int findLastVisibleItemPosition() {
|
|
||||||
final View child = findOneVisibleChild(layoutManager.getChildCount() - 1, -1, false, true);
|
|
||||||
return child == null ? RecyclerView.NO_POSITION : recyclerView.getChildAdapterPosition(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the adapter position of the last fully visible view. This position does not include
|
|
||||||
* adapter changes that were dispatched after the last layout pass.
|
|
||||||
*
|
|
||||||
* @return The adapter position of the last fully visible view or
|
|
||||||
* {@link RecyclerView#NO_POSITION} if there aren't any visible items.
|
|
||||||
*/
|
|
||||||
public int findLastCompletelyVisibleItemPosition() {
|
|
||||||
final View child = findOneVisibleChild(layoutManager.getChildCount() - 1, -1, true, false);
|
|
||||||
return child == null ? RecyclerView.NO_POSITION : recyclerView.getChildAdapterPosition(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
View findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible,
|
|
||||||
boolean acceptPartiallyVisible) {
|
|
||||||
OrientationHelper helper;
|
|
||||||
if (layoutManager.canScrollVertically()) {
|
|
||||||
helper = OrientationHelper.createVerticalHelper(layoutManager);
|
|
||||||
} else {
|
|
||||||
helper = OrientationHelper.createHorizontalHelper(layoutManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
final int start = helper.getStartAfterPadding();
|
|
||||||
final int end = helper.getEndAfterPadding();
|
|
||||||
final int next = toIndex > fromIndex ? 1 : -1;
|
|
||||||
View partiallyVisible = null;
|
|
||||||
for (int i = fromIndex; i != toIndex; i += next) {
|
|
||||||
final View child = layoutManager.getChildAt(i);
|
|
||||||
final int childStart = helper.getDecoratedStart(child);
|
|
||||||
final int childEnd = helper.getDecoratedEnd(child);
|
|
||||||
if (childStart < end && childEnd > start) {
|
|
||||||
if (completelyVisible) {
|
|
||||||
if (childStart >= start && childEnd <= end) {
|
|
||||||
return child;
|
|
||||||
} else if (acceptPartiallyVisible && partiallyVisible == null) {
|
|
||||||
partiallyVisible = child;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return child;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return partiallyVisible;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
@ -18,39 +18,29 @@
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.giph.ui.GiphyActivityToolbar
|
<org.thoughtcrime.securesms.giph.ui.GiphyActivityToolbar
|
||||||
android:id="@+id/giphy_toolbar"
|
android:id="@+id/giphy_toolbar"
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
android:background="?attr/colorPrimary"
|
android:background="?attr/colorPrimary"
|
||||||
android:theme="?attr/actionBarStyle"
|
android:theme="?attr/actionBarStyle"
|
||||||
app:layout_scrollFlags="scroll|enterAlways"/>
|
app:layout_scrollFlags="scroll|enterAlways" />
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabLayout
|
|
||||||
app:tabBackground="@null"
|
|
||||||
app:tabTextColor="@color/signal_text_toolbar_subtitle"
|
|
||||||
app:tabSelectedTextColor="@color/signal_text_toolbar_title"
|
|
||||||
app:tabIndicatorColor="@color/signal_text_toolbar_title"
|
|
||||||
android:id="@+id/tab_layout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:theme="@style/TextSecure.Conversation.TabBar"
|
|
||||||
android:scrollbars="horizontal"/>
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<androidx.viewpager.widget.ViewPager
|
<FrameLayout
|
||||||
android:id="@+id/giphy_pager"
|
android:id="@+id/fragment_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
|
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
||||||
<ImageView android:id="@+id/giphy_logo"
|
<ImageView
|
||||||
android:src="@drawable/poweredby_giphy"
|
android:id="@+id/giphy_logo"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentBottom="true"
|
android:layout_alignParentBottom="true"
|
||||||
|
android:background="@color/black"
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
android:background="@color/black"/>
|
android:src="@drawable/poweredby_giphy" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
android:layout_width="0px"
|
android:layout_width="0px"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:layout_marginStart="5dp"
|
android:layout_marginStart="5dp"
|
||||||
android:hint="@string/giphy_activity_toolbar__search_gifs_and_stickers"
|
android:hint="@string/giphy_activity_toolbar__search_gifs"
|
||||||
android:textColor="@color/signal_text_toolbar_title"
|
android:textColor="@color/signal_text_toolbar_title"
|
||||||
android:textColorHint="@color/signal_text_toolbar_subtitle"
|
android:textColorHint="@color/signal_text_toolbar_subtitle"
|
||||||
android:textCursorDrawable="@null"
|
android:textCursorDrawable="@null"
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/giphy_list"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:scrollbars="vertical"/>
|
|
||||||
|
|
||||||
<ProgressBar android:id="@+id/loading_progress"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:visibility="visible"
|
|
||||||
android:indeterminate="true"/>
|
|
||||||
|
|
||||||
<TextView android:id="@+id/no_results"
|
|
||||||
android:text="@string/giphy_fragment__nothing_found"
|
|
||||||
android:gravity="center"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:visibility="gone"
|
|
||||||
android:layout_gravity="center"/>
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
|
@ -5,11 +5,29 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<!-- Will Inject N VideoViews @ runtime -->
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/giphy_parent"
|
android:id="@+id/giphy_parent"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
<androidx.core.widget.ContentLoadingProgressBar
|
||||||
|
android:id="@+id/content_loading"
|
||||||
|
style="?android:attr/progressBarStyleLarge"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:indeterminate="true" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/nothing_found"
|
||||||
|
style="@style/Signal.Text.Body"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/giphy_fragment__nothing_found"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/giphy_recycler"
|
android:id="@+id/giphy_recycler"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -18,5 +36,4 @@
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
tools:listitem="@layout/giphy_mp4" />
|
tools:listitem="@layout/giphy_mp4" />
|
||||||
|
|
||||||
<!-- Will Inject N VideoViews @ runtime -->
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.giph.ui.AspectRatioImageView
|
|
||||||
android:id="@+id/thumbnail"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:scaleType="fitXY" />
|
|
||||||
|
|
||||||
<ProgressBar android:id="@+id/gif_progress"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:indeterminate="true"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:visibility="visible"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:gravity="center"/>
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
|
@ -1993,7 +1993,7 @@
|
||||||
</plurals>
|
</plurals>
|
||||||
|
|
||||||
<!-- giphy_activity -->
|
<!-- giphy_activity -->
|
||||||
<string name="giphy_activity_toolbar__search_gifs_and_stickers">Search GIFs and stickers</string>
|
<string name="giphy_activity_toolbar__search_gifs">Search GIFs</string>
|
||||||
|
|
||||||
<!-- giphy_fragment -->
|
<!-- giphy_fragment -->
|
||||||
<string name="giphy_fragment__nothing_found">Nothing found</string>
|
<string name="giphy_fragment__nothing_found">Nothing found</string>
|
||||||
|
|
Ładowanie…
Reference in New Issue