diff --git a/.idea/assetWizardSettings.xml b/.idea/assetWizardSettings.xml index cd6d91a3..0f95f84e 100644 --- a/.idea/assetWizardSettings.xml +++ b/.idea/assetWizardSettings.xml @@ -23,7 +23,7 @@ @@ -34,7 +34,7 @@ diff --git a/.idea/misc.xml b/.idea/misc.xml index 53c71f02..8afb8e95 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -37,6 +37,7 @@ + @@ -46,6 +47,7 @@ + @@ -53,17 +55,21 @@ + + + + @@ -79,6 +85,7 @@ + diff --git a/app/build.gradle b/app/build.gradle index 80581e87..2d84a367 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' - -} +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5e51f8c2..53d5818d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,22 +1,23 @@ + package="com.deniscerri.ytdl" > + - + android:theme="@style/AppDefaultTheme" > + @@ -26,20 +27,31 @@ - + android:parentActivityName=".MainActivity" > + + + + + + + + + + + + android:name="ytAPIkey" + android:value="${ytAPI}" /> \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdl/App.java b/app/src/main/java/com/deniscerri/ytdl/App.java index 24c70d87..785cff17 100644 --- a/app/src/main/java/com/deniscerri/ytdl/App.java +++ b/app/src/main/java/com/deniscerri/ytdl/App.java @@ -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() { diff --git a/app/src/main/java/com/deniscerri/ytdl/DownloaderService.java b/app/src/main/java/com/deniscerri/ytdl/DownloaderService.java index e53f6bf5..6600590c 100644 --- a/app/src/main/java/com/deniscerri/ytdl/DownloaderService.java +++ b/app/src/main/java/com/deniscerri/ytdl/DownloaderService.java @@ -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; } diff --git a/app/src/main/java/com/deniscerri/ytdl/MainActivity.java b/app/src/main/java/com/deniscerri/ytdl/MainActivity.java index e28c7f56..3689ca53 100644 --- a/app/src/main/java/com/deniscerri/ytdl/MainActivity.java +++ b/app/src/main/java/com/deniscerri/ytdl/MainActivity.java @@ -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); } diff --git a/app/src/main/java/com/deniscerri/ytdl/page/CustomCommandActivity.java b/app/src/main/java/com/deniscerri/ytdl/page/CustomCommandActivity.java new file mode 100644 index 00000000..854d78ca --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdl/page/CustomCommandActivity.java @@ -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; + } +} diff --git a/app/src/main/java/com/deniscerri/ytdl/page/HistoryFragment.java b/app/src/main/java/com/deniscerri/ytdl/page/HistoryFragment.java index 2b5660dc..7fcb37be 100644 --- a/app/src/main/java/com/deniscerri/ytdl/page/HistoryFragment.java +++ b/app/src/main/java/com/deniscerri/ytdl/page/HistoryFragment.java @@ -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(){ diff --git a/app/src/main/java/com/deniscerri/ytdl/page/HomeFragment.java b/app/src/main/java/com/deniscerri/ytdl/page/HomeFragment.java index fe614efa..5fc57780 100644 --- a/app/src/main/java/com/deniscerri/ytdl/page/HomeFragment.java +++ b/app/src/main/java/com/deniscerri/ytdl/page/HomeFragment.java @@ -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); } } diff --git a/app/src/main/java/com/deniscerri/ytdl/page/MoreFragment.java b/app/src/main/java/com/deniscerri/ytdl/page/MoreFragment.java new file mode 100644 index 00000000..11e616e2 --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdl/page/MoreFragment.java @@ -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 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(); + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdl/page/MusicEditorFragment.java b/app/src/main/java/com/deniscerri/ytdl/page/MusicEditorFragment.java new file mode 100644 index 00000000..78656926 --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdl/page/MusicEditorFragment.java @@ -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); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdl/page/settings/SettingsFragment.java b/app/src/main/java/com/deniscerri/ytdl/page/settings/SettingsFragment.java index 4be31ff2..74d43284 100644 --- a/app/src/main/java/com/deniscerri/ytdl/page/settings/SettingsFragment.java +++ b/app/src/main/java/com/deniscerri/ytdl/page/settings/SettingsFragment.java @@ -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 musicPathResultLauncher = registerForActivityResult( @@ -231,6 +233,17 @@ public class SettingsFragment extends PreferenceFragmentCompat { } }); + ActivityResultLauncher commandPathResultLauncher = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + new ActivityResultCallback() { + @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); - } - - } \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdl/NotificationUtil.java b/app/src/main/java/com/deniscerri/ytdl/util/NotificationUtil.java similarity index 85% rename from app/src/main/java/com/deniscerri/ytdl/NotificationUtil.java rename to app/src/main/java/com/deniscerri/ytdl/util/NotificationUtil.java index 2aa34828..3ac0f5d9 100644 --- a/app/src/main/java/com/deniscerri/ytdl/NotificationUtil.java +++ b/app/src/main/java/com/deniscerri/ytdl/util/NotificationUtil.java @@ -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); } } diff --git a/app/src/main/java/com/deniscerri/ytdl/util/UpdateUtil.java b/app/src/main/java/com/deniscerri/ytdl/util/UpdateUtil.java new file mode 100644 index 00000000..ded1c4e6 --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdl/util/UpdateUtil.java @@ -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){} + } + + +} diff --git a/app/src/main/res/drawable/ic_baseline_keyboard_arrow_right_24.xml b/app/src/main/res/drawable/ic_baseline_keyboard_arrow_right_24.xml new file mode 100644 index 00000000..15bfe6de --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_keyboard_arrow_right_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 index e6afffed..b6f77b76 100644 --- a/app/src/main/res/drawable/ic_delete_all.xml +++ b/app/src/main/res/drawable/ic_delete_all.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/drawable/ic_info.xml b/app/src/main/res/drawable/ic_info.xml new file mode 100644 index 00000000..7c3f0b35 --- /dev/null +++ b/app/src/main/res/drawable/ic_info.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml index b240b830..487d9c85 100644 --- a/app/src/main/res/drawable/ic_settings.xml +++ b/app/src/main/res/drawable/ic_settings.xml @@ -1,5 +1,4 @@ - - + diff --git a/app/src/main/res/layout/activity_custom_command.xml b/app/src/main/res/layout/activity_custom_command.xml new file mode 100644 index 00000000..84906eaa --- /dev/null +++ b/app/src/main/res/layout/activity_custom_command.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_more.xml similarity index 88% rename from app/src/main/res/layout/fragment_settings.xml rename to app/src/main/res/layout/fragment_more.xml index 522b9976..ebbe9341 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_more.xml @@ -1,14 +1,14 @@ @@ -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" /> + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_music_editor.xml b/app/src/main/res/layout/fragment_music_editor.xml new file mode 100644 index 00000000..0641bc32 --- /dev/null +++ b/app/src/main/res/layout/fragment_music_editor.xml @@ -0,0 +1,14 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/bottom_nav_menu.xml b/app/src/main/res/menu/bottom_nav_menu.xml index e50f391f..a990783c 100644 --- a/app/src/main/res/menu/bottom_nav_menu.xml +++ b/app/src/main/res/menu/bottom_nav_menu.xml @@ -10,9 +10,6 @@ - diff --git a/app/src/main/res/menu/history_menu.xml b/app/src/main/res/menu/history_menu.xml index b13db6a2..964e8c7b 100644 --- a/app/src/main/res/menu/history_menu.xml +++ b/app/src/main/res/menu/history_menu.xml @@ -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" /> diff --git a/app/src/main/res/values-land/dimens.xml b/app/src/main/res/values-land/dimens.xml new file mode 100644 index 00000000..10125a35 --- /dev/null +++ b/app/src/main/res/values-land/dimens.xml @@ -0,0 +1,3 @@ + + 48dp + \ No newline at end of file diff --git a/app/src/main/res/values-w1240dp/dimens.xml b/app/src/main/res/values-w1240dp/dimens.xml new file mode 100644 index 00000000..ec434d3d --- /dev/null +++ b/app/src/main/res/values-w1240dp/dimens.xml @@ -0,0 +1,3 @@ + + 200dp + \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index d3e2922a..e8943eb6 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -15,7 +15,16 @@ mp4 mkv - mov webm + + + Reply + Reply to all + + + + reply + reply_all + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index a6b3daec..0247a957 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -1,2 +1,3 @@ - \ No newline at end of file + + 16dp \ 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 7f0e5038..0ca150a9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -42,9 +42,19 @@ Confirm Search from History No Results - TAG Editor + Music Editor Commands More Update App Download the latest version of the app + Version + Run Custom Command + Write yt-dlp command + Run + Running yt-dlp Custom Command + Custom Command Directory + You are running the latest version! + Error checking for latest version! + Couldn\'t find a suitable apk! + Downloading Update diff --git a/app/src/main/res/xml/more_preferences.xml b/app/src/main/res/xml/more_preferences.xml new file mode 100644 index 00000000..c622d9ca --- /dev/null +++ b/app/src/main/res/xml/more_preferences.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml index f3f3226c..7a29095c 100644 --- a/app/src/main/res/xml/root_preferences.xml +++ b/app/src/main/res/xml/root_preferences.xml @@ -1,132 +1,113 @@ - + + + + + app:summary=" Number of fragments of a dash/hlsnative video that should be downloaded concurrently" + app:title="Concurrent Fragments" /> + app:title="Limit Rate" /> - - + + - + - + - + - + - + - + - + - - - - - - - - - - - - - + \ No newline at end of file