Search in sources :

Example 1 with ClaimListAdapter

use of com.odysee.app.adapter.ClaimListAdapter in project odysee-android by OdyseeTeam.

the class FileViewFragment method loadRelatedContent.

private void loadRelatedContent() {
    // reset the list view
    View root = getView();
    if (fileClaim != null && root != null) {
        Context context = getContext();
        List<Claim> loadingPlaceholders = new ArrayList<>();
        int loadingPlaceholdersLength = Claim.TYPE_COLLECTION.equalsIgnoreCase(fileClaim.getValueType()) ? fileClaim.getClaimIds().size() : 15;
        for (int i = 0; i < loadingPlaceholdersLength; i++) {
            Claim placeholder = new Claim();
            placeholder.setLoadingPlaceholder(true);
            loadingPlaceholders.add(placeholder);
        }
        relatedContentAdapter = new ClaimListAdapter(loadingPlaceholders, context);
        relatedContentAdapter.setContextGroupId(FILE_CONTEXT_GROUP_ID);
        RecyclerView relatedContentList = root.findViewById(R.id.file_view_related_content_list);
        relatedContentList.setAdapter(relatedContentAdapter);
        ProgressBar relatedLoading = root.findViewById(R.id.file_view_related_content_progress);
        boolean canShowMatureContent = false;
        if (context != null) {
            SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
            canShowMatureContent = sp.getBoolean(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT, false);
        }
        if (!Claim.TYPE_COLLECTION.equalsIgnoreCase(fileClaim.getValueType())) {
            String title = fileClaim.getTitle();
            String claimId = fileClaim.getClaimId();
            final boolean nsfw = canShowMatureContent;
            relatedLoading.setVisibility(View.VISIBLE);
            // Making a request which explicitly uses a certain value form the amount of results needed
            // and no processing any possible exception, so using a callable instead of an AsyncTask
            // makes sense for all Android API Levels
            Thread t = new Thread(new Runnable() {

                @Override
                public void run() {
                    ExecutorService executor = Executors.newSingleThreadExecutor();
                    LighthouseSearch callable = new LighthouseSearch(title, RELATED_CONTENT_SIZE, 0, nsfw, claimId);
                    Future<List<Claim>> future = executor.submit(callable);
                    try {
                        List<Claim> result = future.get();
                        if (executor != null && !executor.isShutdown()) {
                            executor.shutdown();
                        }
                        MainActivity a = (MainActivity) getActivity();
                        if (a != null) {
                            a.runOnUiThread(new Runnable() {

                                @Override
                                public void run() {
                                    relatedContentRequestSuccedded(result);
                                    relatedLoading.setVisibility(View.GONE);
                                }
                            });
                        }
                    } catch (InterruptedException | ExecutionException e) {
                        if (executor != null && !executor.isShutdown()) {
                            executor.shutdown();
                        }
                        e.printStackTrace();
                    }
                }
            });
            t.start();
        } else {
            TextView relatedOrPlayList = root.findViewById(R.id.related_or_playlist);
            relatedOrPlayList.setText(fileClaim.getTitle());
            relatedOrPlayList.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_cast_connected, 0, 0, 0);
            relatedOrPlayList.setPadding(0, 0, 0, 16);
            relatedOrPlayList.setTypeface(null, Typeface.BOLD);
            Map<String, Object> claimSearchOptions = new HashMap<>(3);
            claimSearchOptions.put("claim_ids", fileClaim.getClaimIds());
            claimSearchOptions.put("not_tags", canShowMatureContent ? null : new ArrayList<>(Predefined.MATURE_TAGS));
            claimSearchOptions.put("page_size", fileClaim.getClaimIds().size());
            ExecutorService executor = Executors.newSingleThreadExecutor();
            Future<List<Claim>> future = executor.submit(new Search(claimSearchOptions));
            try {
                List<Claim> playlistClaimItems = future.get();
                if (playlistClaimItems != null) {
                    relatedContentAdapter.setItems(playlistClaimItems);
                    relatedContentAdapter.setListener(FileViewFragment.this);
                    View v = getView();
                    if (v != null) {
                        relatedContentList.setAdapter(relatedContentAdapter);
                        relatedContentAdapter.notifyDataSetChanged();
                        Helper.setViewVisibility(v.findViewById(R.id.file_view_no_related_content), relatedContentAdapter == null || relatedContentAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
                    }
                    scrollToCommentHash();
                }
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
}
Also used : LinkedHashMap(java.util.LinkedHashMap) HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) MainActivity(com.odysee.app.MainActivity) ClaimListAdapter(com.odysee.app.adapter.ClaimListAdapter) LighthouseSearch(com.odysee.app.callable.LighthouseSearch) Search(com.odysee.app.callable.Search) LighthouseSearch(com.odysee.app.callable.LighthouseSearch) ArrayList(java.util.ArrayList) List(java.util.List) TextView(android.widget.TextView) ExecutionException(java.util.concurrent.ExecutionException) ProgressBar(android.widget.ProgressBar) TrackSelectionOverride(com.google.android.exoplayer2.trackselection.TrackSelectionOverrides.TrackSelectionOverride) AttributeProviderContext(org.commonmark.renderer.html.AttributeProviderContext) Context(android.content.Context) SharedPreferences(android.content.SharedPreferences) SolidIconView(com.odysee.app.ui.controls.SolidIconView) PlayerView(com.google.android.exoplayer2.ui.PlayerView) NestedScrollView(androidx.core.widget.NestedScrollView) AdapterView(android.widget.AdapterView) RecyclerView(androidx.recyclerview.widget.RecyclerView) PhotoView(com.github.chrisbanes.photoview.PhotoView) ImageView(android.widget.ImageView) View(android.view.View) WebView(android.webkit.WebView) TextView(android.widget.TextView) SuppressLint(android.annotation.SuppressLint) AnyThread(androidx.annotation.AnyThread) ScheduledExecutorService(java.util.concurrent.ScheduledExecutorService) ExecutorService(java.util.concurrent.ExecutorService) Future(java.util.concurrent.Future) CompletableFuture(java.util.concurrent.CompletableFuture) ScheduledFuture(java.util.concurrent.ScheduledFuture) RecyclerView(androidx.recyclerview.widget.RecyclerView) JSONObject(org.json.JSONObject) Claim(com.odysee.app.model.Claim)

Example 2 with ClaimListAdapter

use of com.odysee.app.adapter.ClaimListAdapter in project odysee-android by OdyseeTeam.

the class FileViewFragment method resolvePlaylistClaimsAndPlayFirst.

private void resolvePlaylistClaimsAndPlayFirst() {
    if (playlistResolved) {
        return;
    }
    Helper.setViewVisibility(layoutLoadingState, View.VISIBLE);
    Helper.setViewVisibility(layoutResolving, View.VISIBLE);
    Helper.setViewVisibility(layoutNothingAtLocation, View.GONE);
    Helper.setViewVisibility(layoutDisplayArea, View.GONE);
    Map<String, Object> options = new HashMap<>();
    options.put("claim_type", "stream");
    options.put("page", 1);
    options.put("page_size", 999);
    options.put("claim_ids", fileClaim.getClaimIds());
    ClaimSearchTask task = new ClaimSearchTask(options, Lbry.API_CONNECTION_STRING, null, new ClaimSearchResultHandler() {

        @Override
        public void onSuccess(List<Claim> claims, boolean hasReachedEnd) {
            playlistResolved = true;
            // reorder the claims based on the order in the list, TODO: find a more efficient way to do this
            Map<String, Claim> playlistClaimMap = new LinkedHashMap<>();
            List<String> claimIds = fileClaim.getClaimIds();
            for (String id : claimIds) {
                for (Claim claim : claims) {
                    if (id.equalsIgnoreCase(claim.getClaimId())) {
                        playlistClaimMap.put(id, claim);
                        break;
                    }
                }
            }
            playlistClaims = new ArrayList<>(playlistClaimMap.values());
            if (playlistClaims.size() > 0) {
                playClaimFromCollection(playlistClaims.get(0), 0);
            }
            relatedContentAdapter = new ClaimListAdapter(playlistClaims, getContext());
            relatedContentAdapter.setListener(FileViewFragment.this);
            View root = getView();
            if (root != null) {
                RecyclerView relatedContentList = root.findViewById(R.id.file_view_related_content_list);
                relatedContentList.setAdapter(relatedContentAdapter);
            }
        }

        @Override
        public void onError(Exception error) {
            showError(getString(R.string.comment_error));
        }
    });
    task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
Also used : LinkedHashMap(java.util.LinkedHashMap) HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) ClaimListAdapter(com.odysee.app.adapter.ClaimListAdapter) SolidIconView(com.odysee.app.ui.controls.SolidIconView) PlayerView(com.google.android.exoplayer2.ui.PlayerView) NestedScrollView(androidx.core.widget.NestedScrollView) AdapterView(android.widget.AdapterView) RecyclerView(androidx.recyclerview.widget.RecyclerView) PhotoView(com.github.chrisbanes.photoview.PhotoView) ImageView(android.widget.ImageView) View(android.view.View) WebView(android.webkit.WebView) TextView(android.widget.TextView) LbryRequestException(com.odysee.app.exceptions.LbryRequestException) JSONException(org.json.JSONException) LbryUriException(com.odysee.app.exceptions.LbryUriException) IOException(java.io.IOException) ExecutionException(java.util.concurrent.ExecutionException) LbryResponseException(com.odysee.app.exceptions.LbryResponseException) LbryioRequestException(com.odysee.app.exceptions.LbryioRequestException) LbryioResponseException(com.odysee.app.exceptions.LbryioResponseException) ApiCallException(com.odysee.app.exceptions.ApiCallException) ClaimSearchTask(com.odysee.app.tasks.claim.ClaimSearchTask) ClaimSearchResultHandler(com.odysee.app.tasks.claim.ClaimSearchResultHandler) JSONObject(org.json.JSONObject) ArrayList(java.util.ArrayList) List(java.util.List) RecyclerView(androidx.recyclerview.widget.RecyclerView) TrackSelectionOverride(com.google.android.exoplayer2.trackselection.TrackSelectionOverrides.TrackSelectionOverride) Map(java.util.Map) LinkedHashMap(java.util.LinkedHashMap) HashMap(java.util.HashMap) Claim(com.odysee.app.model.Claim)

Example 3 with ClaimListAdapter

use of com.odysee.app.adapter.ClaimListAdapter in project odysee-android by OdyseeTeam.

the class FollowingFragment method fetchClaimSearchContent.

private void fetchClaimSearchContent(boolean reset) {
    if (reset && contentListAdapter != null) {
        contentListAdapter.clearItems();
        currentClaimSearchPage = 1;
    }
    contentClaimSearchLoading = true;
    Helper.setViewVisibility(noContentView, View.GONE);
    Map<String, Object> claimSearchOptions = buildContentOptions();
    contentClaimSearchTask = new ClaimSearchTask(claimSearchOptions, Lbry.API_CONNECTION_STRING, getLoadingView(), new ClaimSearchResultHandler() {

        @Override
        public void onSuccess(List<Claim> claims, boolean hasReachedEnd) {
            claims = Helper.filterClaimsByOutpoint(claims);
            claims = Helper.filterClaimsByBlockedChannels(claims, Lbryio.blockedChannels);
            Date d = new Date();
            Calendar cal = new GregorianCalendar();
            cal.setTime(d);
            // Remove claims with a release time in the future
            claims.removeIf(e -> {
                Claim.GenericMetadata metadata = e.getValue();
                return metadata instanceof Claim.StreamMetadata && (((Claim.StreamMetadata) metadata).getReleaseTime()) > (cal.getTimeInMillis() / 1000L);
            });
            // Sort claims so those which are livestreaming now are shwon on the top of the list
            Collections.sort(claims, new Comparator<Claim>() {

                @Override
                public int compare(Claim claim, Claim t1) {
                    if (claim.isLive() && !t1.isLive())
                        return -1;
                    else if (!claim.isLive() && t1.isLive())
                        return 1;
                    else
                        return 0;
                }
            });
            if (contentListAdapter == null) {
                Context context = getContext();
                if (context != null) {
                    contentListAdapter = new ClaimListAdapter(claims, context);
                    contentListAdapter.setListener(new ClaimListAdapter.ClaimListItemListener() {

                        @Override
                        public void onClaimClicked(Claim claim, int position) {
                            Context context = getContext();
                            if (context instanceof MainActivity) {
                                MainActivity activity = (MainActivity) context;
                                if (claim.getName().startsWith("@")) {
                                    // channel claim
                                    activity.openChannelClaim(claim);
                                } else {
                                    activity.openFileClaim(claim);
                                }
                            }
                        }
                    });
                }
            } else {
                contentListAdapter.addItems(claims);
            }
            if (contentList != null && contentList.getAdapter() == null) {
                contentList.setAdapter(contentListAdapter);
            }
            contentHasReachedEnd = hasReachedEnd;
            contentClaimSearchLoading = false;
            checkNoContent(false);
        }

        @Override
        public void onError(Exception error) {
            contentClaimSearchLoading = false;
            checkNoContent(false);
        }
    });
    contentClaimSearchTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
Also used : Context(android.content.Context) GregorianCalendar(java.util.GregorianCalendar) Calendar(java.util.Calendar) GregorianCalendar(java.util.GregorianCalendar) MainActivity(com.odysee.app.MainActivity) ClaimListAdapter(com.odysee.app.adapter.ClaimListAdapter) Date(java.util.Date) JSONException(org.json.JSONException) LbryUriException(com.odysee.app.exceptions.LbryUriException) ClaimSearchTask(com.odysee.app.tasks.claim.ClaimSearchTask) ClaimSearchResultHandler(com.odysee.app.tasks.claim.ClaimSearchResultHandler) JSONObject(org.json.JSONObject) List(java.util.List) ArrayList(java.util.ArrayList) Claim(com.odysee.app.model.Claim)

Example 4 with ClaimListAdapter

use of com.odysee.app.adapter.ClaimListAdapter in project odysee-android by OdyseeTeam.

the class SearchFragment method search.

public void search(String query, int from) {
    boolean queryChanged = checkQuery(query);
    if (query.equals("")) {
        return;
    }
    if (!queryChanged && from > 0) {
        currentFrom = from;
    }
    if (queryChanged) {
        logSearch(query);
    }
    searchLoading = true;
    Context context = getContext();
    boolean canShowMatureContent = false;
    if (context != null) {
        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
        canShowMatureContent = sp.getBoolean(MainActivity.PREFERENCE_KEY_SHOW_MATURE_CONTENT, false);
    }
    // modify the request so it returns channels on top
    if (currentQuery != null) {
        final String[] split = currentQuery.split(" ");
        if (split.length == 1 && !currentQuery.startsWith("@")) {
            currentQuery = "@".concat(query);
        }
    }
    Activity a = getActivity();
    if (a != null) {
        a.runOnUiThread(new Runnable() {

            @Override
            public void run() {
                loadingView.setVisibility(View.VISIBLE);
            }
        });
    }
    ExecutorService executor = Executors.newSingleThreadExecutor();
    Callable<List<Claim>> c = new LighthouseSearch(currentQuery, PAGE_SIZE, currentFrom, canShowMatureContent, null);
    Thread t = new Thread(new Runnable() {

        @Override
        public void run() {
            Future<List<Claim>> future = executor.submit(c);
            try {
                List<Claim> results = future.get();
                List<Claim> sanitizedClaims = new ArrayList<>(results.size());
                for (Claim item : results) {
                    if (!item.getValueType().equalsIgnoreCase(Claim.TYPE_REPOST)) {
                        sanitizedClaims.add(item);
                    }
                }
                if (a != null) {
                    a.runOnUiThread(new Runnable() {

                        @Override
                        public void run() {
                            Context context = getContext();
                            if (context != null) {
                                if (resultListAdapter == null) {
                                    resultListAdapter = new ClaimListAdapter(sanitizedClaims, context);
                                    resultListAdapter.setContextGroupId(SEARCH_CONTEXT_GROUP_ID);
                                    resultListAdapter.addFeaturedItem(buildFeaturedItem(query));
                                    resolveFeaturedItem(buildVanityUrl(query));
                                    resultListAdapter.setListener(SearchFragment.this);
                                    if (resultList != null) {
                                        resultList.setAdapter(resultListAdapter);
                                    }
                                } else {
                                    resultList.setVisibility(View.VISIBLE);
                                    resultListAdapter.addItems(sanitizedClaims);
                                }
                                resultListAdapter.filterBlockedChannels(Lbryio.blockedChannels);
                                checkNothingToBeShown();
                            }
                        }
                    });
                }
                // Lighthouse doesn't return "valueType" of the claim, so another request is needed
                // to determine if an item is a playlist and get the items on the playlist.
                List<String> claimIds = new ArrayList<>();
                for (Claim sanitizedClaim : sanitizedClaims) {
                    if (!sanitizedClaim.getValueType().equalsIgnoreCase(Claim.TYPE_CHANNEL)) {
                        claimIds.add(sanitizedClaim.getClaimId());
                    }
                }
                Map<String, Object> claimSearchOptions = new HashMap<>(2);
                claimSearchOptions.put("claim_ids", claimIds);
                claimSearchOptions.put("page_size", claimIds.size());
                Future<List<Claim>> futureSearch = executor.submit(new Search(claimSearchOptions));
                List<Claim> totalResults = futureSearch.get();
                // For each claim returned from Lighthouse, replace it by the one using Search API
                for (int i = 0; i < sanitizedClaims.size(); i++) {
                    if (!Claim.TYPE_CHANNEL.equalsIgnoreCase(sanitizedClaims.get(i).getValueType())) {
                        int finalI = i;
                        Claim found = totalResults.stream().filter(filteredClaim -> {
                            return sanitizedClaims.get(finalI).getClaimId().equalsIgnoreCase(filteredClaim.getClaimId());
                        }).findAny().orElse(null);
                        if (found != null) {
                            sanitizedClaims.set(i, found);
                            if (a != null && resultListAdapter != null) {
                                a.runOnUiThread(new Runnable() {

                                    @Override
                                    public void run() {
                                        if (!found.getValueType().equalsIgnoreCase(Claim.TYPE_REPOST)) {
                                            resultListAdapter.setItem(found.getClaimId(), found);
                                        } else {
                                            resultListAdapter.removeItem(found);
                                        }
                                    }
                                });
                            }
                        }
                    }
                }
                contentHasReachedEnd = results.size() < PAGE_SIZE;
                searchLoading = false;
                if (a != null) {
                    a.runOnUiThread(new Runnable() {

                        @Override
                        public void run() {
                            loadingView.setVisibility(View.GONE);
                            int itemCount = resultListAdapter == null ? 0 : resultListAdapter.getItemCount();
                            if (itemCount == 0) {
                                filterLink.setVisibility(View.GONE);
                            } else {
                                filterLink.setVisibility(View.VISIBLE);
                            }
                        }
                    });
                }
            } catch (ExecutionException | InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (!executor.isShutdown()) {
                    executor.shutdown();
                }
            }
        }
    });
    t.start();
}
Also used : Context(android.content.Context) Context(android.content.Context) Chip(com.google.android.material.chip.Chip) java.util(java.util) Setter(lombok.Setter) Bundle(android.os.Bundle) ResolveTask(com.odysee.app.tasks.claim.ResolveTask) NonNull(androidx.annotation.NonNull) ClaimListResultHandler(com.odysee.app.tasks.claim.ClaimListResultHandler) Lbry(com.odysee.app.utils.Lbry) ChipGroup(com.google.android.material.chip.ChipGroup) MenuItem(android.view.MenuItem) LighthouseSearch(com.odysee.app.callable.LighthouseSearch) AppCompatSpinner(androidx.appcompat.widget.AppCompatSpinner) JSONException(org.json.JSONException) JSONObject(org.json.JSONObject) MainActivity(com.odysee.app.MainActivity) LbryUri(com.odysee.app.utils.LbryUri) View(android.view.View) Search(com.odysee.app.callable.Search) LbryFile(com.odysee.app.model.LbryFile) RecyclerView(androidx.recyclerview.widget.RecyclerView) ContextCompat(androidx.core.content.ContextCompat) AsyncTask(android.os.AsyncTask) LayoutInflater(android.view.LayoutInflater) java.util.concurrent(java.util.concurrent) Helper(com.odysee.app.utils.Helper) ClaimListAdapter(com.odysee.app.adapter.ClaimListAdapter) Claim(com.odysee.app.model.Claim) ViewGroup(android.view.ViewGroup) Lbryio(com.odysee.app.utils.Lbryio) DownloadActionListener(com.odysee.app.listener.DownloadActionListener) ClaimCacheKey(com.odysee.app.model.ClaimCacheKey) SharedPreferences(android.content.SharedPreferences) android.widget(android.widget) BaseFragment(com.odysee.app.ui.BaseFragment) LbryAnalytics(com.odysee.app.utils.LbryAnalytics) PreferenceManager(androidx.preference.PreferenceManager) LinearLayoutManager(androidx.recyclerview.widget.LinearLayoutManager) Activity(android.app.Activity) R(com.odysee.app.R) SharedPreferences(android.content.SharedPreferences) MainActivity(com.odysee.app.MainActivity) Activity(android.app.Activity) ClaimListAdapter(com.odysee.app.adapter.ClaimListAdapter) LighthouseSearch(com.odysee.app.callable.LighthouseSearch) LighthouseSearch(com.odysee.app.callable.LighthouseSearch) Search(com.odysee.app.callable.Search) Claim(com.odysee.app.model.Claim)

Example 5 with ClaimListAdapter

use of com.odysee.app.adapter.ClaimListAdapter in project odysee-android by OdyseeTeam.

the class FileViewFragment method handlePlayCollection.

private void handlePlayCollection(Map<String, Object> params) {
    OdyseeCollection collection = (OdyseeCollection) params.get("collection");
    playlistClaims = new ArrayList<>(collection.getClaims());
    playlistResolved = true;
    currentPlaylistTitle = collection.getName();
    relatedContentAdapter = new ClaimListAdapter(playlistClaims, getContext());
    relatedContentAdapter.setListener(FileViewFragment.this);
    View root = getView();
    if (root != null) {
        RecyclerView relatedContentList = root.findViewById(R.id.file_view_related_content_list);
        relatedContentList.setAdapter(relatedContentAdapter);
    }
    if (playlistClaims.size() > 0) {
        if (params.containsKey("item") && params.containsKey("itemIndex")) {
            int index = (int) params.get("itemIndex");
            playClaimFromCollection(playlistClaims.get(index), index);
        } else {
            playClaimFromCollection(playlistClaims.get(0), 0);
        }
    }
}
Also used : RecyclerView(androidx.recyclerview.widget.RecyclerView) OdyseeCollection(com.odysee.app.model.OdyseeCollection) ClaimListAdapter(com.odysee.app.adapter.ClaimListAdapter) SolidIconView(com.odysee.app.ui.controls.SolidIconView) PlayerView(com.google.android.exoplayer2.ui.PlayerView) NestedScrollView(androidx.core.widget.NestedScrollView) AdapterView(android.widget.AdapterView) RecyclerView(androidx.recyclerview.widget.RecyclerView) PhotoView(com.github.chrisbanes.photoview.PhotoView) ImageView(android.widget.ImageView) View(android.view.View) WebView(android.webkit.WebView) TextView(android.widget.TextView) SuppressLint(android.annotation.SuppressLint)

Aggregations

ClaimListAdapter (com.odysee.app.adapter.ClaimListAdapter)13 Claim (com.odysee.app.model.Claim)12 Context (android.content.Context)11 MainActivity (com.odysee.app.MainActivity)11 ArrayList (java.util.ArrayList)7 List (java.util.List)7 JSONObject (org.json.JSONObject)6 View (android.view.View)5 RecyclerView (androidx.recyclerview.widget.RecyclerView)5 ClaimSearchResultHandler (com.odysee.app.tasks.claim.ClaimSearchResultHandler)5 ClaimSearchTask (com.odysee.app.tasks.claim.ClaimSearchTask)5 JSONException (org.json.JSONException)5 WebView (android.webkit.WebView)3 AdapterView (android.widget.AdapterView)3 ImageView (android.widget.ImageView)3 TextView (android.widget.TextView)3 NestedScrollView (androidx.core.widget.NestedScrollView)3 PhotoView (com.github.chrisbanes.photoview.PhotoView)3 PlayerView (com.google.android.exoplayer2.ui.PlayerView)3 ClaimListResultHandler (com.odysee.app.tasks.claim.ClaimListResultHandler)3