added invidous support for youtube queries

also added search suggestions
This commit is contained in:
Denis Çerri 2023-01-01 20:04:34 +01:00
parent 8dff21bc8a
commit 4b5dc1fb40
No known key found for this signature in database
GPG key ID: 96B3554AF5B193EE
10 changed files with 324 additions and 86 deletions

View file

@ -320,7 +320,7 @@
<PersistentState>
<option name="values">
<map>
<entry key="url" value="file:/$USER_HOME$/AppData/Local/Android/Sdk/icons/material/materialicons/file_open/baseline_file_open_24.xml" />
<entry key="url" value="jar:file:/C:/Program%20Files/Android/Android%20Studio/plugins/android/lib/android.jar!/images/material/icons/materialicons/arrow_upward/baseline_arrow_upward_24.xml" />
</map>
</option>
</PersistentState>
@ -331,7 +331,7 @@
<option name="values">
<map>
<entry key="color" value="00a6ff" />
<entry key="outputName" value="ic_baseline_file_open_24" />
<entry key="outputName" value="ic_arrow_up" />
<entry key="sourceFile" value="C:\Users\denis\Desktop\adaptiveproduct_youtube_foreground_color_108 (1).svg" />
</map>
</option>

View file

@ -18,6 +18,7 @@
android:supportsRtl="true"
android:theme="@style/AppDefaultTheme" >
<activity android:name=".MainActivity"
android:windowSoftInputMode="adjustResize"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View file

@ -228,6 +228,7 @@ public class DownloaderService extends Service {
});
}
}catch (Exception ignored){}
onDestroy();
}
private void onDownloadEnd(){

View file

@ -14,9 +14,13 @@ import android.os.Bundle;
import android.os.IBinder;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.view.WindowInsets;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
@ -134,6 +138,14 @@ public class MainActivity extends AppCompatActivity{
return true;
});
getWindow().getDecorView().setOnApplyWindowInsetsListener((view, windowInsets) -> {
WindowInsetsCompat windowInsetsCompat = WindowInsetsCompat.toWindowInsetsCompat(windowInsets, view);
boolean isImeVisible = windowInsetsCompat.isVisible(WindowInsetsCompat.Type.ime());
binding.bottomNavigationView.setVisibility(isImeVisible ? View.GONE : View.VISIBLE);
view.onApplyWindowInsets(windowInsets);
return windowInsets;
});
askPermissions();
Intent intent = getIntent();

View file

@ -21,7 +21,12 @@ import android.view.ViewGroup;
import android.view.Window;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import androidx.appcompat.widget.SearchView;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
@ -44,6 +49,9 @@ import com.google.android.material.floatingactionbutton.ExtendedFloatingActionBu
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.progressindicator.LinearProgressIndicator;
import com.google.android.material.textfield.TextInputLayout;
import com.google.android.material.textview.MaterialTextView;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.regex.Matcher;
@ -59,6 +67,8 @@ public class HomeFragment extends Fragment implements HomeRecyclerViewAdapter.On
private int inputQueriesLength = 0;
private RecyclerView recyclerView;
private HomeRecyclerViewAdapter homeRecyclerViewAdapter;
private ScrollView searchSuggestions;
private LinearLayout searchSuggestionsLinearLayout;
private ShimmerFrameLayout shimmerCards;
private CoordinatorLayout downloadFabs;
private CoordinatorLayout downloadAllFab;
@ -220,6 +230,8 @@ public class HomeFragment extends Fragment implements HomeRecyclerViewAdapter.On
shimmerCards = fragmentView.findViewById(R.id.shimmer_results_framelayout);
topAppBar = fragmentView.findViewById(R.id.home_toolbar);
recyclerView = fragmentView.findViewById(R.id.recycler_view_home);
searchSuggestions = fragmentView.findViewById(R.id.search_suggestions_scroll_view);
searchSuggestionsLinearLayout = fragmentView.findViewById(R.id.search_suggestions_linear_layout);
homeRecyclerViewAdapter = new HomeRecyclerViewAdapter(resultObjects, this, activity);
recyclerView.setAdapter(homeRecyclerViewAdapter);
@ -347,6 +359,7 @@ public class HomeFragment extends Fragment implements HomeRecyclerViewAdapter.On
public boolean onMenuItemActionExpand(MenuItem menuItem) {
homeFabs.setVisibility(View.GONE);
recyclerView.setVisibility(View.GONE);
searchSuggestions.setVisibility(View.VISIBLE);
return true;
}
@ -354,6 +367,7 @@ public class HomeFragment extends Fragment implements HomeRecyclerViewAdapter.On
public boolean onMenuItemActionCollapse(MenuItem menuItem) {
homeFabs.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.VISIBLE);
searchSuggestions.setVisibility(View.GONE);
return true;
}
};
@ -368,6 +382,7 @@ public class HomeFragment extends Fragment implements HomeRecyclerViewAdapter.On
searchView.setQueryHint(getString(R.string.search_hint));
dbManager = new DBManager(context);
infoUtil = new InfoUtil(context);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
@ -394,7 +409,25 @@ public class HomeFragment extends Fragment implements HomeRecyclerViewAdapter.On
}
@Override
public boolean onQueryTextChange(String newText) { return false; }
public boolean onQueryTextChange(String newText) {
searchSuggestionsLinearLayout.removeAllViews();
if (newText.isEmpty()) return false;
Thread thread = new Thread(() -> {
ArrayList<String> suggestions = infoUtil.getSearchSuggestions(newText);
for (int i = 0; i < suggestions.size();i++){
View v = LayoutInflater.from(fragmentContext).inflate(R.layout.search_suggestion_item, null);
TextView textView = v.findViewById(R.id.suggestion_text);
textView.setText(suggestions.get(i));
new Handler(Looper.getMainLooper()).post(() -> searchSuggestionsLinearLayout.addView(v));
textView.setOnClickListener(view -> onQueryTextSubmit(textView.getText().toString()));
MaterialButton mb = v.findViewById(R.id.set_search_query_button);
mb.setOnClickListener(view -> searchView.setQuery(textView.getText(), false));
}
});
thread.start();
return false;
}
});
topAppBar.setOnClickListener(view -> scrollToTop());

View file

@ -28,9 +28,11 @@ import java.util.Locale;
public class InfoUtil {
private static final String TAG = "API MANAGER";
private static String countryCODE = "US";
private static final String invidousURL = "https://invidious.baczek.me/api/v1/";
private static String countryCODE;
private ArrayList<Video> videos;
private String key;
private boolean useInvidous;
private DBManager dbManager;
public InfoUtil(Context context) {
@ -43,22 +45,33 @@ public class InfoUtil {
JSONObject country = genericRequest("https://ipwho.is/");
try{
countryCODE = country.getString("country_code");
}catch(Exception ignored){}
}catch(Exception e){
countryCODE = "US";
}
JSONObject res = genericRequest(invidousURL + "stats");
if(res.length() == 0) useInvidous = false;
else useInvidous = true;
});
thread.start();
thread.join();
}catch(Exception ignored){
Toast.makeText(context, "Couldn't find API Key for the request", Toast.LENGTH_SHORT).show();
}catch(Exception e){
e.printStackTrace();
}
videos = new ArrayList<>();
}
public ArrayList<Video> search(String query) throws JSONException{
if (key.isEmpty()) return getFromYTDL(query);
videos = new ArrayList<>();
if (key.isEmpty()) {
if(useInvidous) return searchFromInvidous(query);
else return getFromYTDL(query);
}
return searchFromKey(query);
}
public ArrayList<Video> searchFromKey(String query) throws JSONException{
//short data
JSONObject res = genericRequest("https://youtube.googleapis.com/youtube/v3/search?part=snippet&q="+query+"&maxResults=25&regionCode="+countryCODE+"&key="+key);
if (!res.has("items")) return getFromYTDL(query);
@ -91,7 +104,7 @@ public class InfoUtil {
}
snippet.put("duration", duration);
snippet = fixThumbnail(snippet);
fixThumbnail(snippet);
Video v = createVideofromJSON(snippet);
if(v == null || v.getThumb().isEmpty()){
@ -105,6 +118,32 @@ public class InfoUtil {
return videos;
}
public ArrayList<Video> searchFromInvidous(String query) throws JSONException{
JSONObject res = genericRequest(invidousURL + "search?q="+query);
if(res.length() == 0) return getFromYTDL(query);
JSONArray dataArray = res.getJSONArray("");
int j = 0;
for(int i = 0; i < dataArray.length(); i++){
JSONObject element = dataArray.getJSONObject(i);
String duration = formatIntegerDuration(element.getInt("lengthSeconds"));
if(duration.equals("0:00")){
continue;
}
element.put("duration", duration);
element.put("thumb", element.getJSONArray("videoThumbnails").getJSONObject(0).getString("url"));
Video v = createVideofromInvidiousJSON(element);
if(v == null || v.getThumb().isEmpty()){
continue;
}
videos.add(v);
}
return videos;
}
public PlaylistTuple getPlaylist(String id, String nextPageToken) throws JSONException{
if (key.isEmpty()) return new PlaylistTuple("", getFromYTDL("https://www.youtube.com/playlist?list="+id));
videos = new ArrayList<>();
@ -136,7 +175,7 @@ public class InfoUtil {
String duration = extra.getJSONArray("items").getJSONObject(j).getJSONObject("contentDetails").getString("duration");
duration = formatDuration(duration);
snippet.put("duration", duration);
snippet = fixThumbnail(snippet);
fixThumbnail(snippet);
Video v = createVideofromJSON(snippet);
if(v == null || v.getThumb().isEmpty()){
@ -152,9 +191,16 @@ public class InfoUtil {
return new PlaylistTuple(next, videos);
}
public Video getVideo(String id) throws JSONException {
if (key.isEmpty()) return getFromYTDL("https://www.youtube.com/watch?v="+id).get(0);
if (key.isEmpty()) {
if (useInvidous){
JSONObject res = genericRequest(invidousURL + "videos/"+id);
if (res.length() == 0) return getFromYTDL("https://www.youtube.com/watch?v="+id).get(0);
return createVideofromInvidiousJSON(res);
}else{
return getFromYTDL("https://www.youtube.com/watch?v="+id).get(0);
}
}
//short data
JSONObject res = genericRequest("https://youtube.googleapis.com/youtube/v3/videos?part=snippet,contentDetails&id="+id+"&key="+key);
@ -166,10 +212,9 @@ public class InfoUtil {
res.put("videoID", id);
res.put("duration", duration);
res = fixThumbnail(res);
fixThumbnail(res);
Video v = createVideofromJSON(res);
return v;
return createVideofromJSON(res);
}
private Video createVideofromJSON(JSONObject obj){
@ -198,6 +243,31 @@ public class InfoUtil {
return video;
}
private Video createVideofromInvidiousJSON(JSONObject obj){
Video video = null;
try{
String id = obj.getString("videoId");
String title = obj.getString("title");
title = Html.fromHtml(title).toString();
String author = obj.getString("author");
author = Html.fromHtml(author).toString();
String duration = formatIntegerDuration(obj.getInt("lengthSeconds"));
String thumb = "https://i.ytimg.com/vi/"+id+"/hqdefault.jpg";
String url = "https://www.youtube.com/watch?v=" + id;
int downloadedAudio = dbManager.checkDownloaded(url, "audio");
int downloadedVideo = dbManager.checkDownloaded(url, "video");
int isPlaylist = 0;
video = new Video(id, url, title, author, duration, thumb, downloadedAudio, downloadedVideo, isPlaylist, "youtube", 0, 0, "");
}catch(Exception e){
Log.e(TAG, e.toString());
}
return video;
}
private JSONObject genericRequest(String url){
Log.e(TAG, url);
@ -231,10 +301,39 @@ public class InfoUtil {
}catch(Exception e){
Log.e(TAG, e.toString());
}
return json;
}
private JSONArray genericArrayRequest(String url){
Log.e(TAG, url);
BufferedReader reader;
String line;
StringBuilder responseContent = new StringBuilder();
HttpURLConnection conn;
JSONArray json = new JSONArray();
try{
URL req = new URL(url);
conn = (HttpURLConnection) req.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(10000);
conn.setReadTimeout(5000);
if(conn.getResponseCode() < 300){
reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
while((line = reader.readLine()) != null){
responseContent.append(line);
}
reader.close();
json = new JSONArray(responseContent.toString());
}
conn.disconnect();
}catch(Exception e){
Log.e(TAG, e.toString());
}
return json;
}
@ -264,6 +363,66 @@ public class InfoUtil {
return o;
}
public ArrayList<Video> getTrending(Context context) throws JSONException{
videos = new ArrayList<>();
if (key.isEmpty()) {
if (useInvidous) return getTrendingFromInvidous(context);
else return new ArrayList<>();
}
return getTrendingFromKey(context);
}
public ArrayList<Video> getTrendingFromKey(Context context) throws JSONException{
String url = "https://www.googleapis.com/youtube/v3/videos?part=snippet&chart=mostPopular&videoCategoryId=10&regionCode="+countryCODE+"&maxResults=25&key="+key;
//short data
JSONObject res = genericRequest(url);
//extra data from the same videos
JSONObject contentDetails = genericRequest("https://www.googleapis.com/youtube/v3/videos?part=contentDetails&chart=mostPopular&videoCategoryId=10&regionCode="+countryCODE+"&maxResults=25&key="+key);
if(!contentDetails.has("items")) return new ArrayList<>();
JSONArray dataArray = res.getJSONArray("items");
JSONArray extraDataArray = contentDetails.getJSONArray("items");
for(int i = 0; i < dataArray.length(); i++){
JSONObject element = dataArray.getJSONObject(i);
JSONObject snippet = element.getJSONObject("snippet");
String duration = extraDataArray.getJSONObject(i).getJSONObject("contentDetails").getString("duration");
duration = formatDuration(duration);
snippet.put("videoID", element.getString("id"));
snippet.put("duration", duration);
fixThumbnail(snippet);
Video v = createVideofromJSON(snippet);
if(v == null || v.getThumb().isEmpty()){
continue;
}
v.setPlaylistTitle(context.getString(R.string.trendingPlaylist));
videos.add(v);
}
return videos;
}
public ArrayList<Video> getTrendingFromInvidous(Context context) {
String url = invidousURL + "trending?type=music&region="+countryCODE;
JSONArray res = genericArrayRequest(url);
try{
for(int i = 0; i < res.length(); i++){
JSONObject element = res.getJSONObject(i);
if(!element.getString("type").equals("video")) continue;
Video v = createVideofromInvidiousJSON(element);
if(v == null || v.getThumb().isEmpty()) continue;
v.setPlaylistTitle(context.getString(R.string.trendingPlaylist));
videos.add(v);
}
}catch(Exception e){
e.printStackTrace();
}
return videos;
}
public ArrayList<Video> getFromYTDL(String query){
videos = new ArrayList<>();
try {
@ -349,42 +508,26 @@ public class InfoUtil {
return videos;
}
public ArrayList<Video> getTrending(Context context) throws JSONException{
if (key.isEmpty()) return new ArrayList<>();
videos = new ArrayList<>();
String url = "https://www.googleapis.com/youtube/v3/videos?part=snippet&chart=mostPopular&videoCategoryId=10&regionCode="+countryCODE+"&maxResults=25&key="+key;
//short data
public ArrayList<String> getSearchSuggestions(String query){
if (!useInvidous) return new ArrayList<>();
String url = invidousURL + "search/suggestions?q="+query;
JSONObject res = genericRequest(url);
//extra data from the same videos
JSONObject contentDetails = genericRequest("https://www.googleapis.com/youtube/v3/videos?part=contentDetails&chart=mostPopular&videoCategoryId=10&regionCode="+countryCODE+"&maxResults=25&key="+key);
if(res.length() == 0) return new ArrayList<>();
if(!contentDetails.has("items")) return new ArrayList<>();
JSONArray dataArray = res.getJSONArray("items");
JSONArray extraDataArray = contentDetails.getJSONArray("items");
for(int i = 0; i < dataArray.length(); i++){
JSONObject element = dataArray.getJSONObject(i);
JSONObject snippet = element.getJSONObject("snippet");
String duration = extraDataArray.getJSONObject(i).getJSONObject("contentDetails").getString("duration");
duration = formatDuration(duration);
snippet.put("videoID", element.getString("id"));
snippet.put("duration", duration);
snippet = fixThumbnail(snippet);
Video v = createVideofromJSON(snippet);
if(v == null || v.getThumb().isEmpty()){
continue;
ArrayList<String> suggestionList = new ArrayList<>();
try{
JSONArray suggestions = res.getJSONArray("suggestions");
for (int i = 0; i < suggestions.length(); i++){
suggestionList.add(suggestions.getString(i));
}
v.setPlaylistTitle(context.getString(R.string.trendingPlaylist));
videos.add(v);
}
return videos;
}catch(Exception ignored){}
return suggestionList;
}
public String formatDuration(String dur){
if(dur.equals("P0D")){
return "LIVE";
@ -426,45 +569,6 @@ public class InfoUtil {
return format;
}
// public ArrayList<String> getSearchHints(String query){
// String url = "https://google.com/complete/search?client=youtube&q="+query;
// ArrayList<String> searchHints = new ArrayList<>();
//
// BufferedReader reader;
// String line;
// StringBuilder responseContent = new StringBuilder();
// HttpURLConnection conn;
// JSONArray json;
//
// try{
// URL req = new URL(url);
// conn = (HttpURLConnection) req.openConnection();
//
// conn.setRequestMethod("GET");
// conn.setConnectTimeout(10000);
// conn.setReadTimeout(5000);
//
// if(conn.getResponseCode() < 300){
// reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
// while((line = reader.readLine()) != null){
// responseContent.append(line);
// }
// reader.close();
// String content = responseContent.substring(19, responseContent.length()-1);
//
// json = new JSONArray(content);
// JSONArray hints = (JSONArray) json.get(1);
// for (int i = 0; i < hints.length(); i++){
// searchHints.add(hints.getJSONArray(i).get(0).toString());
// }
// }
// conn.disconnect();
// }catch(Exception e){
// Log.e(TAG, e.toString());
// }
// return searchHints;
// }
public static class PlaylistTuple {
String nextPageToken;
ArrayList<Video> videos;

View file

@ -78,7 +78,7 @@ public class NotificationUtil {
.setContentIntent(pendingIntent)
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
.clearActions()
//.addAction(0, context.getString(R.string.cancel), cancelNotificationPendingIntent)
.addAction(0, context.getString(R.string.cancel), cancelNotificationPendingIntent)
.build();
return notification;

View file

@ -0,0 +1,5 @@
<vector android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?android:textColorPrimary" android:pathData="M4,12l1.41,1.41L11,7.83V20h2V7.83l5.58,5.59L20,12l-8,-8 -8,8z"/>
</vector>

View file

@ -27,6 +27,21 @@
</com.google.android.material.appbar.AppBarLayout>
<ScrollView
android:id="@+id/search_suggestions_scroll_view"
android:layout_width="match_parent"
android:visibility="gone"
android:layout_marginTop="?attr/actionBarSize"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/search_suggestions_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="@layout/search_suggestion_item" />
</LinearLayout>
</ScrollView>
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"

View file

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/search_suggestions_constraintLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout
android:id="@+id/downloads_relative_layout"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="H,8:1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.google.android.material.card.MaterialCardView
android:id="@+id/downloads_card_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:cardBackgroundColor="@android:color/transparent"
android:checkable="true"
app:strokeWidth="0dp"
app:cardPreventCornerOverlap="true"
android:layout_margin="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
style="@style/Widget.Material3.Button.IconButton"
android:layout_width="40dp"
android:layout_height="wrap_content"
android:layout_weight="0.01"
app:icon="@drawable/ic_search"
/>
<TextView
android:id="@+id/suggestion_text"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:textSize="16sp"
android:textStyle="bold" />
<com.google.android.material.button.MaterialButton
style="@style/Widget.Material3.Button.IconButton"
android:id="@+id/set_search_query_button"
android:layout_width="40dp"
android:layout_height="match_parent"
app:icon="@drawable/ic_arrow_up"
android:layout_weight="0.01"
/>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>