diff --git a/.idea/assetWizardSettings.xml b/.idea/assetWizardSettings.xml index 041600c8..3bd69647 100644 --- a/.idea/assetWizardSettings.xml +++ b/.idea/assetWizardSettings.xml @@ -23,7 +23,7 @@ - + @@ -33,9 +33,10 @@ + - - + + diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml index 23704740..35ffc9d8 100644 --- a/.idea/jarRepositories.xml +++ b/.idea/jarRepositories.xml @@ -26,5 +26,10 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 34e91fab..469d480c 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -38,11 +38,14 @@ + + + @@ -50,17 +53,19 @@ - - + + + + - - + + diff --git a/app/src/androidTest/java/com/yausername/ytdl/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/deniscerri/ytdl/ExampleInstrumentedTest.java similarity index 96% rename from app/src/androidTest/java/com/yausername/ytdl/ExampleInstrumentedTest.java rename to app/src/androidTest/java/com/deniscerri/ytdl/ExampleInstrumentedTest.java index 180e9b3e..caa4380a 100644 --- a/app/src/androidTest/java/com/yausername/ytdl/ExampleInstrumentedTest.java +++ b/app/src/androidTest/java/com/deniscerri/ytdl/ExampleInstrumentedTest.java @@ -1,4 +1,4 @@ -package com.yausername.ytdl; +package com.deniscerri.ytdl; import android.content.Context; import androidx.test.platform.app.InstrumentationRegistry; diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 24edb7c3..1fdcb393 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,6 +4,7 @@ + - @@ -28,7 +28,10 @@ - + = 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(); } } diff --git a/app/src/main/java/com/deniscerri/ytdl/DownloaderService.java b/app/src/main/java/com/deniscerri/ytdl/DownloaderService.java new file mode 100644 index 00000000..397f48d7 --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdl/DownloaderService.java @@ -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; + } + } +} diff --git a/app/src/main/java/com/deniscerri/ytdl/HistoryFragment.java b/app/src/main/java/com/deniscerri/ytdl/HistoryFragment.java index 932eb5ce..d2d3debc 100644 --- a/app/src/main/java/com/deniscerri/ytdl/HistoryFragment.java +++ b/app/src/main/java/com/deniscerri/ytdl/HistoryFragment.java @@ -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 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 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); + } + + } \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdl/HomeFragment.java b/app/src/main/java/com/deniscerri/ytdl/HomeFragment.java index b3b2162d..8d4e32d3 100644 --- a/app/src/main/java/com/deniscerri/ytdl/HomeFragment.java +++ b/app/src/main/java/com/deniscerri/ytdl/HomeFragment.java @@ -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 resultObjects; - private CompositeDisposable compositeDisposable = new CompositeDisposable(); private DBManager dbManager; private YoutubeAPIManager youtubeAPIManager; - private NotificationCompat.Builder download_notification; - private NotificationManagerCompat notificationManager; private Queue 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 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); + } + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdl/MainActivity.java b/app/src/main/java/com/deniscerri/ytdl/MainActivity.java index 16c1ddb3..934d2886 100644 --- a/app/src/main/java/com/deniscerri/ytdl/MainActivity.java +++ b/app/src/main/java/com/deniscerri/ytdl/MainActivity.java @@ -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; + } + + } diff --git a/app/src/main/java/com/deniscerri/ytdl/NotificationUtil.java b/app/src/main/java/com/deniscerri/ytdl/NotificationUtil.java new file mode 100644 index 00000000..b2286966 --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdl/NotificationUtil.java @@ -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); + } +} diff --git a/app/src/main/java/com/deniscerri/ytdl/adapter/HistoryRecyclerViewAdapter.java b/app/src/main/java/com/deniscerri/ytdl/adapter/HistoryRecyclerViewAdapter.java new file mode 100644 index 00000000..ea8e1d21 --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdl/adapter/HistoryRecyclerViewAdapter.java @@ -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 { + private ArrayList videoList; + private final OnItemClickListener onItemClickListener; + private Context context; + + public HistoryRecyclerViewAdapter(ArrayList 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 videoList){ + this.videoList = videoList; + notifyDataSetChanged(); + } + + public void clear(){ + int size = videoList.size(); + videoList.clear(); + notifyItemRangeRemoved(0, size); + } + +} diff --git a/app/src/main/java/com/deniscerri/ytdl/adapter/HomeRecyclerViewAdapter.java b/app/src/main/java/com/deniscerri/ytdl/adapter/HomeRecyclerViewAdapter.java new file mode 100644 index 00000000..d4cc6cf9 --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdl/adapter/HomeRecyclerViewAdapter.java @@ -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 { + private ArrayList videoList; + private final OnItemClickListener onItemClickListener; + private Activity activity; + + public HomeRecyclerViewAdapter(ArrayList 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 videoList){ + this.videoList = videoList; + notifyDataSetChanged(); + } + + public void clear(){ + int size = videoList.size(); + videoList.clear(); + notifyItemRangeRemoved(0, size); + } + +} diff --git a/app/src/main/java/com/deniscerri/ytdl/api/YoutubeAPIManager.java b/app/src/main/java/com/deniscerri/ytdl/api/YoutubeAPIManager.java index dcd195aa..5b3b617f 100644 --- a/app/src/main/java/com/deniscerri/ytdl/api/YoutubeAPIManager.java +++ b/app/src/main/java/com/deniscerri/ytdl/api/YoutubeAPIManager.java @@ -248,11 +248,11 @@ public class YoutubeAPIManager { } public ArrayList getTrending(Context context) throws JSONException{ - String url = "https://www.googleapis.com/youtube/v3/videos?part=snippet&chart=mostPopular®ionCode="+countryCODE+"&maxResults=25&key="+key; + String url = "https://www.googleapis.com/youtube/v3/videos?part=snippet&chart=mostPopular&videoCategoryId=10®ionCode="+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®ionCode="+countryCODE+"&maxResults=25&key="+key); + JSONObject contentDetails = genericRequest("https://www.googleapis.com/youtube/v3/videos?part=contentDetails&chart=mostPopular&videoCategoryId=10®ionCode="+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; } diff --git a/app/src/main/java/com/deniscerri/ytdl/database/DBManager.java b/app/src/main/java/com/deniscerri/ytdl/database/DBManager.java index c72f0fc6..7cf61610 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/DBManager.java +++ b/app/src/main/java/com/deniscerri/ytdl/database/DBManager.java @@ -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 getHistory(){ + public ArrayList 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 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), diff --git a/app/src/main/java/com/deniscerri/ytdl/database/Video.java b/app/src/main/java/com/deniscerri/ytdl/database/Video.java index 846f465c..d66aa70e 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/Video.java +++ b/app/src/main/java/com/deniscerri/ytdl/database/Video.java @@ -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; diff --git a/app/src/main/java/com/deniscerri/ytdl/placeholder/PlaceholderContent.java b/app/src/main/java/com/deniscerri/ytdl/placeholder/PlaceholderContent.java deleted file mode 100644 index 3ad96446..00000000 --- a/app/src/main/java/com/deniscerri/ytdl/placeholder/PlaceholderContent.java +++ /dev/null @@ -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. - * - */ -public class PlaceholderContent { - - /** - * An array of sample (placeholder) items. - */ - public static final List ITEMS = new ArrayList(); - - /** - * A map of sample (placeholder) items, by ID. - */ - public static final Map ITEM_MAP = new HashMap(); - - 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; - } - } -} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_delete_outline_24.xml b/app/src/main/res/drawable/ic_baseline_delete_outline_24.xml new file mode 100644 index 00000000..4f5a408b --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_delete_outline_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_open_in_new_24.xml b/app/src/main/res/drawable/ic_baseline_open_in_new_24.xml new file mode 100644 index 00000000..9e8cb14a --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_open_in_new_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_delete_all.xml b/app/src/main/res/drawable/ic_delete_all.xml new file mode 100644 index 00000000..e6afffed --- /dev/null +++ b/app/src/main/res/drawable/ic_delete_all.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_link.xml b/app/src/main/res/drawable/ic_link.xml new file mode 100644 index 00000000..87c89b46 --- /dev/null +++ b/app/src/main/res/drawable/ic_link.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_no_results.xml b/app/src/main/res/drawable/ic_no_results.xml new file mode 100644 index 00000000..8dd147c0 --- /dev/null +++ b/app/src/main/res/drawable/ic_no_results.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_refresh.xml b/app/src/main/res/drawable/ic_refresh.xml new file mode 100644 index 00000000..454dda4d --- /dev/null +++ b/app/src/main/res/drawable/ic_refresh.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index fb68b8dc..03576714 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -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"/> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_history.xml b/app/src/main/res/layout/fragment_history.xml index acadcae8..3623784b 100644 --- a/app/src/main/res/layout/fragment_history.xml +++ b/app/src/main/res/layout/fragment_history.xml @@ -6,116 +6,105 @@ android:id="@+id/historycoordinator" xmlns:android="http://schemas.android.com/apk/res/android"> - + android:scrollbars="vertical" + app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> - + android:layout_height="match_parent"> - + + + + + + + + android:id="@+id/shimmer_history_linear_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="10dp" + android:orientation="vertical"> - + - + - + - + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + - + - - - - + android:layout_height="wrap_content"> + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 52d76850..3aed07f5 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -6,104 +6,75 @@ android:id="@+id/homecoordinator" xmlns:android="http://schemas.android.com/apk/res/android"> - + android:scrollbars="vertical" + app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> - + android:layout_height="match_parent"> - + + + + + + + + android:id="@+id/shimmer_linear_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="10dp" + android:orientation="vertical"> + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - + + + diff --git a/app/src/main/res/layout/history_bottom_sheet.xml b/app/src/main/res/layout/history_bottom_sheet.xml new file mode 100644 index 00000000..ee72802b --- /dev/null +++ b/app/src/main/res/layout/history_bottom_sheet.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/history_no_results.xml b/app/src/main/res/layout/history_no_results.xml new file mode 100644 index 00000000..6b862a87 --- /dev/null +++ b/app/src/main/res/layout/history_no_results.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/history_menu.xml b/app/src/main/res/menu/history_menu.xml index e3f23171..b13db6a2 100644 --- a/app/src/main/res/menu/history_menu.xml +++ b/app/src/main/res/menu/history_menu.xml @@ -3,12 +3,25 @@ xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity" > + + + + + - diff --git a/app/src/main/res/menu/main_menu.xml b/app/src/main/res/menu/main_menu.xml index ad6f962d..bb284c2a 100644 --- a/app/src/main/res/menu/main_menu.xml +++ b/app/src/main/res/menu/main_menu.xml @@ -10,9 +10,14 @@ app:showAsAction="collapseActionView|always" app:actionViewClass="androidx.appcompat.widget.SearchView"/> + + diff --git a/app/src/main/res/values-sq-rAL/strings.xml b/app/src/main/res/values-sq-rAL/strings.xml index c26154e2..c7c2aaa5 100644 --- a/app/src/main/res/values-sq-rAL/strings.xml +++ b/app/src/main/res/values-sq-rAL/strings.xml @@ -32,4 +32,14 @@ U vendos në rradhë! Duke u përditësuar. Prisni! Filloi Perditesimi + Hap Linkun + Hiqe + Rifresko Rezultatet + Rifresko Historinë + OK + Anullo + Kjo do të fshijë të gjithë elementet në histori! Nuk mund t\'i rikthesh më! + Konfirmo + Kërko nga Historia + Nuk ka Rezultate \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7f1934a7..6f71400e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -32,4 +32,14 @@ Failed Updating Updating in Progress! Please Wait! Updating Started + Open link + Remove + Refresh Results + Refresh History + OK + Cancel + This will delete every item on your history! You can\'t recover them! + Confirm + Search from History + No Results diff --git a/app/src/test/java/com/yausername/ytdl/ExampleUnitTest.java b/app/src/test/java/com/deniscerri/ytdl/ExampleUnitTest.java similarity index 92% rename from app/src/test/java/com/yausername/ytdl/ExampleUnitTest.java rename to app/src/test/java/com/deniscerri/ytdl/ExampleUnitTest.java index a6402711..20ba6072 100644 --- a/app/src/test/java/com/yausername/ytdl/ExampleUnitTest.java +++ b/app/src/test/java/com/deniscerri/ytdl/ExampleUnitTest.java @@ -1,4 +1,4 @@ -package com.yausername.ytdl; +package com.deniscerri.ytdl; import org.junit.Test; diff --git a/build.gradle b/build.gradle index 15769daa..bfa5c796 100644 --- a/build.gradle +++ b/build.gradle @@ -37,7 +37,7 @@ ext { } allprojects { - group = 'com.github.yausername' + group = 'com.github.deniscerri' version = versionName repositories {
- */ -public class PlaceholderContent { - - /** - * An array of sample (placeholder) items. - */ - public static final List ITEMS = new ArrayList(); - - /** - * A map of sample (placeholder) items, by ID. - */ - public static final Map ITEM_MAP = new HashMap(); - - 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; - } - } -} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_delete_outline_24.xml b/app/src/main/res/drawable/ic_baseline_delete_outline_24.xml new file mode 100644 index 00000000..4f5a408b --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_delete_outline_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_open_in_new_24.xml b/app/src/main/res/drawable/ic_baseline_open_in_new_24.xml new file mode 100644 index 00000000..9e8cb14a --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_open_in_new_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_delete_all.xml b/app/src/main/res/drawable/ic_delete_all.xml new file mode 100644 index 00000000..e6afffed --- /dev/null +++ b/app/src/main/res/drawable/ic_delete_all.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_link.xml b/app/src/main/res/drawable/ic_link.xml new file mode 100644 index 00000000..87c89b46 --- /dev/null +++ b/app/src/main/res/drawable/ic_link.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_no_results.xml b/app/src/main/res/drawable/ic_no_results.xml new file mode 100644 index 00000000..8dd147c0 --- /dev/null +++ b/app/src/main/res/drawable/ic_no_results.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_refresh.xml b/app/src/main/res/drawable/ic_refresh.xml new file mode 100644 index 00000000..454dda4d --- /dev/null +++ b/app/src/main/res/drawable/ic_refresh.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index fb68b8dc..03576714 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -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"/> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_history.xml b/app/src/main/res/layout/fragment_history.xml index acadcae8..3623784b 100644 --- a/app/src/main/res/layout/fragment_history.xml +++ b/app/src/main/res/layout/fragment_history.xml @@ -6,116 +6,105 @@ android:id="@+id/historycoordinator" xmlns:android="http://schemas.android.com/apk/res/android"> - + android:scrollbars="vertical" + app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> - + android:layout_height="match_parent"> - + + + + + + + + android:id="@+id/shimmer_history_linear_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="10dp" + android:orientation="vertical"> - + - + - + - + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + - + - - - - + android:layout_height="wrap_content"> + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 52d76850..3aed07f5 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -6,104 +6,75 @@ android:id="@+id/homecoordinator" xmlns:android="http://schemas.android.com/apk/res/android"> - + android:scrollbars="vertical" + app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> - + android:layout_height="match_parent"> - + + + + + + + + android:id="@+id/shimmer_linear_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="10dp" + android:orientation="vertical"> + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - + + + diff --git a/app/src/main/res/layout/history_bottom_sheet.xml b/app/src/main/res/layout/history_bottom_sheet.xml new file mode 100644 index 00000000..ee72802b --- /dev/null +++ b/app/src/main/res/layout/history_bottom_sheet.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/history_no_results.xml b/app/src/main/res/layout/history_no_results.xml new file mode 100644 index 00000000..6b862a87 --- /dev/null +++ b/app/src/main/res/layout/history_no_results.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/history_menu.xml b/app/src/main/res/menu/history_menu.xml index e3f23171..b13db6a2 100644 --- a/app/src/main/res/menu/history_menu.xml +++ b/app/src/main/res/menu/history_menu.xml @@ -3,12 +3,25 @@ xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity" > + + + + + - diff --git a/app/src/main/res/menu/main_menu.xml b/app/src/main/res/menu/main_menu.xml index ad6f962d..bb284c2a 100644 --- a/app/src/main/res/menu/main_menu.xml +++ b/app/src/main/res/menu/main_menu.xml @@ -10,9 +10,14 @@ app:showAsAction="collapseActionView|always" app:actionViewClass="androidx.appcompat.widget.SearchView"/> + + diff --git a/app/src/main/res/values-sq-rAL/strings.xml b/app/src/main/res/values-sq-rAL/strings.xml index c26154e2..c7c2aaa5 100644 --- a/app/src/main/res/values-sq-rAL/strings.xml +++ b/app/src/main/res/values-sq-rAL/strings.xml @@ -32,4 +32,14 @@ U vendos në rradhë! Duke u përditësuar. Prisni! Filloi Perditesimi + Hap Linkun + Hiqe + Rifresko Rezultatet + Rifresko Historinë + OK + Anullo + Kjo do të fshijë të gjithë elementet në histori! Nuk mund t\'i rikthesh më! + Konfirmo + Kërko nga Historia + Nuk ka Rezultate \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7f1934a7..6f71400e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -32,4 +32,14 @@ Failed Updating Updating in Progress! Please Wait! Updating Started + Open link + Remove + Refresh Results + Refresh History + OK + Cancel + This will delete every item on your history! You can\'t recover them! + Confirm + Search from History + No Results diff --git a/app/src/test/java/com/yausername/ytdl/ExampleUnitTest.java b/app/src/test/java/com/deniscerri/ytdl/ExampleUnitTest.java similarity index 92% rename from app/src/test/java/com/yausername/ytdl/ExampleUnitTest.java rename to app/src/test/java/com/deniscerri/ytdl/ExampleUnitTest.java index a6402711..20ba6072 100644 --- a/app/src/test/java/com/yausername/ytdl/ExampleUnitTest.java +++ b/app/src/test/java/com/deniscerri/ytdl/ExampleUnitTest.java @@ -1,4 +1,4 @@ -package com.yausername.ytdl; +package com.deniscerri.ytdl; import org.junit.Test; diff --git a/build.gradle b/build.gradle index 15769daa..bfa5c796 100644 --- a/build.gradle +++ b/build.gradle @@ -37,7 +37,7 @@ ext { } allprojects { - group = 'com.github.yausername' + group = 'com.github.deniscerri' version = versionName repositories {