Added Custom Command Feature and Self Updating Feature

- also other minor stuff, and new preferences
This commit is contained in:
Denis Çerri 2022-09-16 01:24:17 +02:00
parent d931edad72
commit 8117f9081b
No known key found for this signature in database
GPG key ID: 3F50F14A8E7F7A13
31 changed files with 893 additions and 263 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/arrow_downward/baseline_arrow_downward_24.xml" />
<entry key="url" value="file:/$USER_HOME$/AppData/Local/Android/Sdk/icons/material/materialicons/keyboard_arrow_right/baseline_keyboard_arrow_right_24.xml" />
</map>
</option>
</PersistentState>
@ -34,7 +34,7 @@
<option name="values">
<map>
<entry key="color" value="00a6ff" />
<entry key="outputName" value="ic_down" />
<entry key="outputName" value="ic_baseline_keyboard_arrow_right_24" />
<entry key="sourceFile" value="C:\Users\denis\Desktop\eaKdW401.svg" />
</map>
</option>

View file

@ -37,6 +37,7 @@
<entry key="..\:/Users/denis/Desktop/ytdlnis/ytdlnis/app/src/main/res/xml/shortcuts.xml" value="0.13852073535699017" />
<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_back.xml" value="0.109" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/drawable/ic_baseline_keyboard_arrow_right_24.xml" value="0.109" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/drawable/ic_chapters.xml" value="0.109" />
<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" />
@ -46,6 +47,7 @@
<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_image.xml" value="0.109" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/drawable/ic_info.xml" value="0.109" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/drawable/ic_lines.xml" value="0.109" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/drawable/ic_more.xml" value="0.109" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/drawable/ic_music.xml" value="0.1275" />
@ -53,17 +55,21 @@
<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_settings.xml" value="0.109" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/drawable/ic_subtitles.xml" value="0.109" />
<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_update_app.xml" value="0.109" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/drawable/ic_video.xml" value="0.1275" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/drawable/ic_video_downloaded.xml" value="0.1275" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/layout/activity_custom_command.xml" value="0.15940685820203893" />
<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/activity_settings.xml" value="0.16875" />
<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.2" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/layout/fragment_home.xml" value="0.2" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/layout/fragment_more.xml" value="0.33" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/layout/fragment_music_editor.xml" value="0.16875" />
<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" />
@ -79,6 +85,7 @@
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/xml/about_preferences.xml" value="0.16875" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/xml/display_preferences.xml" value="0.16875" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/xml/download_preferences.xml" value="0.16875" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/xml/more_preferences.xml" value="0.20555555555555555" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/xml/notification_preferences.xml" value="0.16875" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/xml/root_preferences.xml" value="0.25" />
<entry key="..\:/Users/denis/Documents/GitHub/ytdlnis/app/src/main/res/xml/update_preferences.xml" value="0.16875" />

View file

@ -65,8 +65,6 @@ dependencies {
implementation 'androidx.core:core:1.8.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.preference:preference:1.2.0'
implementation 'androidx.navigation:navigation-fragment:2.5.2'
implementation 'androidx.navigation:navigation-ui:2.5.2'
testImplementation "junit:junit:$junitVer"
androidTestImplementation "androidx.test.ext:junit:$androidJunitVer"
androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVer"
@ -80,5 +78,4 @@ dependencies {
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
implementation 'com.facebook.shimmer:shimmer:0.5.0'
}
}

View file

@ -1,22 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.deniscerri.ytdl">
package="com.deniscerri.ytdl" >
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application
android:name=".App"
android:allowBackup="false"
android:extractNativeLibs="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppDefaultTheme"
android:extractNativeLibs="true"
android:requestLegacyExternalStorage="true">
<activity android:name=".MainActivity">
android:theme="@style/AppDefaultTheme" >
<activity android:name=".MainActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@ -26,20 +27,31 @@
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
<activity
android:name=".page.settings.SettingsActivity"
android:parentActivityName=".MainActivity"
/>
android:parentActivityName=".MainActivity" >
<intent-filter>
<action android:name="ytdlnis.settings.SettingsActivity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".page.CustomCommandActivity"
android:theme="@style/AppDefaultTheme"
android:parentActivityName=".MainActivity">
<intent-filter>
<action android:name="ytdlnis.page.CustomCommandActivity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<service
android:name=".DownloaderService"
android:enabled="true"
android:exported="false" />
<meta-data
android:name = "ytAPIkey"
android:value = "${ytAPI}"
/>
android:name="ytAPIkey"
android:value="${ytAPI}" />
</application>
</manifest>

View file

@ -4,6 +4,7 @@ import android.app.Application;
import android.util.Log;
import android.widget.Toast;
import com.deniscerri.ytdl.util.NotificationUtil;
import com.google.android.material.color.DynamicColors;
import com.yausername.ffmpeg.FFmpeg;
import com.yausername.youtubedl_android.YoutubeDL;
@ -20,8 +21,6 @@ public class App extends Application {
private static final String TAG = "App";
public static NotificationUtil notificationUtil;
public static String arch = System.getProperty("os.arch");
public static String versionName = BuildConfig.VERSION_NAME;
@Override
public void onCreate() {

View file

@ -6,6 +6,8 @@ import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
@ -21,9 +23,10 @@ public class DownloaderService extends Service {
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, theIntent, PendingIntent.FLAG_IMMUTABLE);
String title = intent.getStringExtra("title");
int id = intent.getIntExtra("id", 1);
Notification notification = App.notificationUtil.createDownloadServiceNotification(pendingIntent,title);
startForeground(NotificationUtil.DOWNLOAD_NOTIFICATION_ID, notification);
startForeground(id, notification);
return binder;
}

View file

@ -13,6 +13,9 @@ import androidx.fragment.app.FragmentManager;
import com.deniscerri.ytdl.databinding.ActivityMainBinding;
import com.deniscerri.ytdl.page.HistoryFragment;
import com.deniscerri.ytdl.page.HomeFragment;
import com.deniscerri.ytdl.page.MoreFragment;
import com.deniscerri.ytdl.page.MusicEditorFragment;
import com.deniscerri.ytdl.page.settings.SettingsActivity;
public class MainActivity extends AppCompatActivity{
@ -24,6 +27,9 @@ public class MainActivity extends AppCompatActivity{
private HomeFragment homeFragment;
private HistoryFragment historyFragment;
private MusicEditorFragment musicEditorFragment;
private MoreFragment moreFragment;
private Fragment lastFragment;
private FragmentManager fm;
@ -59,33 +65,37 @@ public class MainActivity extends AppCompatActivity{
homeFragment = new HomeFragment();
historyFragment = new HistoryFragment();
moreFragment = new MoreFragment();
musicEditorFragment = new MusicEditorFragment();
fm.beginTransaction()
.replace(R.id.frame_layout, homeFragment)
.add(R.id.frame_layout, historyFragment)
.hide(historyFragment)
.commit();
lastFragment = homeFragment;
initFragments();
binding.bottomNavigationView.setOnItemSelectedListener(item -> {
switch(item.getItemId()){
case R.id.home:
if(lastFragment == homeFragment){
homeFragment.scrollToTop();
}else{
this.setTitle(R.string.app_name);;
}
replaceFragment(homeFragment);
break;
case R.id.history:
if(lastFragment == historyFragment){
historyFragment.scrollToTop();
}else {
this.setTitle(getString(R.string.history));
}
replaceFragment(historyFragment);
break;
int id = item.getItemId();
if(id == R.id.home){
if(lastFragment == homeFragment){
homeFragment.scrollToTop();
}else{
this.setTitle(R.string.app_name);;
}
replaceFragment(homeFragment);
}else if(id == R.id.history){
if(lastFragment == historyFragment){
historyFragment.scrollToTop();
}else {
this.setTitle(getString(R.string.history));
}
replaceFragment(historyFragment);
}else if(id == R.id.more){
if(lastFragment == moreFragment){
Intent intent = new Intent(context, SettingsActivity.class);
startActivity(intent);
}else{
this.setTitle(getString(R.string.more));
}
replaceFragment(moreFragment);
}else if(id == R.id.music_editor){
replaceFragment(musicEditorFragment);
}
return true;
});
@ -109,25 +119,34 @@ public class MainActivity extends AppCompatActivity{
historyFragment = new HistoryFragment();
homeFragment.handleIntent(intent);
fm.beginTransaction()
.replace(R.id.frame_layout, homeFragment)
.add(R.id.frame_layout, historyFragment)
.hide(historyFragment)
.commit();
lastFragment = homeFragment;
initFragments();
}
}
private void initFragments(){
fm.beginTransaction()
.replace(R.id.frame_layout, homeFragment)
.add(R.id.frame_layout, historyFragment)
.add(R.id.frame_layout, moreFragment)
.add(R.id.frame_layout, musicEditorFragment)
.hide(historyFragment)
.hide(moreFragment)
.hide(musicEditorFragment)
.commit();
lastFragment = homeFragment;
}
private void replaceFragment(Fragment f){
fm.beginTransaction().hide(lastFragment).show(f).commit();
lastFragment = f;
}
public void startDownloadService(String title){
public void startDownloadService(String title, int id){
if(isDownloadServiceRunning) return;
Intent serviceIntent = new Intent(context, DownloaderService.class);
serviceIntent.putExtra("title", title);
serviceIntent.putExtra("id", id);
context.getApplicationContext().bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
}

View file

@ -0,0 +1,191 @@
package com.deniscerri.ytdl.page;
import static com.deniscerri.ytdl.App.notificationUtil;
import android.Manifest;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.media.MediaScannerConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import com.deniscerri.ytdl.BuildConfig;
import com.deniscerri.ytdl.DownloaderService;
import com.deniscerri.ytdl.R;
import com.deniscerri.ytdl.util.NotificationUtil;
import com.google.android.material.appbar.MaterialToolbar;
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
import com.yausername.youtubedl_android.DownloadProgressCallback;
import com.yausername.youtubedl_android.YoutubeDL;
import com.yausername.youtubedl_android.YoutubeDLRequest;
import java.io.File;
import java.util.Collections;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
public class CustomCommandActivity extends AppCompatActivity {
private static final String TAG = "CustomCommandActivity";
private MaterialToolbar topAppBar;
private boolean running = false;
private boolean isDownloadServiceRunning = false;
public DownloaderService downloaderService;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
private TextView output;
private EditText input;
private ExtendedFloatingActionButton fab;
Context context;
private final DownloadProgressCallback callback = (progress, etaInSeconds, line) -> CustomCommandActivity.this.runOnUiThread(() -> {
output.append(line);
notificationUtil.updateDownloadNotification(NotificationUtil.COMMAND_DOWNLOAD_NOTIFICATION_ID,
line, (int) progress, 0, getString(R.string.running_ytdlp_command));
});
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
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_custom_command);
context = getBaseContext();
topAppBar = findViewById(R.id.custom_command_toolbar);
topAppBar.setNavigationOnClickListener(view -> onBackPressed());
output = findViewById(R.id.custom_command_output);
output.setMovementMethod(new ScrollingMovementMethod());
input = findViewById(R.id.command_edittext);
input.requestFocus();
fab = findViewById(R.id.command_fab);
fab.setOnClickListener(view -> {
runCommand(input.getText().toString());
});
}
@Override
public void onBackPressed() {
super.onBackPressed();
}
private void runCommand(String text){
if (running) {
Toast.makeText(this, "Cannot start command! A command is already in progress", Toast.LENGTH_LONG).show();
return;
}
if(!text.startsWith("yt-dlp ")){
Toast.makeText(context, "Wrong input! Try Again!", Toast.LENGTH_SHORT).show();
return;
}
fab.hide();
input.setEnabled(false);
output.setText("");
startDownloadService(getString(R.string.running_ytdlp_command), NotificationUtil.COMMAND_DOWNLOAD_NOTIFICATION_ID);
text = text.substring(6).trim();
if (!isStoragePermissionGranted()) {
Toast.makeText(context, R.string.try_again_after_permission, Toast.LENGTH_LONG).show();
stopDownloadService();
return;
}
YoutubeDLRequest request = new YoutubeDLRequest(Collections.emptyList());
String commandRegex = "\"([^\"]*)\"|(\\S+)";
Matcher m = Pattern.compile(commandRegex).matcher(text);
while (m.find()) {
if (m.group(1) != null) {
request.addOption(m.group(1));
} else {
request.addOption(m.group(2));
}
}
SharedPreferences sharedPreferences = context.getSharedPreferences("root_preferences", Activity.MODE_PRIVATE);
String downloadsDir = sharedPreferences.getString("command_path", "");
File youtubeDLDir = new File(downloadsDir);
if (!youtubeDLDir.exists()) {
boolean isDirCreated = youtubeDLDir.mkdir();
if (!isDirCreated) {
Toast.makeText(context, R.string.failed_making_directory, Toast.LENGTH_LONG).show();
}
}
request.addOption("-o", youtubeDLDir.getAbsolutePath() + "/%(title)s.%(ext)s");
running = true;
Disposable disposable = Observable.fromCallable(() -> YoutubeDL.getInstance().execute(request, callback))
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(youtubeDLResponse -> {
output.append(youtubeDLResponse.getOut());
running = false;
stopDownloadService();
// MEDIA SCAN
MediaScannerConnection.scanFile(context, new String[]{"/storage"}, null, null);
fab.show();
input.setEnabled(true);
}, e -> {
if (BuildConfig.DEBUG) Log.e(TAG, getString(R.string.failed_download), e);
output.append(e.getMessage());
running = false;
stopDownloadService();
fab.show();
input.setEnabled(true);
});
compositeDisposable.add(disposable);
}
public boolean isStoragePermissionGranted() {
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
return true;
} else {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
return false;
}
}
public void startDownloadService(String title, int id){
if(isDownloadServiceRunning) return;
Intent serviceIntent = new Intent(context, DownloaderService.class);
serviceIntent.putExtra("title", title);
serviceIntent.putExtra("id", id);
context.getApplicationContext().bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
}
public void stopDownloadService(){
if(!isDownloadServiceRunning) return;
context.getApplicationContext().unbindService(serviceConnection);
isDownloadServiceRunning = false;
}
}

View file

@ -7,6 +7,7 @@ import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import androidx.appcompat.widget.SearchView;
import androidx.core.widget.NestedScrollView;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
@ -31,6 +32,7 @@ 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.AppBarLayout;
import com.google.android.material.appbar.MaterialToolbar;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
@ -50,6 +52,7 @@ public class HistoryFragment extends Fragment implements HistoryRecyclerViewAdap
private ShimmerFrameLayout shimmerCards;
private MaterialToolbar topAppBar;
private RecyclerView recyclerView;
private NestedScrollView scrollView;
private HistoryRecyclerViewAdapter historyRecyclerViewAdapter;
private BottomSheetDialog bottomSheet;
private Handler uiHandler;
@ -91,6 +94,7 @@ public class HistoryFragment extends Fragment implements HistoryRecyclerViewAdap
historyObjects = new ArrayList<>();
recyclerView = fragmentView.findViewById(R.id.recycler_view_history);
scrollView = fragmentView.findViewById(R.id.history_scrollview);
historyRecyclerViewAdapter = new HistoryRecyclerViewAdapter(historyObjects, this, context);
recyclerView.setAdapter(historyRecyclerViewAdapter);
@ -128,7 +132,8 @@ public class HistoryFragment extends Fragment implements HistoryRecyclerViewAdap
}
public void scrollToTop(){
recyclerView.smoothScrollToPosition(0);
scrollView.smoothScrollTo(-100,-100);
new Handler(Looper.getMainLooper()).post(() -> ((AppBarLayout) topAppBar.getParent()).setExpanded(true, true));
}
private void addNoResultsView(){

View file

@ -39,8 +39,8 @@ 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.deniscerri.ytdl.NotificationUtil;
import com.deniscerri.ytdl.page.settings.SettingsActivity;
import com.deniscerri.ytdl.util.NotificationUtil;
import com.facebook.shimmer.ShimmerFrameLayout;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.MaterialToolbar;
@ -487,10 +487,6 @@ public class HomeFragment extends Fragment implements HomeRecyclerViewAdapter.On
}
if (type.equals("mp3")) {
boolean embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false);
if(embedThumb){
request.addOption("--embed-thumbnail");
}
boolean removeNonMusic = sharedPreferences.getBoolean("remove_non_music", false);
if(removeNonMusic){
request.addOption("--sponsorblock-remove", "all");
@ -502,12 +498,15 @@ public class HomeFragment extends Fragment implements HomeRecyclerViewAdapter.On
String format = sharedPreferences.getString("audio_format", "");
request.addOption("--audio-format", format);
if(format.equals("mp3") || format.equals("m4a") || format.equals("flac")){
boolean embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false);
if(embedThumb){
request.addOption("--embed-thumbnail");
}
}
clickedButton = recyclerView.findViewWithTag(id + "##mp3");
} else if (type.equals("mp4")) {
boolean embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false);
if(embedThumb){
request.addOption("--embed-thumbnail");
}
boolean addChapters = sharedPreferences.getBoolean("add_chapters", false);
if(addChapters){
request.addOption("--sponsorblock-mark", "all");
@ -516,8 +515,16 @@ public class HomeFragment extends Fragment implements HomeRecyclerViewAdapter.On
if(embedSubs){
request.addOption("--embed-subs", "");
}
request.addOption("-f", "bestvideo+bestaudio/best");
String format = sharedPreferences.getString("video_format", "");
request.addOption("-f", "bestvideo[ext="+format+"]+bestaudio[ext=m4a]/best[ext="+format+"]/best");
request.addOption("--merge-output-format", format);
if(!format.equals("webm")){
boolean embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false);
if(embedThumb){
request.addOption("--embed-thumbnail");
}
}
clickedButton = recyclerView.findViewWithTag(id + "##mp4");
}
@ -655,7 +662,7 @@ public class HomeFragment extends Fragment implements HomeRecyclerViewAdapter.On
Toast.makeText(context, R.string.added_to_queue, Toast.LENGTH_LONG).show();
return;
}
mainActivity.startDownloadService(vid.getTitle());
mainActivity.startDownloadService(vid.getTitle(), NotificationUtil.DOWNLOAD_NOTIFICATION_ID);
startDownload(downloadQueue);
}
@ -689,7 +696,7 @@ public class HomeFragment extends Fragment implements HomeRecyclerViewAdapter.On
Toast.makeText(context, R.string.added_to_queue, Toast.LENGTH_LONG).show();
return;
}
mainActivity.startDownloadService(downloadQueue.peek().getTitle());
mainActivity.startDownloadService(downloadQueue.peek().getTitle(), NotificationUtil.DOWNLOAD_NOTIFICATION_ID);
startDownload(downloadQueue);
}
}

View file

@ -0,0 +1,137 @@
package com.deniscerri.ytdl.page;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import com.deniscerri.ytdl.BuildConfig;
import com.deniscerri.ytdl.R;
import com.deniscerri.ytdl.util.UpdateUtil;
import com.google.android.material.card.MaterialCardView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.yausername.youtubedl_android.YoutubeDL;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.concurrent.atomic.AtomicReference;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
public class MoreFragment extends PreferenceFragmentCompat {
Preference updateYTDL;
Preference updateApp;
MaterialCardView materialCardView;
public static boolean updatingYTDL;
public static boolean updatingApp;
public static final String TAG = "MoreFragment";
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.more_preferences, rootKey);
updateYTDL = findPreference("update_ytdl");
updateApp = findPreference("update_app");
updateYTDL.setOnPreferenceClickListener(preference -> {
updateYoutubeDL();
return true;
});
updateApp.setOnPreferenceClickListener(preference -> {
updateApp();
return true;
});
}
private void updateYoutubeDL() {
if (updatingYTDL) {
Toast.makeText(getContext(), getString(R.string.ytdl_already_updating), Toast.LENGTH_LONG).show();
return;
}
Toast.makeText(getContext(), getString(R.string.ytdl_updating_started), Toast.LENGTH_SHORT).show();
updatingYTDL = true;
Disposable disposable = Observable.fromCallable(() -> YoutubeDL.getInstance().updateYoutubeDL(getContext()))
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(status -> {
switch (status) {
case DONE:
Toast.makeText(getContext(), getString(R.string.ytld_update_success), Toast.LENGTH_LONG).show();
break;
case ALREADY_UP_TO_DATE:
Toast.makeText(getContext(), getString(R.string.you_are_in_latest_version), Toast.LENGTH_LONG).show();
break;
default:
Toast.makeText(getContext(), status.toString(), Toast.LENGTH_LONG).show();
break;
}
updatingYTDL = false;
}, e -> {
if(BuildConfig.DEBUG) Log.e(TAG, getString(R.string.ytdl_update_failed), e);
Toast.makeText(getContext(), getString(R.string.ytdl_update_failed), Toast.LENGTH_LONG).show();
updatingYTDL = false;
});
compositeDisposable.add(disposable);
}
private void updateApp(){
if (updatingApp) {
Toast.makeText(getContext(), getString(R.string.ytdl_already_updating), Toast.LENGTH_LONG).show();
return;
}
UpdateUtil updateUtil = new UpdateUtil(getContext());
AtomicReference<JSONObject> res = new AtomicReference<>(new JSONObject());
try{
Thread thread = new Thread(() -> res.set(updateUtil.checkForAppUpdate()));
thread.start();
thread.join();
}catch(Exception e){
Log.e(TAG, e.toString());
}
if(res.get() == null){
Toast.makeText(getContext(), R.string.error_checking_latest_version, Toast.LENGTH_SHORT).show();
}
String version = "";
String body = "";
try {
version = res.get().getString("tag_name");
body = res.get().getString("body");
} catch (JSONException ignored) {}
if(version.equals("v"+BuildConfig.VERSION_NAME)){
getActivity().runOnUiThread(() -> Toast.makeText(getContext(), R.string.you_are_running_latest_version, Toast.LENGTH_SHORT).show());
return;
}
MaterialAlertDialogBuilder updateDialog = new MaterialAlertDialogBuilder(getContext())
.setTitle(version)
.setMessage(body)
.setIcon(R.drawable.ic_update_app)
.setNegativeButton("Cancel", (dialogInterface, i) -> {})
.setPositiveButton("Update", (dialogInterface, i) -> updateUtil.updateApp(res.get()));
updateDialog.show();
}
}

View file

@ -0,0 +1,30 @@
package com.deniscerri.ytdl.page;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.deniscerri.ytdl.R;
public class MusicEditorFragment extends Fragment {
public MusicEditorFragment() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_music_editor, container, false);
}
}

View file

@ -33,6 +33,7 @@ public class SettingsFragment extends PreferenceFragmentCompat {
Preference musicPath;
Preference videoPath;
Preference commandPath;
SeekBarPreference concurrentFragments;
EditTextPreference limitRate;
@ -46,17 +47,13 @@ public class SettingsFragment extends PreferenceFragmentCompat {
ListPreference videoFormat;
SeekBarPreference audioQuality;
Preference updateYTDL;
Preference updateApp;
Preference version;
public static final int MUSIC_PATH_CODE = 33333;
public static final int VIDEO_PATH_CODE = 55555;
public static boolean updatingYTDL;
public static boolean updatingApp;
public static final int COMMAND_PATH_CODE = 77777;
private static boolean firstRun = true;
private static final String TAG = "SettingsFragment";
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
@Override
@ -65,6 +62,7 @@ public class SettingsFragment extends PreferenceFragmentCompat {
musicPath = findPreference("music_path");
videoPath = findPreference("video_path");
commandPath = findPreference("command_path");
concurrentFragments = findPreference("concurrent_fragments");
limitRate = findPreference("limit_rate");
@ -78,9 +76,8 @@ public class SettingsFragment extends PreferenceFragmentCompat {
videoFormat = findPreference("video_format");
audioQuality = findPreference("audio_quality");
updateYTDL = findPreference("update_ytdl");
updateApp = findPreference("update_app");
version = findPreference("version");
version.setSummary(BuildConfig.VERSION_NAME);
if(firstRun){
initPreferences();
@ -98,6 +95,9 @@ public class SettingsFragment extends PreferenceFragmentCompat {
if(preferences.getString("video_path", "").isEmpty()){
editor.putString("video_path", "/storage/emulated/0/Movies/");
}
if(preferences.getString("command_path", "").isEmpty()){
editor.putString("command_path", "/storage/emulated/0/Download");
}
editor.putInt("concurrent_fragments", concurrentFragments.getValue());
editor.putString("limit_rate", limitRate.getText());
@ -132,6 +132,13 @@ public class SettingsFragment extends PreferenceFragmentCompat {
return true;
});
commandPath.setSummary(preferences.getString("command_path", ""));
commandPath.setOnPreferenceClickListener(preference -> {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
commandPathResultLauncher.launch(intent);
return true;
});
concurrentFragments.setOnPreferenceChangeListener((preference, newValue) -> {
int value = Integer.parseInt(String.valueOf(newValue));
editor.putInt("concurrent_fragments", value);
@ -180,14 +187,18 @@ public class SettingsFragment extends PreferenceFragmentCompat {
return true;
});
audioFormat.setSummary(preferences.getString("audio_format", ""));
audioFormat.setOnPreferenceChangeListener((preference, newValue) -> {
editor.putString("audio_format", String.valueOf(newValue));
audioFormat.setSummary(String.valueOf(newValue));
editor.apply();
return true;
});
videoFormat.setSummary(preferences.getString("video_format", ""));
videoFormat.setOnPreferenceChangeListener((preference, newValue) -> {
editor.putString("video_format", String.valueOf(newValue));
videoFormat.setSummary(String.valueOf(newValue));
editor.apply();
return true;
});
@ -198,15 +209,6 @@ public class SettingsFragment extends PreferenceFragmentCompat {
return true;
});
updateYTDL.setOnPreferenceClickListener(preference -> {
updateYoutubeDL();
return true;
});
updateApp.setOnPreferenceClickListener(preference -> {
updateApp();
return true;
});
}
ActivityResultLauncher<Intent> musicPathResultLauncher = registerForActivityResult(
@ -231,6 +233,17 @@ public class SettingsFragment extends PreferenceFragmentCompat {
}
});
ActivityResultLauncher<Intent> commandPathResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_OK) {
changePath(commandPath, result.getData(), COMMAND_PATH_CODE);
}
}
});
public void changePath(Preference p, Intent data, int requestCode){
Log.e("TEST", data.toUri(0));
String dataValue = data.getData().toString();
@ -255,7 +268,7 @@ public class SettingsFragment extends PreferenceFragmentCompat {
path.append(pieces[i]).append("/");
}
Log.e("TEST", path.toString());
Log.e(TAG, path.toString());
p.setSummary(path.toString());
SharedPreferences sharedPreferences = requireContext().getSharedPreferences("root_preferences", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
@ -266,76 +279,10 @@ public class SettingsFragment extends PreferenceFragmentCompat {
case VIDEO_PATH_CODE:
editor.putString("video_path", path.toString());
break;
case COMMAND_PATH_CODE:
editor.putString("command_path", path.toString());
break;
}
editor.apply();
}
private void updateYoutubeDL() {
if (updatingYTDL) {
Toast.makeText(getContext(), getString(R.string.ytdl_already_updating), Toast.LENGTH_LONG).show();
return;
}
Toast.makeText(getContext(), getString(R.string.ytdl_updating_started), Toast.LENGTH_SHORT).show();
updatingYTDL = true;
Disposable disposable = Observable.fromCallable(() -> YoutubeDL.getInstance().updateYoutubeDL(getContext()))
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(status -> {
switch (status) {
case DONE:
Toast.makeText(getContext(), getString(R.string.ytld_update_success), Toast.LENGTH_LONG).show();
break;
case ALREADY_UP_TO_DATE:
Toast.makeText(getContext(), getString(R.string.you_are_in_latest_version), Toast.LENGTH_LONG).show();
break;
default:
Toast.makeText(getContext(), status.toString(), Toast.LENGTH_LONG).show();
break;
}
updatingYTDL = false;
}, e -> {
if(BuildConfig.DEBUG) Log.e(TAG, getString(R.string.ytdl_update_failed), e);
Toast.makeText(getContext(), getString(R.string.ytdl_update_failed), Toast.LENGTH_LONG).show();
updatingYTDL = false;
});
compositeDisposable.add(disposable);
}
private void updateApp(){
if (updatingApp) {
Toast.makeText(getContext(), getString(R.string.ytdl_already_updating), Toast.LENGTH_LONG).show();
return;
}
Toast.makeText(getContext(), getString(R.string.ytdl_updating_started), Toast.LENGTH_SHORT).show();
updatingApp = true;
Disposable disposable = Observable.fromCallable(() -> YoutubeDL.getInstance().updateYoutubeDL(getContext()))
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(status -> {
switch (status) {
case DONE:
Toast.makeText(getContext(), getString(R.string.ytld_update_success), Toast.LENGTH_LONG).show();
break;
case ALREADY_UP_TO_DATE:
Toast.makeText(getContext(), getString(R.string.you_are_in_latest_version), Toast.LENGTH_LONG).show();
break;
default:
Toast.makeText(getContext(), status.toString(), Toast.LENGTH_LONG).show();
break;
}
updatingApp = false;
}, e -> {
if(BuildConfig.DEBUG) Log.e(TAG, getString(R.string.ytdl_update_failed), e);
Toast.makeText(getContext(), getString(R.string.ytdl_update_failed), Toast.LENGTH_LONG).show();
updatingApp = false;
});
compositeDisposable.add(disposable);
}
}

View file

@ -1,4 +1,4 @@
package com.deniscerri.ytdl;
package com.deniscerri.ytdl.util;
import android.app.Notification;
import android.app.NotificationChannel;
@ -17,6 +17,10 @@ public class NotificationUtil {
Context context;
public static final String DOWNLOAD_SERVICE_CHANNEL_ID = "1";
public static final int DOWNLOAD_NOTIFICATION_ID = 1;
public static final String COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID = "2";
public static final int COMMAND_DOWNLOAD_NOTIFICATION_ID = 2;
public static NotificationCompat.Builder notificationBuilder;
private static int PROGRESS_MAX = 100;
private static int PROGRESS_CURR = 0;
@ -30,12 +34,21 @@ public class NotificationUtil {
public void createNotificationChannel(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
//gui downloads
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);
//command downloads
name = "Command Downloads";
description = "Notification that shows the download progress of a yt-dlp command";
channel = new NotificationChannel(COMMAND_DOWNLOAD_SERVICE_CHANNEL_ID, name, importance);
channel.setDescription(description);
notificationManager.createNotificationChannel(channel);
}
}

View file

@ -0,0 +1,108 @@
package com.deniscerri.ytdl.util;
import android.app.DownloadManager;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;
import com.deniscerri.ytdl.App;
import com.deniscerri.ytdl.R;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class UpdateUtil {
Context context;
private final String TAG = "UpdateUtil";
private DownloadManager downloadManager;
public UpdateUtil(Context context){
this.context = context;
downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
}
public JSONObject checkForAppUpdate(){
String url = "https://api.github.com/repos/deniscerri/ytdlnis/releases/latest";
BufferedReader reader;
String line;
StringBuilder responseContent = new StringBuilder();
HttpURLConnection conn;
JSONObject json = new JSONObject();
try{
URL req = new URL(url);
conn = (HttpURLConnection) req.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(10000);
conn.setReadTimeout(5000);
if(conn.getResponseCode() < 300){
reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
while((line = reader.readLine()) != null){
responseContent.append(line);
}
reader.close();
json = new JSONObject(responseContent.toString());
if(json.has("error")){
throw new Exception();
}
}
conn.disconnect();
}catch(Exception e){
Log.e(TAG, e.toString());
}
return json;
}
public void updateApp(JSONObject updateInfo){
try{
JSONArray versions = updateInfo.getJSONArray("assets");
String url = "";
String app_name = "";
for (int i = 0; i < versions.length(); i++){
JSONObject tmp = versions.getJSONObject(i);
if(tmp.getString("name").contains(Build.SUPPORTED_ABIS[0])){
url = tmp.getString("browser_download_url");
app_name = tmp.getString("name");
break;
}
}
if(url.isEmpty()){
Toast.makeText(context, R.string.couldnt_find_apk, Toast.LENGTH_SHORT).show();
return;
}
Uri uri = Uri.parse(url);
Environment
.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
.mkdirs();
downloadManager.enqueue(new DownloadManager.Request(uri)
.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI |
DownloadManager.Request.NETWORK_MOBILE)
.setAllowedOverRoaming(true)
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
.setTitle(context.getString(R.string.downloading_update))
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, app_name));
}catch(Exception ignored){}
}
}

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:colorAccent" android:pathData="M8.59,16.59L13.17,12 8.59,7.41 10,6l6,6 -6,6 -1.41,-1.41z"/>
</vector>

View file

@ -1,5 +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"/>
<path android:fillColor="?android:textColorPrimary" 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:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?android:colorAccent" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z"/>
</vector>

View file

@ -1,5 +1,4 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
<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:color/white" android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z"/>
<path android:fillColor="?android:colorAccent" android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z"/>
</vector>

View file

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.deniscerri.ytdl.page.CustomCommandActivity">
<FrameLayout
android:id="@+id/custom_command_frame_layout"
android:layout_width="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_margin="10dp"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="@+id/command_edittext"
android:background="@android:color/transparent"
android:text="yt-dlp "
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textMultiLine"
android:gravity="start"
android:maxLines="5" />
<TextView
android:id="@+id/custom_command_output"
android:layout_width="match_parent"
android:gravity="bottom"
android:layout_height="wrap_content" />
</LinearLayout>
</FrameLayout>
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:liftOnScroll="false"
android:background="@android:color/transparent"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/custom_command_toolbar"
android:elevation="0dp"
app:layout_scrollFlags="scroll|enterAlways|snap"
app:title="@string/custom_command"
android:layout_width="match_parent"
app:navigationIcon="@drawable/ic_back"
android:layout_height="match_parent"/>
</com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/command_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:text="Run Command"
app:icon="@drawable/ic_baseline_keyboard_arrow_right_24"
/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -1,14 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="?attr/actionBarSize"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/preference_coordinatorLayout"
xmlns:android="http://schemas.android.com/apk/res/android">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="match_parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
@ -17,10 +17,11 @@
android:id="@+id/home_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="@string/settings"
app:title="@string/more"
app:menu="@menu/empty_menu"
/>
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
tools:context=".page.MusicEditorFragment">
<!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="ello" />
</FrameLayout>

View file

@ -10,9 +10,6 @@
<item android:title="@string/music_editor"
android:icon="@drawable/ic_music_downloaded"
android:id="@+id/music_editor"/>
<item android:title="@string/cli"
android:icon="@drawable/ic_code"
android:id="@+id/cli"/>
<item android:title="@string/more"
android:icon="@drawable/ic_more"
android:id="@+id/more"/>

View file

@ -21,7 +21,7 @@
android:id="@+id/delete_history"
android:icon="@drawable/ic_delete_all"
android:title="@string/fshi_historin"
app:showAsAction="never" />
app:showAsAction="ifRoom" />
</menu>

View file

@ -0,0 +1,3 @@
<resources>
<dimen name="fab_margin">48dp</dimen>
</resources>

View file

@ -0,0 +1,3 @@
<resources>
<dimen name="fab_margin">200dp</dimen>
</resources>

View file

@ -15,7 +15,16 @@
<string-array name="video_formats">
<item>mp4</item>
<item>mkv</item>
<item>mov</item>
<item>webm</item>
</string-array>
<!-- Reply Preference -->
<string-array name="reply_entries">
<item>Reply</item>
<item>Reply to all</item>
</string-array>
<string-array name="reply_values">
<item>reply</item>
<item>reply_all</item>
</string-array>
</resources>

View file

@ -1,2 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>
<resources>
<dimen name="fab_margin">16dp</dimen></resources>

View file

@ -42,9 +42,19 @@
<string name="confirm_delete_history">Confirm</string>
<string name="search_history_hint">Search from History</string>
<string name="no_results">No Results</string>
<string name="music_editor">TAG Editor</string>
<string name="music_editor">Music Editor</string>
<string name="cli">Commands</string>
<string name="more">More</string>
<string name="update_app">Update App</string>
<string name="download_latest_version">Download the latest version of the app</string>
<string name="version">Version</string>
<string name="custom_command">Run Custom Command</string>
<string name="write_ytdlp_command">Write yt-dlp command</string>
<string name="run">Run</string>
<string name="running_ytdlp_command">Running yt-dlp Custom Command</string>
<string name="command_directory">Custom Command Directory</string>
<string name="you_are_running_latest_version">You are running the latest version!</string>
<string name="error_checking_latest_version">Error checking for latest version!</string>
<string name="couldnt_find_apk">Couldn\'t find a suitable apk!</string>
<string name="downloading_update">Downloading Update</string>
</resources>

View file

@ -0,0 +1,52 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Preference
android:layout="@layout/fragment_more"/>
<PreferenceCategory android:title="@string/updating">
<Preference
app:icon="@drawable/ic_update"
app:key="update_ytdl"
app:summary="@string/ytdl_update_hint"
app:title="@string/update_ytdl" />
<Preference
app:icon="@drawable/ic_update_app"
app:key="update_app"
app:summary="@string/download_latest_version"
app:title="@string/update_app" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/about">
<Preference
app:icon="@drawable/ic_code"
app:key="rreth"
app:summary="https://github.com/deniscerri/ytdlnis"
app:title="@string/source_code">
<intent
android:action="android.intent.action.VIEW"
android:data="https://github.com/deniscerri/ytdlnis" />
</Preference>
</PreferenceCategory>
<Preference
app:icon="@drawable/ic_baseline_keyboard_arrow_right_24"
app:allowDividerAbove="true"
app:allowDividerBelow="true"
app:key="run_command"
app:title="@string/custom_command">
<intent android:action="ytdlnis.page.CustomCommandActivity" />
</Preference>
<Preference
app:icon="@drawable/ic_settings"
app:key="open_settings"
app:title="@string/settings">
<intent android:action="ytdlnis.settings.SettingsActivity" />
</Preference>
</PreferenceScreen>

View file

@ -1,132 +1,113 @@
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory android:title="@string/directories">
<Preference
app:key="music_path"
app:icon="@drawable/ic_music_downloaded"
app:key="music_path"
app:title="@string/music_directory" />
<Preference
app:key="video_path"
app:icon="@drawable/ic_video_downloaded"
app:key="video_path"
app:title="@string/video_directory" />
<Preference
app:icon="@drawable/ic_baseline_keyboard_arrow_right_24"
app:key="command_path"
app:title="@string/command_directory" />
</PreferenceCategory>
<PreferenceCategory app:title="Downloading">
<SeekBarPreference
app:key="concurrent_fragments"
app:title="Concurrent Fragments"
android:icon="@drawable/ic_lines"
app:summary=" Number of fragments of a dash/hlsnative video that should be downloaded concurrently"
android:defaultValue="1"
android:icon="@drawable/ic_lines"
android:max="10"
app:key="concurrent_fragments"
app:min="1"
app:showSeekBarValue="true"
/>
app:summary=" Number of fragments of a dash/hlsnative video that should be downloaded concurrently"
app:title="Concurrent Fragments" />
<EditTextPreference
app:key="limit_rate"
android:icon="@drawable/ic_down"
app:title="Limit Rate"
app:key="limit_rate"
app:summary="Maximum download rate in bytes per second, e.g. 50K or 4.2M"
/>
app:title="Limit Rate" />
</PreferenceCategory>
<PreferenceCategory android:title="Processing">
<SwitchPreferenceCompat
app:key="remove_non_music"
app:icon="@drawable/ic_music"
app:defaultValue="true"
app:title="Remove non music parts" />
<PreferenceCategory android:title="Processing">
<SwitchPreferenceCompat
app:defaultValue="true"
app:icon="@drawable/ic_music"
app:key="remove_non_music"
app:summary="Remove SponsorBlock segments that contain non music parts and others"
app:title="Remove non music parts" />
<SwitchPreferenceCompat
app:key="embed_subtitles"
app:defaultValue="true"
app:summary="Embed subtitles in the video (only for mp4,webm and mkv videos)"
app:icon="@drawable/ic_subtitles"
app:title="Embed Subtitles on Videos" />
<SwitchPreferenceCompat
app:defaultValue="true"
app:icon="@drawable/ic_subtitles"
app:key="embed_subtitles"
app:summary="Embed subtitles in the video (only for mp4,webm and mkv videos)"
app:title="Embed Subtitles on Videos" />
<SwitchPreferenceCompat
app:key="embed_thumbnail"
app:defaultValue="true"
app:icon="@drawable/ic_image"
app:summary="Embed thumbnail as cover art"
app:title="Embed Thumbnail" />
<SwitchPreferenceCompat
app:defaultValue="true"
app:icon="@drawable/ic_image"
app:key="embed_thumbnail"
app:summary="Embed thumbnail as cover art"
app:title="Embed Thumbnail" />
<SwitchPreferenceCompat
app:key="add_chapters"
app:defaultValue="true"
app:icon="@drawable/ic_chapters"
app:title="Add Chapters on Videos" />
<SwitchPreferenceCompat
app:defaultValue="true"
app:icon="@drawable/ic_chapters"
app:key="add_chapters"
app:title="Add Chapters on Videos" />
<SwitchPreferenceCompat
app:key="write_thumbnail"
app:defaultValue="false"
app:icon="@drawable/ic_image"
app:title="Write Thumbnail to disk" />
<SwitchPreferenceCompat
app:defaultValue="false"
app:icon="@drawable/ic_image"
app:key="write_thumbnail"
app:title="Write Thumbnail to disk" />
<ListPreference
app:key="audio_format"
app:title="Audio Format"
android:defaultValue="mp3"
android:icon="@drawable/ic_code"
android:entries="@array/music_formats"
android:entryValues="@array/music_formats" />
<ListPreference
android:defaultValue="mp3"
android:entries="@array/music_formats"
android:entryValues="@array/music_formats"
android:icon="@drawable/ic_code"
app:key="audio_format"
app:summary="mp3"
app:title="Audio Format" />
<ListPreference
app:key="video_format"
app:title="Video Format"
android:defaultValue="mp4"
android:icon="@drawable/ic_code"
android:entries="@array/video_formats"
android:entryValues="@array/video_formats" />
<ListPreference
android:defaultValue="mp4"
android:entries="@array/video_formats"
android:entryValues="@array/video_formats"
android:icon="@drawable/ic_code"
app:key="video_format"
app:summary="mp4"
app:title="Video Format" />
<SeekBarPreference
app:key="audio_quality"
app:title="Audio Quality"
android:icon="@drawable/ic_music"
app:summary="0 (Best) 10 (Worst)"
android:defaultValue="5"
android:max="10"
app:min="0"
app:showSeekBarValue="true"
/>
<SeekBarPreference
android:defaultValue="5"
android:icon="@drawable/ic_music"
android:max="10"
app:key="audio_quality"
app:min="0"
app:showSeekBarValue="true"
app:summary="0 (Best) 10 (Worst)"
app:title="Audio Quality" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/updating">
<Preference
app:key="update_ytdl"
app:icon="@drawable/ic_update"
app:summary="@string/ytdl_update_hint"
app:title="@string/update_ytdl" />
<Preference
app:key="update_app"
app:icon="@drawable/ic_update_app"
app:summary="@string/download_latest_version"
app:title="@string/update_app" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/about">
<Preference
app:key="rreth"
app:icon="@drawable/ic_code"
app:summary="https://github.com/deniscerri/ytdlnis"
app:title="@string/source_code">
<intent
android:action="android.intent.action.VIEW"
android:data="https://github.com/deniscerri/ytdlnis"
/>
</Preference>
</PreferenceCategory>
<Preference
app:allowDividerAbove="true"
app:icon="@drawable/ic_info"
app:key="version"
app:title="@string/version"></Preference>
</PreferenceScreen>