system overhaul / fixes / new features

- fixed titles in folders and gradle builds
- converted downloading to a background service so that it continues even if the app is killed
- converted listviews on home and history fragments with recyclerviews to increase speed
- removed pull to refresh and floating action buttons for proper top app bar icons
- added search functionality to history
- added dialog if pressed delete all in history
- made new downloads add to queue if a download is on progress
- fixed leftover hardcoded strings
- added sponsorblock functionality. Removed non music parts for audio files and added chapters for videos
- fixed shimmer not removing when opening a video from share intent
- showed no results image when history is empty or there are no results
- made history cards clickable. You can delete them or open the link in youtube or copy the link to the clipboard
This commit is contained in:
Denis Çerri 2022-09-04 23:50:11 +02:00
parent 28a72866ca
commit 8c3525c1d5
No known key found for this signature in database
GPG key ID: 3F50F14A8E7F7A13
34 changed files with 1211 additions and 688 deletions

View file

@ -23,7 +23,7 @@
<PersistentState>
<option name="values">
<map>
<entry key="url" value="file:/$USER_HOME$/AppData/Local/Android/Sdk/icons/material/materialicons/code/baseline_code_24.xml" />
<entry key="url" value="file:/$USER_HOME$/AppData/Local/Android/Sdk/icons/material/materialicons/delete_sweep/baseline_delete_sweep_24.xml" />
</map>
</option>
</PersistentState>
@ -33,9 +33,10 @@
</option>
<option name="values">
<map>
<entry key="assetSourceType" value="FILE" />
<entry key="color" value="00a6ff" />
<entry key="outputName" value="ic_code" />
<entry key="sourceFile" value="C:\Users\denis\Desktop\youtubedl-android-0.13.1\youtubedl-android-0.13.1" />
<entry key="outputName" value="ic_no_results" />
<entry key="sourceFile" value="C:\Users\denis\Desktop\eaKdW401.svg" />
</map>
</option>
</PersistentState>

View file

@ -26,5 +26,10 @@
<option name="name" value="maven" />
<option name="url" value="https://jitpack.io" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
</component>
</project>

View file

@ -38,11 +38,14 @@
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/drawable/ic_announcement.xml" value="0.1275" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/drawable/ic_cloud_download.xml" value="0.1275" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/drawable/ic_code.xml" value="0.1275" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/drawable/ic_delete_all.xml" value="0.109" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/drawable/ic_download.xml" value="0.1275" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/drawable/ic_go_up.xml" value="0.1275" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/drawable/ic_home.xml" value="0.1275" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/drawable/ic_music.xml" value="0.1275" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/drawable/ic_music_downloaded.xml" value="0.1275" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/drawable/ic_no_results.xml" value="0.109" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/drawable/ic_refresh.xml" value="0.109" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/drawable/ic_search.xml" value="0.1275" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/drawable/ic_update.xml" value="0.1275" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/drawable/ic_video.xml" value="0.1275" />
@ -50,17 +53,19 @@
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/layout/activity_main.xml" value="0.18" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/layout/download_all_card.xml" value="0.3375" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/layout/empty_history_hint.xml" value="0.2" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/layout/fragment_history.xml" value="0.20555555555555555" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/layout/fragment_home.xml" value="0.18" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/layout/fragment_history.xml" value="0.2" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/layout/fragment_home.xml" value="0.1" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/layout/fragment_settings.xml" value="0.16875" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/layout/history_bottom_sheet.xml" value="0.16875" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/layout/history_card.xml" value="0.1" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/layout/history_card_shimmer.xml" value="0.22" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/layout/history_no_results.xml" value="0.21" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/layout/result_card.xml" value="0.33" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/layout/result_card_shimmer.xml" value="0.1" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/layout/search_result.xml" value="0.33" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/menu/bottom_nav_menu.xml" value="0.20555555555555555" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/menu/history_menu.xml" value="0.16875" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/menu/main_menu.xml" value="0.16875" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/menu/history_menu.xml" value="0.17" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/menu/main_menu.xml" value="0.33" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/menu/settings_menu.xml" value="0.16875" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/xml/root_preferences.xml" value="0.18" />
</map>

View file

@ -1,4 +1,4 @@
package com.yausername.ytdl;
package com.deniscerri.ytdl;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;

View file

@ -4,6 +4,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:name=".App"
@ -18,7 +19,6 @@
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
@ -28,7 +28,10 @@
</intent-filter>
</activity>
<service
android:name=".DownloaderService"
android:enabled="true"
android:exported="false" />
<meta-data
android:name = "ytAPIkey"
android:value = "${ytAPI}"

View file

@ -1,10 +1,6 @@
package com.deniscerri.ytdl;
import android.app.Application;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.res.Configuration;
import android.os.Build;
import android.util.Log;
import android.widget.Toast;
@ -13,7 +9,6 @@ import com.google.android.material.color.DynamicColors;
import com.yausername.ffmpeg.FFmpeg;
import com.yausername.youtubedl_android.YoutubeDL;
import com.yausername.youtubedl_android.YoutubeDLException;
import com.deniscerri.ytdl.databinding.ActivityMainBinding;
import io.reactivex.Completable;
import io.reactivex.android.schedulers.AndroidSchedulers;
@ -25,15 +20,16 @@ import io.reactivex.schedulers.Schedulers;
public class App extends Application {
private static final String TAG = "App";
public static final String DOWNLOAD_CHANNEL_ID = "1";
ActivityMainBinding binding;
public static NotificationUtil notificationUtil;
@Override
public void onCreate() {
super.onCreate();
DynamicColors.applyToActivitiesIfAvailable(this);
createNotificationChannel();
notificationUtil = new NotificationUtil(this);
createNotificationChannels();
configureRxJavaErrorHandler();
Completable.fromAction(this::initLibraries).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new DisposableCompletableObserver() {
@Override
@ -71,15 +67,7 @@ public class App extends Application {
FFmpeg.getInstance().init(this);
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = getString(R.string.download_notification_channel_name);
String description = getString(R.string.download_notification_channel_description);
int importance = NotificationManager.IMPORTANCE_HIGH;
NotificationChannel channel = new NotificationChannel(DOWNLOAD_CHANNEL_ID, name, importance);
channel.setDescription(description);
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
private void createNotificationChannels() {
notificationUtil.createNotificationChannel();
}
}

View file

@ -0,0 +1,44 @@
package com.deniscerri.ytdl;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
public class DownloaderService extends Service {
private LocalBinder binder = new LocalBinder();
private NotificationCompat.Builder builder;
@Nullable
@Override
public IBinder onBind(Intent intent) {
Intent theIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, theIntent, PendingIntent.FLAG_IMMUTABLE);
String title = intent.getStringExtra("title");
Notification notification = App.notificationUtil.createDownloadServiceNotification(pendingIntent,title);
startForeground(NotificationUtil.DOWNLOAD_NOTIFICATION_ID, notification);
return binder;
}
@Override
public boolean onUnbind(Intent intent) {
stopForeground(true);
stopSelf();
return super.onUnbind(intent);
}
public class LocalBinder extends Binder {
DownloaderService getService() {
return DownloaderService.this;
}
}
}

View file

@ -1,59 +1,64 @@
package com.deniscerri.ytdl;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.graphics.Color;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.SearchView;
import androidx.cardview.widget.CardView;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Handler;
import android.os.Looper;
import android.text.InputType;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import com.deniscerri.ytdl.adapter.HistoryRecyclerViewAdapter;
import com.deniscerri.ytdl.database.DBManager;
import com.deniscerri.ytdl.database.Video;
import com.facebook.shimmer.ShimmerFrameLayout;
import com.google.android.material.appbar.MaterialToolbar;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.squareup.picasso.Picasso;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.ArrayList;
/**
* A fragment representing a list of Items.
*/
public class HistoryFragment extends Fragment implements View.OnClickListener{
public class HistoryFragment extends Fragment implements HistoryRecyclerViewAdapter.OnItemClickListener, View.OnClickListener{
private LinearLayout linearLayout;
private ScrollView scrollView;
private View fragmentView;
private DBManager dbManager;
Context context;
private LayoutInflater layoutinflater;
private ShimmerFrameLayout shimmerCards;
private MaterialToolbar topAppBar;
private RecyclerView recyclerView;
private HistoryRecyclerViewAdapter historyRecyclerViewAdapter;
private BottomSheetDialog bottomSheet;
private ArrayList<Video> historyObjects;
private static final String TAG = "HistoryFragment";
private static final String youtubeVideoURL = "https://www.youtube.com/watch?v=";
public HistoryFragment() {
}
@ -75,31 +80,23 @@ public class HistoryFragment extends Fragment implements View.OnClickListener{
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
fragmentView = inflater.inflate(R.layout.fragment_history, container, false);
context = fragmentView.getContext();
layoutinflater = LayoutInflater.from(context);
linearLayout = fragmentView.findViewById(R.id.linearLayout1);
scrollView = fragmentView.findViewById(R.id.scrollView1);
linearLayout = fragmentView.findViewById(R.id.historylinearLayout1);
shimmerCards = fragmentView.findViewById(R.id.shimmer_history_framelayout);
topAppBar = fragmentView.findViewById(R.id.history_toolbar);
dbManager = new DBManager(context);
historyObjects = new ArrayList<>();
SwipeRefreshLayout swipeRefreshLayout = fragmentView.findViewById(R.id.swiperefresh);
swipeRefreshLayout.setOnRefreshListener(() -> {
initCards();
swipeRefreshLayout.setRefreshing(false);
});
recyclerView = fragmentView.findViewById(R.id.recycler_view_history);
historyRecyclerViewAdapter = new HistoryRecyclerViewAdapter(historyObjects, this, context);
recyclerView.setAdapter(historyRecyclerViewAdapter);
recyclerView.setNestedScrollingEnabled(false);
FloatingActionButton fab = fragmentView.findViewById(R.id.fab_history);
fab.setOnClickListener(this);
scrollView.setOnScrollChangeListener((view, x, y, oldX, oldY) -> {
if( y > 500){
fab.show();
}else{
fab.hide();
}
});
initMenu();
initCards();
return fragmentView;
@ -109,24 +106,26 @@ public class HistoryFragment extends Fragment implements View.OnClickListener{
private void initCards(){
shimmerCards.startShimmer();
shimmerCards.setVisibility(View.VISIBLE);
historyRecyclerViewAdapter.clear();
linearLayout.removeAllViews();
try{
Thread thread = new Thread(() -> {
Handler uiHandler = new Handler(Looper.getMainLooper());
ArrayList<Video> historyObjects = dbManager.getHistory();
for(int i = historyObjects.size()-1; i >= 0; i--){
createCard(historyObjects.get(i));
}
TextView padding = new TextView(context);
int dp = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 80, context.getResources().getDisplayMetrics());
padding.setHeight(dp);
padding.setGravity(Gravity.CENTER);
historyObjects = dbManager.getHistory("");
uiHandler.post(() -> {
linearLayout.addView(padding);
historyRecyclerViewAdapter.setVideoList(historyObjects);
shimmerCards.stopShimmer();
shimmerCards.setVisibility(View.GONE);
});
if(historyObjects.size() == 0){
uiHandler.post(() -> {
RelativeLayout no_results = new RelativeLayout(context);
layoutinflater.inflate(R.layout.history_no_results, no_results);
linearLayout.addView(no_results);
});
}
});
thread.start();
}catch(Exception e){
@ -136,63 +135,87 @@ public class HistoryFragment extends Fragment implements View.OnClickListener{
}
public void scrollToTop(){
scrollView.smoothScrollTo(0,0);
recyclerView.smoothScrollToPosition(0);
}
private void createCard(Video video){
RelativeLayout r = new RelativeLayout(context);
layoutinflater.inflate(R.layout.history_card, r);
CardView card = r.findViewById(R.id.history_card_view);
// THUMBNAIL ----------------------------------
ImageView thumbnail = card.findViewById(R.id.history_image_view);
String imageURL= video.getThumb();
Handler uiHandler = new Handler(Looper.getMainLooper());
uiHandler.post(() -> Picasso.get().load(imageURL).into(thumbnail));
thumbnail.setColorFilter(Color.argb(70, 0, 0, 0));
// TITLE ----------------------------------
TextView videoTitle = card.findViewById(R.id.history_title);
String title = video.getTitle();
if(title.length() > 100){
title = title.substring(0, 40) + "...";
}
videoTitle.setText(title);
// Bottom Info ----------------------------------
TextView bottomInfo = card.findViewById(R.id.history_info_bottom);
String info = video.getAuthor() + "" + video.getDuration();
bottomInfo.setText(info);
// TIME DOWNLOADED ----------------------------------
TextView datetime = card.findViewById(R.id.history_info_time);
String downloadedTime = video.getDownloadedTime();
datetime.setText(downloadedTime);
// BUTTON ----------------------------------
LinearLayout buttonLayout = card.findViewById(R.id.history_download_button_layout);
Button btn = buttonLayout.findViewById(R.id.history_download_button_type);
if(video.getDownloadedType().equals("mp3")){
btn.setBackground(ContextCompat.getDrawable(context, R.drawable.ic_music));
}else{
btn.setBackground(ContextCompat.getDrawable(context, R.drawable.ic_video));
}
uiHandler.post(() -> linearLayout.addView(r));
private void addNoResultsView(){
RelativeLayout no_results = new RelativeLayout(context);
layoutinflater.inflate(R.layout.history_no_results, no_results);
linearLayout.addView(no_results);
}
private void initMenu(){
topAppBar.setOnClickListener(view -> {
scrollToTop();
MenuItem.OnActionExpandListener onActionExpandListener = new MenuItem.OnActionExpandListener() {
@Override
public boolean onMenuItemActionExpand(MenuItem menuItem) {
return true;
}
@Override
public boolean onMenuItemActionCollapse(MenuItem menuItem) {
return true;
}
};
topAppBar.getMenu().findItem(R.id.search_history).setOnActionExpandListener(onActionExpandListener);
SearchView searchView = (SearchView) topAppBar.getMenu().findItem(R.id.search_history).getActionView();
searchView.setInputType(InputType.TYPE_CLASS_TEXT);
searchView.setQueryHint(getString(R.string.search_history_hint));
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
topAppBar.getMenu().findItem(R.id.search_history).collapseActionView();
historyObjects = dbManager.getHistory(query);
historyRecyclerViewAdapter.clear();
historyRecyclerViewAdapter.setVideoList(historyObjects);
if(historyObjects.size() == 0) addNoResultsView();
return true;
}
@Override
public boolean onQueryTextChange(String newText) {
historyObjects = dbManager.getHistory(newText);
historyRecyclerViewAdapter.clear();
historyRecyclerViewAdapter.setVideoList(historyObjects);
if (historyObjects.size() == 0) {
linearLayout.removeAllViews();
addNoResultsView();
}else linearLayout.removeAllViews();
return true;
}
});
topAppBar.setOnClickListener(view -> scrollToTop());
topAppBar.setOnMenuItemClickListener((MenuItem m) -> {
switch (m.getItemId()){
case R.id.delete_history:
dbManager.clearHistory();
linearLayout.removeAllViews();
if(historyObjects.size() == 0){
Toast.makeText(context, "History is Empty!", Toast.LENGTH_SHORT).show();
return true;
}
MaterialAlertDialogBuilder delete_dialog = new MaterialAlertDialogBuilder(context);
delete_dialog.setTitle(getString(R.string.confirm_delete_history));
delete_dialog.setMessage(getString(R.string.confirm_delete_history_desc));
delete_dialog.setNegativeButton(getString(R.string.cancel), (dialogInterface, i) -> {
dialogInterface.cancel();
});
delete_dialog.setPositiveButton(getString(R.string.ok), (dialogInterface, i) -> {
dbManager.clearHistory();
historyRecyclerViewAdapter.clear();
addNoResultsView();
});
delete_dialog.show();
return true;
case R.id.refresh_history:
historyRecyclerViewAdapter.clear();
initCards();
return true;
}
return true;
@ -202,9 +225,86 @@ public class HistoryFragment extends Fragment implements View.OnClickListener{
@Override
public void onClick(View v) {
if (v.getId() == R.id.fab_history) {
scrollView.smoothScrollBy(0, 0);
scrollView.smoothScrollTo(0, 0);
int id = v.getId();
switch (id){
case R.id.bottomsheet_remove_button:
removeHistoryItem((Integer) v.getTag());
break;
case R.id.bottom_sheet_link:
copyLinkToClipBoard((Integer) v.getTag());
break;
case R.id.bottomsheet_open_link_button:
openLinkIntent((Integer) v.getTag());
break;
}
}
private void removeHistoryItem(int position){
if(bottomSheet != null) bottomSheet.hide();
Video v = historyObjects.get(position);
MaterialAlertDialogBuilder delete_dialog = new MaterialAlertDialogBuilder(context);
delete_dialog.setTitle(getString(R.string.confirm_delete_history));
delete_dialog.setMessage("You are going to delete \""+v.getTitle()+"\"!");
delete_dialog.setNegativeButton(getString(R.string.cancel), (dialogInterface, i) -> {
dialogInterface.cancel();
});
delete_dialog.setPositiveButton(getString(R.string.ok), (dialogInterface, i) -> {
historyObjects.remove(position);
historyRecyclerViewAdapter.notifyItemRemoved(position);
historyRecyclerViewAdapter.notifyItemRangeChanged(position, historyObjects.size());
dbManager.clearHistoryItem(v);
});
delete_dialog.show();
}
private void copyLinkToClipBoard(int position){
String url = youtubeVideoURL + historyObjects.get(position).getVideoId();
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("Youtube URL", url);
clipboard.setPrimaryClip(clip);
if(bottomSheet != null) bottomSheet.hide();
Toast.makeText(context, "Link copied to clipboard", Toast.LENGTH_SHORT).show();
}
private void openLinkIntent(int position){
String url = youtubeVideoURL + historyObjects.get(position).getVideoId();
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
if(bottomSheet != null) bottomSheet.hide();
startActivity(i);
}
@Override
public void onCardClick(int position) {
bottomSheet = new BottomSheetDialog(context);
bottomSheet.requestWindowFeature(Window.FEATURE_NO_TITLE);
bottomSheet.setContentView(R.layout.history_bottom_sheet);
Video video = historyObjects.get(position);
TextView title = bottomSheet.findViewById(R.id.bottom_sheet_title);
title.setText(video.getTitle());
TextView author = bottomSheet.findViewById(R.id.bottom_sheet_author);
author.setText(video.getAuthor());
Button link = bottomSheet.findViewById(R.id.bottom_sheet_link);
String url = youtubeVideoURL+video.getVideoId();
link.setText(url);
link.setTag(position);
link.setOnClickListener(this);
Button remove = bottomSheet.findViewById(R.id.bottomsheet_remove_button);
remove.setTag(position);
remove.setOnClickListener(this);
Button openLink = bottomSheet.findViewById(R.id.bottomsheet_open_link_button);
openLink.setTag(position);
openLink.setOnClickListener(this);
bottomSheet.show();
bottomSheet.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);
}
}

View file

@ -9,25 +9,20 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.media.MediaScannerConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.InputType;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
@ -37,17 +32,17 @@ import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
import androidx.core.widget.NestedScrollView;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.deniscerri.ytdl.adapter.HomeRecyclerViewAdapter;
import com.deniscerri.ytdl.api.YoutubeAPIManager;
import com.deniscerri.ytdl.database.DBManager;
import com.deniscerri.ytdl.database.Video;
import com.facebook.shimmer.ShimmerFrameLayout;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.MaterialToolbar;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.squareup.picasso.Picasso;
import com.yausername.youtubedl_android.DownloadProgressCallback;
import com.yausername.youtubedl_android.YoutubeDL;
import com.yausername.youtubedl_android.YoutubeDLRequest;
@ -70,15 +65,18 @@ import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
public class HomeFragment extends Fragment implements View.OnClickListener{
public class HomeFragment extends Fragment implements HomeRecyclerViewAdapter.OnItemClickListener, View.OnClickListener {
private boolean downloading = false;
private View fragmentView;
private ProgressBar progressBar;
private String inputQuery;
private LinearLayout linearLayout;
private ScrollView scrollView;
private LinearLayout homeLinearLayout;
private RecyclerView recyclerView;
private HomeRecyclerViewAdapter homeRecyclerViewAdapter;
private NestedScrollView scrollView;
private ShimmerFrameLayout shimmerCards;
private LayoutInflater layoutinflater;
Context context;
Activity activity;
private MaterialToolbar topAppBar;
@ -86,33 +84,22 @@ public class HomeFragment extends Fragment implements View.OnClickListener{
private static final String TAG = "HomeFragment";
private ArrayList<Video> resultObjects;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
private DBManager dbManager;
private YoutubeAPIManager youtubeAPIManager;
private NotificationCompat.Builder download_notification;
private NotificationManagerCompat notificationManager;
private Queue<Video> downloadQueue;
MainActivity mainActivity;
private static final NotificationUtil notificationUtil = App.notificationUtil;
private final DownloadProgressCallback callback = new DownloadProgressCallback() {
@Override
public void onProgressUpdate(float progress, long etaInSeconds, String line) {
activity.runOnUiThread(() -> {
progressBar.setProgress((int) progress);
String contentText = "";
if(downloadQueue.size() > 0){
contentText += " " + downloadQueue.size() + " items left\n";
}
contentText += line.replaceAll("\\[.*?\\]", "");
download_notification.setProgress(100,(int) progress,false)
.setContentText(contentText);
notificationManager.notify(Integer.parseInt(App.DOWNLOAD_CHANNEL_ID), download_notification.build());
notificationUtil.updateDownloadNotification(NotificationUtil.DOWNLOAD_NOTIFICATION_ID,
line, (int) progress, downloadQueue.size(), downloadQueue.peek().getTitle());
});
}
};
@ -129,81 +116,51 @@ public class HomeFragment extends Fragment implements View.OnClickListener{
setHasOptionsMenu(true);
}
public String convertETASecondsToTime(long eta){
String time = "";
int s = (int) eta;
int h = s / 3600;
int m = (s % 3600) / 60;
s %= 60;
if(h > 0){
time+=h+"hr ";
}else if(m > 0){
if(m < 10) time +="0";
time+=m+"min";
}
if(s < 10 && s > 0 && m > 0) time +="0";
if(s < 0) s = 0;
time+=s+"sec";
return time;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
compositeDisposable = new CompositeDisposable();
downloadQueue = new LinkedList<>();
resultObjects = new ArrayList<>();
// Inflate the layout for this fragment
fragmentView = inflater.inflate(R.layout.fragment_home, container, false);
context = fragmentView.getContext();
activity = getActivity();
mainActivity = (MainActivity) activity;
layoutinflater = LayoutInflater.from(context);
//initViews
linearLayout = fragmentView.findViewById(R.id.linearLayout1);
scrollView = fragmentView.findViewById(R.id.scrollView1);
homeLinearLayout = fragmentView.findViewById(R.id.linearLayout1);
shimmerCards = fragmentView.findViewById(R.id.shimmer_results_framelayout);
topAppBar = fragmentView.findViewById(R.id.home_toolbar);
scrollView = fragmentView.findViewById(R.id.home_scrollview);
recyclerView = fragmentView.findViewById(R.id.recycler_view_home);
downloadQueue = new LinkedList<>();
SwipeRefreshLayout swipeRefreshLayout = fragmentView.findViewById(R.id.swiperefreshhome);
swipeRefreshLayout.setOnRefreshListener(() -> {
initCards();
swipeRefreshLayout.setRefreshing(false);
});
FloatingActionButton fab = fragmentView.findViewById(R.id.fab_home);
fab.setOnClickListener(this);
scrollView.setOnScrollChangeListener((view, x, y, oldX, oldY) -> {
if(y > 500){
fab.show();
}else{
fab.hide();
}
});
homeRecyclerViewAdapter = new HomeRecyclerViewAdapter(resultObjects, this, activity);
recyclerView.setAdapter(homeRecyclerViewAdapter);
recyclerView.setNestedScrollingEnabled(false);
initMenu();
if(inputQuery != null){
if (inputQuery != null) {
parseQuery();
inputQuery = null;
}else{
} else {
initCards();
}
return fragmentView;
}
private void initCards(){
private void initCards() {
shimmerCards.startShimmer();
shimmerCards.setVisibility(View.VISIBLE);
linearLayout.removeAllViews();
try{
homeRecyclerViewAdapter.clear();
homeLinearLayout.removeAllViews();
try {
Thread thread = new Thread(() -> {
Handler uiHandler = new Handler(Looper.getMainLooper());
dbManager = new DBManager(context);
@ -211,51 +168,40 @@ public class HomeFragment extends Fragment implements View.OnClickListener{
resultObjects = dbManager.getResults();
boolean trending = false;
if(resultObjects.size() == 0){
if (resultObjects.size() == 0) {
trending = true;
try {
resultObjects = youtubeAPIManager.getTrending(context);
}catch(Exception e){
} catch (Exception e) {
Log.e(TAG, e.toString());
}
}
TextView trendingText = new TextView(context);
if(resultObjects != null){
if (resultObjects != null) {
scrollToTop();
if(trending){
trendingText.setText(R.string.Trending);
trendingText.setPadding(30, 0 ,0 ,0);
uiHandler.post(() -> linearLayout.addView(trendingText));
if (trending) {
uiHandler.post(this::addTrendingText);
}else{
if(resultObjects.size() > 1 && resultObjects.get(1).getIsPlaylistItem() == 1){
} else {
if (resultObjects.size() > 1 && resultObjects.get(1).getIsPlaylistItem() == 1) {
createDownloadAllCard();
}
}
for(int i = 0; i < resultObjects.size(); i++){
createCard(resultObjects.get(i));
}
}
uiHandler.post(() -> {
homeRecyclerViewAdapter.setVideoList(resultObjects);
shimmerCards.stopShimmer();
shimmerCards.setVisibility(View.GONE);
if(resultObjects.size() > 1){
addEndofResultsText();
}
});
});
thread.start();
}catch(Exception e){
} catch (Exception e) {
Log.e(TAG, e.toString());
}
}
private void initMenu(){
private void initMenu() {
MenuItem.OnActionExpandListener onActionExpandListener = new MenuItem.OnActionExpandListener() {
@Override
public boolean onMenuItemActionExpand(MenuItem menuItem) {
@ -290,31 +236,36 @@ public class HomeFragment extends Fragment implements View.OnClickListener{
topAppBar.setOnClickListener(view -> scrollToTop());
topAppBar.setOnMenuItemClickListener((MenuItem m) -> {
if (m.getItemId() == R.id.delete_results) {
dbManager.clearResults();
linearLayout.removeAllViews();
initCards();
return true;
switch (m.getItemId()){
case R.id.delete_results:
dbManager.clearResults();
recyclerView.removeAllViews();
initCards();
return true;
case R.id.refresh_results:
recyclerView.removeAllViews();
initCards();
}
return true;
});
}
public void handleIntent(Intent intent){
public void handleIntent(Intent intent) {
inputQuery = intent.getStringExtra(Intent.EXTRA_TEXT);
}
public void scrollToTop(){
scrollView.smoothScrollTo(0,0);
public void scrollToTop() {
scrollView.smoothScrollTo(-100,-100);
new Handler(Looper.getMainLooper()).post(() -> ((AppBarLayout) topAppBar.getParent()).setExpanded(true, true));
}
private void parseQuery() {
shimmerCards.startShimmer();
shimmerCards.setVisibility(View.VISIBLE);
linearLayout.removeAllViews();
homeRecyclerViewAdapter.clear();
homeLinearLayout.removeAllViews();
resultObjects = new ArrayList<>();
dbManager = new DBManager(context);
youtubeAPIManager = new YoutubeAPIManager(context);
scrollToTop();
@ -323,38 +274,37 @@ public class HomeFragment extends Fragment implements View.OnClickListener{
Pattern p = Pattern.compile("^(https?)://(www.)?youtu(.be)?");
Matcher m = p.matcher(inputQuery);
if(m.find()){
if (m.find()) {
type = "Video";
if (inputQuery.contains("playlist?list=")) {
type = "Playlist";
}
}
Log.e(TAG, inputQuery + " "+ type);
Log.e(TAG, inputQuery + " " + type);
try {
switch (type) {
case "Search": {
Thread thread = new Thread(new Runnable(){
Thread thread = new Thread(new Runnable() {
private final String query;
{
this.query = inputQuery;
}
@Override
public void run(){
public void run() {
try {
resultObjects = youtubeAPIManager.search(query);
}catch(Exception e){
} catch (Exception e) {
Log.e(TAG, e.toString());
}
Log.e(TAG, resultObjects.toString());
for(int i = 0; i < resultObjects.size(); i++){
createCard(resultObjects.get(i));
}
dbManager.addToResults(resultObjects);
new Handler(Looper.getMainLooper()).post(() -> {
addEndofResultsText();
homeRecyclerViewAdapter.setVideoList(resultObjects);
//addEndofResultsText();
shimmerCards.stopShimmer();
shimmerCards.setVisibility(View.GONE);
});
@ -362,36 +312,40 @@ public class HomeFragment extends Fragment implements View.OnClickListener{
});
thread.start();
break;
}case "Video": {
}
case "Video": {
String[] el = inputQuery.split("/");
inputQuery = el[el.length -1];
inputQuery = el[el.length - 1];
if(inputQuery.contains("watch?v=")){
if (inputQuery.contains("watch?v=")) {
inputQuery = inputQuery.substring(8);
}
if(inputQuery.contains("&list=")){
if (inputQuery.contains("&list=")) {
el = inputQuery.split("&list=");
inputQuery = el[0];
}
Thread thread = new Thread(new Runnable(){
Thread thread = new Thread(new Runnable() {
private final String query;
{
this.query = inputQuery;
}
@Override
public void run(){
public void run() {
try {
resultObjects.add(youtubeAPIManager.getVideo(query));
}catch(Exception e){
} catch (Exception e) {
Log.e(TAG, e.toString());
}
createCard(resultObjects.get(0));
dbManager.addToResults(resultObjects);
new Handler(Looper.getMainLooper()).post(() -> {
homeRecyclerViewAdapter.setVideoList(resultObjects);
shimmerCards.stopShimmer();
shimmerCards.setVisibility(View.GONE);
});
@ -399,31 +353,32 @@ public class HomeFragment extends Fragment implements View.OnClickListener{
});
thread.start();
break;
}case "Playlist": {
}
case "Playlist": {
inputQuery = inputQuery.split("list=")[1];
Thread thread = new Thread(new Runnable() {
private final String query;
{
this.query = inputQuery;
}
@Override
public void run(){
public void run() {
try {
resultObjects = youtubeAPIManager.getPlaylist(query, "");
}catch(Exception e){
} catch (Exception e) {
Log.e(TAG, e.toString());
}
dbManager.addToResults(resultObjects);
// DOWNLOAD ALL BUTTON
if(resultObjects.size() > 1){
if (resultObjects.size() > 1) {
createDownloadAllCard();
}
for(int i = 0 ; i < resultObjects.size(); i++){
createCard(resultObjects.get(i));
}
new Handler(Looper.getMainLooper()).post(() -> {
addEndofResultsText();
homeRecyclerViewAdapter.setVideoList(resultObjects);
//addEndofResultsText();
shimmerCards.stopShimmer();
shimmerCards.setVisibility(View.GONE);
});
@ -433,23 +388,20 @@ public class HomeFragment extends Fragment implements View.OnClickListener{
break;
}
}
}catch(Exception e){
Log.e(TAG, e.toString());
} catch (Exception e) {
Log.e(TAG, e.toString());
}
}
private void addEndofResultsText(){
TextView padding = new TextView(context);
int dp = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 80, fragmentView.getResources().getDisplayMetrics());
padding.setHeight(dp);
padding.setText(R.string.end_of_results);
padding.setGravity(Gravity.CENTER);
linearLayout.addView(padding);
private void addTrendingText(){
TextView trendingText = new TextView(context);
trendingText.setText(R.string.Trending);
trendingText.setPadding(70, 0 , 0, 0);
homeLinearLayout.addView(trendingText);
}
private void createDownloadAllCard(){
private void createDownloadAllCard() {
RelativeLayout r = new RelativeLayout(context);
layoutinflater.inflate(R.layout.download_all_card, r);
@ -466,116 +418,13 @@ public class HomeFragment extends Fragment implements View.OnClickListener{
videoBtn.setOnClickListener(this);
Handler uiHandler = new Handler(Looper.getMainLooper());
uiHandler.post(() -> linearLayout.addView(r));
uiHandler.post(() -> homeLinearLayout.addView(r));
}
private void createCard(Video video){
RelativeLayout r = new RelativeLayout(context);
layoutinflater.inflate(R.layout.result_card, r);
CardView card = r.findViewById(R.id.result_card_view);
// THUMBNAIL ----------------------------------
ImageView thumbnail = card.findViewById(R.id.result_image_view);
String imageURL= video.getThumb();
Handler uiHandler = new Handler(Looper.getMainLooper());
uiHandler.post(() -> Picasso.get().load(imageURL).into(thumbnail));
thumbnail.setColorFilter(Color.argb(70, 0, 0, 0));
// TITLE ----------------------------------
TextView videoTitle = card.findViewById(R.id.result_title);
String title = video.getTitle();
if(title.length() > 100){
title = title.substring(0, 40) + "...";
}
videoTitle.setText(title);
// Bottom Info ----------------------------------
TextView bottomInfo = card.findViewById(R.id.result_info_bottom);
String info = video.getAuthor() + "" + video.getDuration();
bottomInfo.setText(info);
// BUTTONS ----------------------------------
String videoID = video.getVideoId();
LinearLayout buttonLayout = card.findViewById(R.id.download_button_layout);
MaterialButton musicBtn = buttonLayout.findViewById(R.id.download_music);
musicBtn.setTag(videoID + "##mp3");
MaterialButton videoBtn = buttonLayout.findViewById(R.id.download_video);
videoBtn.setTag(videoID + "##mp4");
musicBtn.setOnClickListener(this);
videoBtn.setOnClickListener(this);
// PROGRESS BAR ----------------------------------------------------
ProgressBar progressBar = card.findViewById(R.id.download_progress);
progressBar.setVisibility(View.GONE);
progressBar.setTag(videoID + "##progress");
if(video.isAudioDownloaded() == 1){
musicBtn.setIcon(ContextCompat.getDrawable(activity, R.drawable.ic_music_downloaded));
}
if(video.isVideoDownloaded() == 1){
videoBtn.setIcon(ContextCompat.getDrawable(activity, R.drawable.ic_video_downloaded));
}
card.setTag(videoID + "##card");
uiHandler.post(() -> linearLayout.addView(r));
}
@Override
public void onClick(View v) {
//do what you want to do when button is clicked
String viewIdName;
try{
viewIdName = v.getTag().toString();
}catch(Exception e){
viewIdName = "";
}
if(!viewIdName.isEmpty()){
if(viewIdName.contains("mp3") || viewIdName.contains("mp4")){
Log.e(TAG, viewIdName);
String[] buttonData = viewIdName.split("##");
if(buttonData[0].equals("ALL")){
for (int i = 0; i < resultObjects.size(); i++){
Video vid = findVideo(resultObjects.get(i).getVideoId());
vid.setDownloadedType(buttonData[1]);
downloadQueue.add(vid);
}
}else{
Video vid = findVideo(buttonData[0]);
vid.setDownloadedType(buttonData[1]);
downloadQueue.add(vid);
}
if (downloading) {
Toast.makeText(context, R.string.added_to_queue, Toast.LENGTH_LONG).show();
return;
}
startDownload(downloadQueue);
}
}else{
if (v.getId() == R.id.fab_home) {
scrollView.smoothScrollBy(0, 0);
scrollToTop();
}
}
}
public Video findVideo(String id){
for(int i = 0; i < resultObjects.size(); i++){
public Video findVideo(String id) {
for (int i = 0; i < resultObjects.size(); i++) {
Video v = resultObjects.get(i);
if((v.getVideoId()).equals(id)){
if ((v.getVideoId()).equals(id)) {
return v;
}
}
@ -584,20 +433,25 @@ public class HomeFragment extends Fragment implements View.OnClickListener{
}
private void startDownload(Queue<Video> videos) {
Video video;
try{
if(videos.size() == 0){
mainActivity.stopDownloadService();
return;
}
try {
video = videos.remove();
}catch(Exception e){
} catch (Exception e) {
return;
}
if (!isStoragePermissionGranted()) {
Toast.makeText(context, R.string.try_again_after_permission, Toast.LENGTH_LONG).show();
mainActivity.stopDownloadService();
return;
}
String id = video.getVideoId();
String url = "https://www.youtube.com/watch?v=" + id;
YoutubeDLRequest request = new YoutubeDLRequest(url);
@ -608,7 +462,7 @@ public class HomeFragment extends Fragment implements View.OnClickListener{
MaterialButton clickedButton = null;
if(type.equals("mp3")){
if (type.equals("mp3")) {
request.addOption("--embed-thumbnail");
request.addOption("--sponsorblock-remove", "all");
request.addOption("--postprocessor-args", "-write_id3v1 1 -id3v2_version 3");
@ -617,25 +471,24 @@ public class HomeFragment extends Fragment implements View.OnClickListener{
request.addOption("-x");
request.addOption("--audio-format", "mp3");
clickedButton = linearLayout.findViewWithTag(id+"##mp3");
}else if(type.equals("mp4")){
clickedButton = recyclerView.findViewWithTag(id + "##mp3");
} else if (type.equals("mp4")) {
request.addOption("--embed-thumbnail");
request.addOption("--sponsorblock-mark", "all");
request.addOption("--embed-subs", "");
request.addOption("-f", "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best");
clickedButton = linearLayout.findViewWithTag(id+"##mp4");
clickedButton = recyclerView.findViewWithTag(id + "##mp4");
}
request.addOption("-o", youtubeDLDir.getAbsolutePath() + "/%(title)s.%(ext)s");
progressBar = fragmentView.findViewWithTag(id+"##progress");
progressBar = fragmentView.findViewWithTag(id + "##progress");
progressBar.setVisibility(View.VISIBLE);
//scroll to Card
View view = fragmentView.findViewWithTag(id+"##progress");
view.getParent().requestChildFocus(view,view);
showStart(video);
View view = fragmentView.findViewWithTag(id + "##progress");
scrollView.requestChildFocus(view, view);
progressBar.setProgress(0);
downloading = true;
Video theVideo = video;
@ -647,41 +500,34 @@ public class HomeFragment extends Fragment implements View.OnClickListener{
progressBar.setProgress(0);
progressBar.setVisibility(View.GONE);
notificationManager.cancel(Integer.parseInt(App.DOWNLOAD_CHANNEL_ID));
download_notification.setContentText(getString(R.string.download_success))
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setLargeIcon(BitmapFactory.decodeResource(this.getResources(), android.R.drawable.stat_sys_download_done))
.setProgress(0,0,false);
notificationManager.notify(Integer.parseInt(App.DOWNLOAD_CHANNEL_ID), download_notification.build());
//TODO show download finished
if(theClickedButton != null){
if(type.equals("mp3")){
if (theClickedButton != null) {
if (type.equals("mp3")) {
theClickedButton.setIcon(ContextCompat.getDrawable(activity, R.drawable.ic_music_downloaded));
}else{
} else {
theClickedButton.setIcon(ContextCompat.getDrawable(activity, R.drawable.ic_video_downloaded));
}
}
addToHistory(theVideo, new Date());
updateDownloadStatusOnResult(theVideo, type);
downloading = false;
// MEDIA SCAN
MediaScannerConnection.scanFile(context, new String[]{youtubeDLDir.getAbsolutePath()}, null, null);
// SCAN NEXT IN QUEUE
startDownload(videos);
}, e -> {
if(BuildConfig.DEBUG) Log.e(TAG, getString(R.string.failed_download), e);
if (BuildConfig.DEBUG) Log.e(TAG, getString(R.string.failed_download), e);
Toast.makeText(context, R.string.failed_download, Toast.LENGTH_LONG).show();
progressBar.setProgress(0);
progressBar.setVisibility(View.GONE);
download_notification.setContentText(getString(R.string.failed_download))
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setProgress(0,0,false);
notificationManager.notify(Integer.parseInt(App.DOWNLOAD_CHANNEL_ID), download_notification.build());
//TODO notification failed
downloading = false;
// SCAN NEXT IN QUEUE
@ -691,7 +537,7 @@ public class HomeFragment extends Fragment implements View.OnClickListener{
}
public void addToHistory(Video video, Date date){
public void addToHistory(Video video, Date date) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
int day = cal.get(Calendar.DAY_OF_MONTH);
@ -703,80 +549,46 @@ public class HomeFragment extends Fragment implements View.OnClickListener{
String time = formatter.format(date);
String downloadedTime = day + " " + month + " " + year + " " + time;
if(video != null){
if (video != null) {
dbManager = new DBManager(context);
try{
try {
video.setDownloadedTime(downloadedTime);
dbManager.addToHistory(video);
}catch(Exception ignored){}
} catch (Exception ignored) {
}
}
}
public void updateDownloadStatusOnResult(Video v, String type){
if(v != null){
public void updateDownloadStatusOnResult(Video v, String type) {
if (v != null) {
dbManager = new DBManager(context);
try{
try {
dbManager.updateDownloadStatusOnResult(v.getVideoId(), type);
}catch(Exception ignored){}
} catch (Exception ignored) {
}
}
}
@Override
public void onDestroy() {
compositeDisposable.dispose();
super.onDestroy();
}
@NonNull
private File getDownloadLocation(String type) {
SharedPreferences sharedPreferences = context.getSharedPreferences("root_preferences", Activity.MODE_PRIVATE);
String downloadsDir;
if(type.equals("mp3")){
if (type.equals("mp3")) {
downloadsDir = sharedPreferences.getString("music_path", "");
}else{
} else {
downloadsDir = sharedPreferences.getString("video_path", "");
}
File youtubeDLDir = new File(downloadsDir);
if (!youtubeDLDir.exists()){
if (!youtubeDLDir.exists()) {
boolean isDirCreated = youtubeDLDir.mkdir();
if(!isDirCreated){
if (!isDirCreated) {
Toast.makeText(context, R.string.failed_making_directory, Toast.LENGTH_LONG).show();
}
}
return youtubeDLDir;
}
private void showStart(Video video) {
progressBar.setProgress(0);
//NOTIFICATION BUILDER
Intent intent = new Intent(context, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pendingIntent;
pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
int PROGRESS_MAX = 100;
int PROGRESS_CURR = progressBar.getProgress();
download_notification = new NotificationCompat.Builder(context, App.DOWNLOAD_CHANNEL_ID)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setLargeIcon(BitmapFactory.decodeResource(this.getResources(), android.R.drawable.stat_sys_download))
.setContentTitle(video.getTitle())
.setContentText("")
.setPriority(NotificationCompat.PRIORITY_MAX)
.setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setCategory(NotificationCompat.CATEGORY_PROGRESS)
.setOnlyAlertOnce(true)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setProgress(PROGRESS_MAX, PROGRESS_CURR, false);
notificationManager = NotificationManagerCompat.from(context);
notificationManager.notify(Integer.parseInt(App.DOWNLOAD_CHANNEL_ID), download_notification.build());
}
public boolean isStoragePermissionGranted() {
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
@ -787,4 +599,56 @@ public class HomeFragment extends Fragment implements View.OnClickListener{
}
}
@Override
public void onButtonClick(int position, String type) {
Log.e(TAG, type);
Video vid = resultObjects.get(position);
vid.setDownloadedType(type);
downloadQueue.add(vid);
if (downloading) {
Toast.makeText(context, R.string.added_to_queue, Toast.LENGTH_LONG).show();
return;
}
mainActivity.startDownloadService(vid.getTitle());
startDownload(downloadQueue);
}
@Override
public void onCardClick(CardView card) {
}
@Override
public void onClick(View v) {
String viewIdName;
try {
viewIdName = v.getTag().toString();
} catch (Exception e) {
viewIdName = "";
}
if (!viewIdName.isEmpty()) {
if (viewIdName.contains("mp3") || viewIdName.contains("mp4")) {
Log.e(TAG, viewIdName);
String[] buttonData = viewIdName.split("##");
if (buttonData[0].equals("ALL")) {
for (int i = 0; i < resultObjects.size(); i++) {
Video vid = findVideo(resultObjects.get(i).getVideoId());
vid.setDownloadedType(buttonData[1]);
downloadQueue.add(vid);
}
if (downloading) {
Toast.makeText(context, R.string.added_to_queue, Toast.LENGTH_LONG).show();
return;
}
mainActivity.startDownloadService(downloadQueue.peek().getTitle());
startDownload(downloadQueue);
}
}
}
}
}

View file

@ -1,22 +1,26 @@
package com.deniscerri.ytdl;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import com.deniscerri.ytdl.databinding.ActivityMainBinding;
import io.reactivex.disposables.CompositeDisposable;
public class MainActivity extends AppCompatActivity{
ActivityMainBinding binding;
Context context;
private static final String TAG = "MainActivity";
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
private HomeFragment homeFragment;
private HistoryFragment historyFragment;
@ -24,6 +28,24 @@ public class MainActivity extends AppCompatActivity{
private Fragment lastFragment;
private FragmentManager fm;
private boolean isDownloadServiceRunning = false;
public DownloaderService downloaderService;
private final ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
downloaderService = ((DownloaderService.LocalBinder) service).getService();
isDownloadServiceRunning = true;
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
downloaderService = null;
isDownloadServiceRunning = false;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
binding = ActivityMainBinding.inflate(getLayoutInflater());
@ -32,6 +54,8 @@ public class MainActivity extends AppCompatActivity{
setContentView(R.layout.activity_main);
setContentView(binding.getRoot());
context = getBaseContext();
fm = getSupportFragmentManager();
homeFragment = new HomeFragment();
@ -107,15 +131,23 @@ public class MainActivity extends AppCompatActivity{
}
}
@Override
protected void onDestroy() {
compositeDisposable.dispose();
super.onDestroy();
}
private void replaceFragment(Fragment f){
fm.beginTransaction().hide(lastFragment).show(f).commit();
lastFragment = f;
}
public void startDownloadService(String title){
if(isDownloadServiceRunning) return;
Intent serviceIntent = new Intent(context, DownloaderService.class);
serviceIntent.putExtra("title", title);
context.getApplicationContext().bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
}
public void stopDownloadService(){
if(!isDownloadServiceRunning) return;
context.getApplicationContext().unbindService(serviceConnection);
isDownloadServiceRunning = false;
}
}

View file

@ -0,0 +1,75 @@
package com.deniscerri.ytdl;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.Build;
import androidx.core.app.NotificationCompat;
public class NotificationUtil {
Context context;
public static final String DOWNLOAD_SERVICE_CHANNEL_ID = "1";
public static final int DOWNLOAD_NOTIFICATION_ID = 1;
public static NotificationCompat.Builder notificationBuilder;
private static int PROGRESS_MAX = 100;
private static int PROGRESS_CURR = 0;
private NotificationManager notificationManager;
public NotificationUtil(Context context){
this.context = context;
notificationBuilder = new NotificationCompat.Builder(context, DOWNLOAD_SERVICE_CHANNEL_ID);
notificationManager = context.getSystemService(NotificationManager.class);
}
public void createNotificationChannel(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = context.getString(R.string.download_notification_channel_name);
String description = context.getString(R.string.download_notification_channel_description);
int importance = NotificationManager.IMPORTANCE_HIGH;
NotificationChannel channel = new NotificationChannel(DOWNLOAD_SERVICE_CHANNEL_ID, name, importance);
channel.setDescription(description);
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
}
public Notification createDownloadServiceNotification(PendingIntent pendingIntent, String title){
Notification notification = notificationBuilder
.setContentTitle(title)
.setOngoing(true)
.setCategory(Notification.CATEGORY_PROGRESS)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setLargeIcon(BitmapFactory.decodeResource(context.getResources(), android.R.drawable.stat_sys_download))
.setContentText("")
.setPriority(NotificationCompat.PRIORITY_MAX)
.setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE)
.setOnlyAlertOnce(true)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setProgress(PROGRESS_MAX, PROGRESS_CURR, false)
.setContentIntent(pendingIntent)
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
.build();
return notification;
}
public void updateDownloadNotification(int id, String desc, int progress, int queue, String title){
String contentText = "";
if (queue > 0) contentText += queue + " items left\n";
contentText += desc.replaceAll("\\[.*?\\]", "");
notificationBuilder.setProgress(100, (int) progress, false)
.setContentTitle(title)
.setStyle(new NotificationCompat.BigTextStyle().bigText(contentText));
notificationManager.notify(id, notificationBuilder.build());
}
public void cancelNotification(int id){
notificationManager.cancel(id);
}
}

View file

@ -0,0 +1,127 @@
package com.deniscerri.ytdl.adapter;
import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.cardview.widget.CardView;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.deniscerri.ytdl.R;
import com.deniscerri.ytdl.database.Video;
import com.google.android.material.button.MaterialButton;
import com.squareup.picasso.Picasso;
import java.util.ArrayList;
public class HistoryRecyclerViewAdapter extends RecyclerView.Adapter<HistoryRecyclerViewAdapter.ViewHolder> {
private ArrayList<Video> videoList;
private final OnItemClickListener onItemClickListener;
private Context context;
public HistoryRecyclerViewAdapter(ArrayList<Video> videos, OnItemClickListener onItemClickListener, Context context){
this.videoList = videos;
this.onItemClickListener = onItemClickListener;
this.context = context;
}
@Override
public int getItemViewType(int position) {
return super.getItemViewType(position);
}
public static class ViewHolder extends RecyclerView.ViewHolder {
private CardView cardView;
public ViewHolder(@NonNull View itemView, OnItemClickListener onItemClickListener) {
super(itemView);
cardView = itemView.findViewById(R.id.history_card_view);
}
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View cardView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.history_card, parent, false);
return new HistoryRecyclerViewAdapter.ViewHolder(cardView, onItemClickListener);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Video video = videoList.get(position);
CardView card = holder.cardView;
// THUMBNAIL ----------------------------------
ImageView thumbnail = card.findViewById(R.id.history_image_view);
String imageURL= video.getThumb();
Handler uiHandler = new Handler(Looper.getMainLooper());
uiHandler.post(() -> Picasso.get().load(imageURL).into(thumbnail));
thumbnail.setColorFilter(Color.argb(70, 0, 0, 0));
// TITLE ----------------------------------
TextView videoTitle = card.findViewById(R.id.history_title);
String title = video.getTitle();
if(title.length() > 100){
title = title.substring(0, 40) + "...";
}
videoTitle.setText(title);
// Bottom Info ----------------------------------
TextView bottomInfo = card.findViewById(R.id.history_info_bottom);
String info = video.getAuthor() + "" + video.getDuration();
bottomInfo.setText(info);
// TIME DOWNLOADED ----------------------------------
TextView datetime = card.findViewById(R.id.history_info_time);
String downloadedTime = video.getDownloadedTime();
datetime.setText(downloadedTime);
// BUTTON ----------------------------------
LinearLayout buttonLayout = card.findViewById(R.id.history_download_button_layout);
Button btn = buttonLayout.findViewById(R.id.history_download_button_type);
if(video.getDownloadedType().equals("mp3")){
btn.setBackground(ContextCompat.getDrawable(context, R.drawable.ic_music));
}else{
btn.setBackground(ContextCompat.getDrawable(context, R.drawable.ic_video));
}
card.setOnClickListener(view -> onItemClickListener.onCardClick(position));
}
@Override
public int getItemCount() {
return videoList.size();
}
public interface OnItemClickListener {
void onCardClick(int position);
}
public void setVideoList(ArrayList<Video> videoList){
this.videoList = videoList;
notifyDataSetChanged();
}
public void clear(){
int size = videoList.size();
videoList.clear();
notifyItemRangeRemoved(0, size);
}
}

View file

@ -0,0 +1,140 @@
package com.deniscerri.ytdl.adapter;
import android.app.Activity;
import android.graphics.Color;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.cardview.widget.CardView;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.deniscerri.ytdl.R;
import com.deniscerri.ytdl.database.Video;
import com.google.android.material.button.MaterialButton;
import com.squareup.picasso.Picasso;
import java.util.ArrayList;
public class HomeRecyclerViewAdapter extends RecyclerView.Adapter<HomeRecyclerViewAdapter.ViewHolder> {
private ArrayList<Video> videoList;
private final OnItemClickListener onItemClickListener;
private Activity activity;
public HomeRecyclerViewAdapter(ArrayList<Video> videos, OnItemClickListener onItemClickListener, Activity activity){
this.videoList = videos;
this.onItemClickListener = onItemClickListener;
this.activity = activity;
}
@Override
public int getItemViewType(int position) {
return super.getItemViewType(position);
}
public static class ViewHolder extends RecyclerView.ViewHolder {
private CardView cardView;
public ViewHolder(@NonNull View itemView, OnItemClickListener onItemClickListener) {
super(itemView);
cardView = itemView.findViewById(R.id.result_card_view);
}
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View cardView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.result_card, parent, false);
return new HomeRecyclerViewAdapter.ViewHolder(cardView, onItemClickListener);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Video video = videoList.get(position);
CardView card = holder.cardView;
// THUMBNAIL ----------------------------------
ImageView thumbnail = card.findViewById(R.id.result_image_view);
String imageURL= video.getThumb();
Handler uiHandler = new Handler(Looper.getMainLooper());
uiHandler.post(() -> Picasso.get().load(imageURL).into(thumbnail));
thumbnail.setColorFilter(Color.argb(70, 0, 0, 0));
// TITLE ----------------------------------
TextView videoTitle = card.findViewById(R.id.result_title);
String title = video.getTitle();
if(title.length() > 100){
title = title.substring(0, 40) + "...";
}
videoTitle.setText(title);
// Bottom Info ----------------------------------
TextView bottomInfo = card.findViewById(R.id.result_info_bottom);
String info = video.getAuthor() + "" + video.getDuration();
bottomInfo.setText(info);
// BUTTONS ----------------------------------
String videoID = video.getVideoId();
LinearLayout buttonLayout = card.findViewById(R.id.download_button_layout);
MaterialButton musicBtn = buttonLayout.findViewById(R.id.download_music);
musicBtn.setTag(videoID + "##mp3");
musicBtn.setOnClickListener(view -> onItemClickListener.onButtonClick(position, "mp3"));
MaterialButton videoBtn = buttonLayout.findViewById(R.id.download_video);
videoBtn.setTag(videoID + "##mp4");
videoBtn.setOnClickListener(view -> onItemClickListener.onButtonClick(position, "mp4"));
// PROGRESS BAR ----------------------------------------------------
ProgressBar progressBar = card.findViewById(R.id.download_progress);
progressBar.setVisibility(View.GONE);
progressBar.setTag(videoID + "##progress");
if(video.isAudioDownloaded() == 1){
musicBtn.setIcon(ContextCompat.getDrawable(activity, R.drawable.ic_music_downloaded));
}
if(video.isVideoDownloaded() == 1){
videoBtn.setIcon(ContextCompat.getDrawable(activity, R.drawable.ic_video_downloaded));
}
card.setTag(videoID + "##card");
}
@Override
public int getItemCount() {
return videoList.size();
}
public interface OnItemClickListener {
void onButtonClick(int position, String type);
void onCardClick(CardView card);
}
public void setVideoList(ArrayList<Video> videoList){
this.videoList = videoList;
notifyDataSetChanged();
}
public void clear(){
int size = videoList.size();
videoList.clear();
notifyItemRangeRemoved(0, size);
}
}

View file

@ -248,11 +248,11 @@ public class YoutubeAPIManager {
}
public ArrayList<Video> getTrending(Context context) throws JSONException{
String url = "https://www.googleapis.com/youtube/v3/videos?part=snippet&chart=mostPopular&regionCode="+countryCODE+"&maxResults=25&key="+key;
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&regionCode="+countryCODE+"&maxResults=25&key="+key);
JSONObject contentDetails = genericRequest("https://www.googleapis.com/youtube/v3/videos?part=contentDetails&chart=mostPopular&videoCategoryId=10&regionCode="+countryCODE+"&maxResults=25&key="+key);
JSONArray dataArray = res.getJSONArray("items");
JSONArray extraDataArray = contentDetails.getJSONArray("items");
@ -312,9 +312,7 @@ public class YoutubeAPIManager {
if(seconds.isEmpty()) seconds = "00";
else if(Integer.parseInt(seconds) < 10) seconds = "0" + seconds;
if(minutes.isEmpty()){
if(hours.isEmpty()){
minutes = "0";
}
minutes = "0";
}
duration = minutes + ":" + seconds;
@ -322,7 +320,6 @@ public class YoutubeAPIManager {
if(Integer.parseInt(minutes) < 10) minutes = "0" + minutes;
duration = hours + ":" + minutes + ":" + seconds;
}
return duration;
}

View file

@ -10,7 +10,7 @@ import java.util.ArrayList;
public class DBManager extends SQLiteOpenHelper {
public static final String db_name = "ytdlnis_db";
public static final int db_version = 1;
public static final int db_version = 2;
public static final String results_table_name = "results";
public static final String history_table_name = "history";
public static final String id = "id";
@ -68,6 +68,24 @@ public class DBManager extends SQLiteOpenHelper {
" WHERE downloadedAudio=1 OR downloadedVideo=1");
}
public void clearHistoryItem(Video video){
SQLiteDatabase db = this.getWritableDatabase();
db.execSQL("DELETE FROM " + history_table_name + " WHERE id=" + video.getId());
String where = "";
switch(video.getDownloadedType()){
case "mp3":
where = " SET downloadedAudio=0 WHERE downloadedAudio=1 AND videoId='" + video.getVideoId()+"'";
break;
case "mp4":
where = " SET downloadedVideo=0 WHERE downloadedVideo=1 AND videoId='" + video.getVideoId()+"'";
break;
}
//remove downloaded status from results
db.execSQL("UPDATE "+results_table_name+ where);
}
public void clearResults(){
SQLiteDatabase db = this.getWritableDatabase();
db.execSQL("DELETE FROM " + results_table_name);
@ -104,15 +122,18 @@ public class DBManager extends SQLiteOpenHelper {
return list;
}
public ArrayList<Video> getHistory(){
public ArrayList<Video> getHistory(String query){
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.rawQuery("SELECT * FROM " + history_table_name, null);
Cursor cursor = db.rawQuery("SELECT * FROM " + history_table_name
+ " WHERE title LIKE '%"+query+"%'",
null);
ArrayList<Video> list = new ArrayList<>();
if(cursor.moveToFirst()){
do {
// on below line we are adding the data from cursor to our array list.
list.add(new Video(cursor.getString(1),
list.add(new Video(cursor.getInt(0),
cursor.getString(1),
cursor.getString(2),
cursor.getString(3),
cursor.getString(4),

View file

@ -28,8 +28,9 @@ public class Video {
}
//HISTORY OBJECT
public Video(String videoId, String title, String author, String duration, String thumb,
public Video(int id, String videoId, String title, String author, String duration, String thumb,
String downloadedType, String downloadedTime, int isPlaylistItem) {
this.id = id;
this.videoId = videoId;
this.title = title;
this.author = author;

View file

@ -1,71 +0,0 @@
package com.deniscerri.ytdl.placeholder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Helper class for providing sample content for user interfaces created by
* Android template wizards.
* <p>
*/
public class PlaceholderContent {
/**
* An array of sample (placeholder) items.
*/
public static final List<PlaceholderItem> ITEMS = new ArrayList<PlaceholderItem>();
/**
* A map of sample (placeholder) items, by ID.
*/
public static final Map<String, PlaceholderItem> ITEM_MAP = new HashMap<String, PlaceholderItem>();
private static final int COUNT = 25;
static {
// Add some sample items.
for (int i = 1; i <= COUNT; i++) {
addItem(createPlaceholderItem(i));
}
}
private static void addItem(PlaceholderItem item) {
ITEMS.add(item);
ITEM_MAP.put(item.id, item);
}
private static PlaceholderItem createPlaceholderItem(int position) {
return new PlaceholderItem(String.valueOf(position), "Item " + position, makeDetails(position));
}
private static String makeDetails(int position) {
StringBuilder builder = new StringBuilder();
builder.append("Details about Item: ").append(position);
for (int i = 0; i < position; i++) {
builder.append("\nMore details information here.");
}
return builder.toString();
}
/**
* A placeholder item representing a piece of content.
*/
public static class PlaceholderItem {
public final String id;
public final String content;
public final String details;
public PlaceholderItem(String id, String content, String details) {
this.id = id;
this.content = content;
this.details = details;
}
@Override
public String toString() {
return content;
}
}
}

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#00A6FF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2L18,7L6,7v12zM8,9h8v10L8,19L8,9zM15.5,4l-1,-1h-5l-1,1L5,4v2h14L19,4z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#00A6FF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19,19H5V5h7V3H5c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2v-7h-2v7zM14,3v2h3.59l-9.83,9.83 1.41,1.41L19,6.41V10h2V3h-7z"/>
</vector>

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:colorPrimary" android:pathData="M15,16h4v2h-4zM15,8h7v2h-7zM15,12h6v2h-6zM3,18c0,1.1 0.9,2 2,2h6c1.1,0 2,-0.9 2,-2L13,8L3,8v10zM14,5h-3l-1,-1L6,4L5,5L2,5v2h12z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#00A6FF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M3.9,12c0,-1.71 1.39,-3.1 3.1,-3.1h4L11,7L7,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5h4v-1.9L7,15.1c-1.71,0 -3.1,-1.39 -3.1,-3.1zM8,13h8v-2L8,11v2zM17,7h-4v1.9h4c1.71,0 3.1,1.39 3.1,3.1s-1.39,3.1 -3.1,3.1h-4L13,17h4c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5z"/>
</vector>

View file

@ -0,0 +1,54 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="254dp"
android:height="129dp"
android:viewportWidth="254"
android:viewportHeight="129">
<path
android:pathData="M63.4,35c0,1.9 0.7,2 16.5,2 14.8,-0 16.3,0.2 14.4,1.5 -2.8,1.9 -3.7,5.5 -1.4,5.5 0.9,-0 2.4,-0.9 3.4,-2 1,-1.2 2.9,-2 4.7,-2 1.9,-0 3.4,0.7 4.2,2 1.4,2.2 4.8,2.7 4.8,0.7 0,-2.2 -2.9,-5.5 -5.5,-6.1 -1.4,-0.4 -2.5,-1.3 -2.5,-2.1 0,-1.3 -2.9,-1.5 -19.3,-1.5 -18.6,-0 -19.3,0.1 -19.3,2z"
android:fillColor="?android:textColorPrimary"
android:strokeColor="?android:textColorPrimary"/>
<path
android:pathData="M163,35c0,1.9 0.7,2 16.5,2l16.4,-0 -2.4,2.5c-1.4,1.3 -2.5,2.9 -2.5,3.5 0,1.8 3.9,1.1 5,-1 0.8,-1.4 2.1,-2 4.5,-2 2.4,-0 3.9,0.6 4.7,2 1.5,2.3 4.8,2.7 4.8,0.5 0,-2.1 -3.4,-5.3 -6.4,-6.1 -1.6,-0.4 -2.3,-1.1 -1.9,-2 0.4,-1.2 -2.6,-1.4 -19.1,-1.4 -18.9,-0 -19.6,0.1 -19.6,2z"
android:fillColor="?android:textColorPrimary"
android:strokeColor="?android:textColorPrimary"/>
<path
android:pathData="M36.1,40.9c-11.3,14.2 -13.3,33.1 -5.1,49.1 3,6 7.8,12 9.6,12 1.9,-0 1.8,-1.4 -0.3,-3.1 -0.9,-0.8 -3.3,-3.9 -5.3,-7.1 -4.6,-7.1 -6.4,-15.1 -5.8,-26 0.5,-9.7 2.9,-16 8.9,-23 4.3,-5.1 4.6,-5.8 2.5,-5.8 -0.8,-0 -2.8,1.7 -4.5,3.9z"
android:fillColor="?android:textColorPrimary"
android:strokeColor="?android:textColorPrimary"/>
<path
android:pathData="M224,38c0,0.7 1.4,2.8 3,4.7 7.6,8.6 11.1,22 9,34.1 -1.5,8.8 -3.9,14.1 -8.9,19.5 -4.4,4.8 -4.9,5.7 -2.7,5.7 2.2,-0 7.6,-6.9 11,-14 2.9,-6.1 3.1,-7.2 3.1,-18 0,-9.6 -0.4,-12.4 -2.2,-17 -3.5,-8.4 -12.3,-19.2 -12.3,-15z"
android:fillColor="?android:textColorPrimary"
android:strokeColor="?android:textColorPrimary"/>
<path
android:pathData="M151,41.4c0,0.8 1,1.6 2.1,1.8 3,0.4 4.7,3.3 3.3,5.5 -0.6,1 -2.1,1.9 -3.3,2.1 -2.8,0.4 -2.7,3.2 0.2,3.2 1.2,-0 3.3,-0.7 4.5,-1.6 3.1,-2.1 3.1,-8.7 0,-10.8 -2.8,-2 -6.8,-2.1 -6.8,-0.2z"
android:fillColor="?android:textColorPrimary"
android:strokeColor="?android:textColorPrimary"/>
<path
android:pathData="M98,47c0,2.7 0.3,3 3,3 2.7,-0 3,-0.3 3,-3 0,-2.7 -0.3,-3 -3,-3 -2.7,-0 -3,0.3 -3,3z"
android:fillColor="?android:textColorPrimary"
android:strokeColor="?android:textColorPrimary"/>
<path
android:pathData="M198,47c0,2.7 0.3,3 3,3 2.7,-0 3,-0.3 3,-3 0,-2.7 -0.3,-3 -3,-3 -2.7,-0 -3,0.3 -3,3z"
android:fillColor="?android:textColorPrimary"
android:strokeColor="?android:textColorPrimary"/>
<path
android:pathData="M124,64.7c-6.5,1.1 -21,6.9 -21,8.4 0,1.3 0.6,1.2 8.9,-1.8 6.8,-2.5 9,-2.8 20.6,-2.8 11.6,-0 13.8,0.3 20.8,2.8 8.8,3.1 8.7,3.1 8.7,1.6 0,-1.5 -10.5,-5.9 -18,-7.5 -6.2,-1.3 -14.6,-1.6 -20,-0.7z"
android:fillColor="?android:textColorPrimary"
android:strokeColor="?android:textColorPrimary"/>
<path
android:pathData="M43.2,77.5c2.2,2.9 4.1,4.4 5.6,4.5 2.7,-0 2.7,-0.1 0.2,-5 -1.8,-3.5 -2.5,-4 -5.6,-4l-3.5,-0 3.3,4.5z"
android:fillColor="?android:textColorPrimary"
android:strokeColor="?android:textColorPrimary"/>
<path
android:pathData="M54.7,77.5c2.4,2.9 4.5,4.5 6,4.5l2.3,-0 -2.2,-4.5c-2,-4.2 -2.5,-4.5 -6,-4.5l-3.9,-0 3.8,4.5z"
android:fillColor="?android:textColorPrimary"
android:strokeColor="?android:textColorPrimary"/>
<path
android:pathData="M201.7,77.5c2.4,2.9 4.5,4.5 6,4.5l2.2,-0 -2.2,-4.5c-2,-4.2 -2.5,-4.5 -6,-4.5l-3.8,-0 3.8,4.5z"
android:fillColor="?android:textColorPrimary"
android:strokeColor="?android:textColorPrimary"/>
<path
android:pathData="M213.1,77.4c2.2,3.2 4,4.6 5.5,4.6l2.3,-0 -1.9,-4.3c-1.7,-3.7 -2.3,-4.2 -5.6,-4.5l-3.6,-0.3 3.3,4.5z"
android:fillColor="?android:textColorPrimary"
android:strokeColor="?android:textColorPrimary"/>
</vector>

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:colorPrimary" android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
</vector>

View file

@ -23,6 +23,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
app:menu="@menu/bottom_nav_menu"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -6,116 +6,105 @@
android:id="@+id/historycoordinator"
xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
<androidx.core.widget.NestedScrollView
android:id="@+id/history_scrollview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/history_big_linearlayout"
android:layout_marginTop="?attr/actionBarSize">
android:scrollbars="vertical"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/swiperefresh"
<LinearLayout
android:id="@+id/home_main_linearlayout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
android:layout_height="match_parent">
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
<LinearLayout
android:id="@+id/historylinearLayout1"
android:layout_width="match_parent"
android:layout_gravity="center"
android:layout_height="match_parent"
android:backgroundTintMode="src_over"
tools:context=".HomeFragment"
android:id="@+id/scrollView1"
android:orientation="vertical" />
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:id="@+id/recycler_view_history"
android:orientation="vertical"
android:layout_height="wrap_content"
android:scrollbars="vertical"
android:nestedScrollingEnabled="false"
android:layout_margin="10dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
>
</androidx.recyclerview.widget.RecyclerView>
<com.facebook.shimmer.ShimmerFrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/shimmer_history_framelayout"
android:orientation="vertical">
<LinearLayout
android:id="@+id/history_main_linearlayout"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
android:id="@+id/shimmer_history_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/linearLayout1"
android:layout_margin="10dp"
android:orientation="vertical" >
<include layout="@layout/history_card_shimmer" />
<include layout="@layout/history_card"
android:visibility="gone" />
<include layout="@layout/history_card_shimmer" />
</LinearLayout>
<include layout="@layout/history_card_shimmer" />
<com.facebook.shimmer.ShimmerFrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/shimmer_history_framelayout"
android:orientation="vertical">
<include layout="@layout/history_card_shimmer" />
<include layout="@layout/history_card_shimmer" />
<include layout="@layout/history_card_shimmer" />
<include layout="@layout/history_card_shimmer" />
<include layout="@layout/history_card_shimmer" />
<include layout="@layout/history_card_shimmer" />
<include layout="@layout/history_card_shimmer" />
<LinearLayout
android:id="@+id/shimmer_history_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:orientation="vertical">
<include layout="@layout/history_card_shimmer" />
<include layout="@layout/history_card_shimmer" />
<include layout="@layout/history_card_shimmer" />
<include layout="@layout/history_card_shimmer" />
<include layout="@layout/history_card_shimmer" />
<include layout="@layout/history_card_shimmer" />
<include layout="@layout/history_card_shimmer" />
<include layout="@layout/history_card_shimmer" />
</LinearLayout>
</com.facebook.shimmer.ShimmerFrameLayout>
</LinearLayout>
</ScrollView>
</com.facebook.shimmer.ShimmerFrameLayout>
</LinearLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_history"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:visibility="invisible"
app:srcCompat="@drawable/ic_go_up"/>
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/history_appbarlayout"
app:liftOnScroll="false"
android:background="@android:color/transparent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/history_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways|snap"
app:title="@string/history"
app:menu="@menu/history_menu"
/>
</com.google.android.material.appbar.AppBarLayout>
<include layout="@layout/history_bottom_sheet"
android:visibility="gone" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -6,104 +6,75 @@
android:id="@+id/homecoordinator"
xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
<androidx.core.widget.NestedScrollView
android:id="@+id/home_scrollview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/home_big_linearlayout"
android:orientation="vertical"
android:layout_marginTop="?attr/actionBarSize"
>
android:scrollbars="vertical"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swiperefreshhome"
<LinearLayout
android:id="@+id/home_main_linearlayout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
android:layout_height="match_parent">
<ScrollView
android:visibility="visible"
<LinearLayout
android:id="@+id/linearLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:backgroundTintMode="src_over"
tools:context=".HomeFragment"
android:id="@+id/scrollView1"
android:layout_height="wrap_content"
android:orientation="vertical" />
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:id="@+id/recycler_view_home"
android:orientation="vertical"
android:layout_height="wrap_content"
android:scrollbars="vertical"
android:nestedScrollingEnabled="false"
android:layout_margin="10dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
>
</androidx.recyclerview.widget.RecyclerView>
<com.facebook.shimmer.ShimmerFrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/shimmer_results_framelayout"
android:orientation="vertical">
<LinearLayout
android:id="@+id/home_main_linearlayout"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
android:id="@+id/shimmer_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:orientation="vertical">
<include layout="@layout/result_card_shimmer" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/linearLayout1"
android:layout_margin="10dp"
android:orientation="vertical" >
<include layout="@layout/result_card_shimmer" />
<include layout="@layout/download_all_card"
android:visibility="gone" />
<include layout="@layout/result_card_shimmer" />
<include layout="@layout/result_card"
android:visibility="gone" />
<include layout="@layout/result_card_shimmer" />
</LinearLayout>
<include layout="@layout/result_card_shimmer" />
<com.facebook.shimmer.ShimmerFrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/shimmer_results_framelayout"
android:orientation="vertical">
<LinearLayout
android:id="@+id/shimmer_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:orientation="vertical">
<include layout="@layout/result_card_shimmer" />
<include layout="@layout/result_card_shimmer" />
<include layout="@layout/result_card_shimmer" />
<include layout="@layout/result_card_shimmer" />
<include layout="@layout/result_card_shimmer" />
<include layout="@layout/result_card_shimmer" />
<include layout="@layout/result_card_shimmer" />
<include layout="@layout/result_card_shimmer" />
</LinearLayout>
</com.facebook.shimmer.ShimmerFrameLayout>
</LinearLayout>
</ScrollView>
</com.facebook.shimmer.ShimmerFrameLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_home"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:visibility="invisible"
app:srcCompat="@drawable/ic_go_up"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/home_appbarlayout"
app:liftOnScroll="false"
android:background="@android:color/transparent"
android:layout_width="match_parent"
android:layout_height="wrap_content">

View file

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout android:id="@+id/history_element_bottom_sheet"
style="@style/Widget.Material3.BottomSheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:orientation="vertical">
<TextView
android:id="@+id/bottom_sheet_title"
android:layout_width="wrap_content"
android:textSize="25sp"
android:textColor="?attr/colorOnSurface"
android:layout_height="wrap_content"
android:text="@string/app_name" />
<TextView
android:id="@+id/bottom_sheet_author"
android:layout_width="wrap_content"
android:paddingTop="5dp"
android:textSize="15sp"
android:layout_height="wrap_content"
android:text="@string/app_name" />
</LinearLayout>
<Button
style="@style/Widget.Material3.Button.TextButton.Icon"
android:id="@+id/bottom_sheet_link"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="15sp"
android:layout_gravity="center"
app:icon="@drawable/ic_link"
android:text="@string/app_name" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:gravity="right"
android:layout_margin="20dp"
android:orientation="horizontal">
<Button
style="@style/Widget.Material3.Button.OutlinedButton.Icon"
android:id="@+id/bottomsheet_remove_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="10dp"
app:icon="@drawable/ic_baseline_delete_outline_24"
android:text="@string/Remove" />
<Button
style="@style/Widget.Material3.Button.TonalButton.Icon"
android:id="@+id/bottomsheet_open_link_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/Open_Link"
app:icon="@drawable/ic_baseline_open_in_new_24"
android:autoLink="all"/>
</LinearLayout>
</LinearLayout>
</FrameLayout>

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:layout_margin="100dp"
tools:ignore="MissingConstraints">
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/ic_no_results" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="center"
android:textSize="20sp"
android:textStyle="bold"
android:text="@string/no_results"
/>
</LinearLayout>
</RelativeLayout>

View file

@ -3,12 +3,25 @@
xmlns:tools="http://schemas.android.com/tools"
tools:context=".MainActivity" >
<item
android:id="@+id/search_history"
android:title="@string/search"
android:icon="@drawable/ic_search"
app:showAsAction="collapseActionView|always"
app:actionViewClass="androidx.appcompat.widget.SearchView"/>
<item
android:id="@+id/refresh_history"
android:icon="@drawable/ic_refresh"
android:title="@string/refresh_history"
app:showAsAction="ifRoom" />
<item
android:id="@+id/delete_history"
android:orderInCategory="100"
android:icon="@drawable/ic_delete_all"
android:title="@string/fshi_historin"
app:showAsAction="never" />
</menu>

View file

@ -10,9 +10,14 @@
app:showAsAction="collapseActionView|always"
app:actionViewClass="androidx.appcompat.widget.SearchView"/>
<item
android:id="@+id/refresh_results"
android:icon="@drawable/ic_refresh"
android:title="@string/refresh_results"
app:showAsAction="ifRoom" />
<item
android:id="@+id/delete_results"
android:orderInCategory="100"
android:title="@string/fshi_rezultatet"
app:showAsAction="never" />

View file

@ -32,4 +32,14 @@
<string name="added_to_queue">U vendos në rradhë!</string>
<string name="ytdl_already_updating">Duke u përditësuar. Prisni!</string>
<string name="ytdl_updating_started">Filloi Perditesimi</string>
<string name="Open_Link">Hap Linkun</string>
<string name="Remove">Hiqe</string>
<string name="refresh_results">Rifresko Rezultatet</string>
<string name="refresh_history">Rifresko Historinë</string>
<string name="ok">OK</string>
<string name="cancel">Anullo</string>
<string name="confirm_delete_history_desc">Kjo do të fshijë të gjithë elementet në histori! Nuk mund t\'i rikthesh më!</string>
<string name="confirm_delete_history">Konfirmo</string>
<string name="search_history_hint">Kërko nga Historia</string>
<string name="no_results">Nuk ka Rezultate</string>
</resources>

View file

@ -32,4 +32,14 @@
<string name="ytdl_update_failed">Failed Updating</string>
<string name="ytdl_already_updating">Updating in Progress! Please Wait!</string>
<string name="ytdl_updating_started">Updating Started</string>
<string name="Open_Link">Open link</string>
<string name="Remove">Remove</string>
<string name="refresh_results">Refresh Results</string>
<string name="refresh_history">Refresh History</string>
<string name="ok">OK</string>
<string name="cancel">Cancel</string>
<string name="confirm_delete_history_desc">This will delete every item on your history! You can\'t recover them!</string>
<string name="confirm_delete_history">Confirm</string>
<string name="search_history_hint">Search from History</string>
<string name="no_results">No Results</string>
</resources>

View file

@ -1,4 +1,4 @@
package com.yausername.ytdl;
package com.deniscerri.ytdl;
import org.junit.Test;

View file

@ -37,7 +37,7 @@ ext {
}
allprojects {
group = 'com.github.yausername'
group = 'com.github.deniscerri'
version = versionName
repositories {