package com.simplecity.amp_library.tagger; import android.app.Activity; import android.app.Dialog; import android.app.ProgressDialog; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.design.widget.TextInputLayout; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentManager; import android.support.v4.provider.DocumentFile; import android.text.Editable; import android.text.TextWatcher; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ScrollView; import android.widget.Toast; import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; import com.annimon.stream.Collectors; import com.annimon.stream.Stream; import com.simplecity.amp_library.R; import com.simplecity.amp_library.ShuttleApplication; import com.simplecity.amp_library.model.Album; import com.simplecity.amp_library.model.AlbumArtist; import com.simplecity.amp_library.model.Song; import com.simplecity.amp_library.utils.CustomMediaScanner; import com.simplecity.amp_library.utils.DialogUtils; import com.simplecity.amp_library.utils.SettingsManager; import com.simplecity.amp_library.utils.ShuttleUtils; import com.simplecity.amp_library.utils.ThemeUtils; import org.jaudiotagger.audio.AudioFile; import org.jaudiotagger.audio.AudioFileIO; import org.jaudiotagger.audio.exceptions.CannotReadException; import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException; import org.jaudiotagger.audio.exceptions.ReadOnlyFileException; import org.jaudiotagger.tag.FieldKey; import org.jaudiotagger.tag.Tag; import org.jaudiotagger.tag.TagException; import java.io.File; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.List; public class TaggerDialog extends DialogFragment { interface TextChangeListener { void onTextChanged(boolean isDifferent); } public static final String TAG = "TaggerDialog"; public static final int DOCUMENT_TREE_REQUEST_CODE = 901; public static final String ARG_MODEL = "model"; private MaterialDialog materialDialog; private boolean hasCheckedPermissions; private AlbumArtist albumArtist; private Album album; private Song song; List<String> originalSongPaths = new ArrayList<>(); private List<DocumentFile> documentFiles = new ArrayList<>(); private boolean showAlbum = true; private boolean showTrack = true; private EditText albumArtistEditText; private EditText artistEditText; private EditText albumEditText; private EditText titleEditText; private EditText genreEditText; private EditText yearEditText; private EditText trackEditText; private EditText trackTotalEditText; private EditText discEditText; private EditText discTotalEditText; private EditText lyricsEditText; private EditText commentEditText; private TextInputLayout albumInputLayout; private TextInputLayout titleInputLayout; private TextInputLayout trackInputLayout; private TextInputLayout discInputLayout; private TextInputLayout lyricsInputLayout; private TextInputLayout commentInputLayout; private String artistName; private String albumName; private String albumArtistName; private String title; private String genre; private String year; private String track; private String trackTotal; private String disc; private String discTotal; private String lyrics; private String comment; private TextChangeListener textChangeListener = isDifferent -> materialDialog.getActionButton(DialogAction.POSITIVE).setEnabled(isDifferent); public static TaggerDialog newInstance(Serializable model) { Bundle args = new Bundle(); args.putSerializable(ARG_MODEL, model); TaggerDialog fragment = new TaggerDialog(); fragment.setArguments(args); return fragment; } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Serializable model = getArguments().getSerializable(ARG_MODEL); if (model instanceof AlbumArtist) { albumArtist = (AlbumArtist) model; originalSongPaths = Stream.of(albumArtist.albums) .flatMap(value -> Stream.of(value.paths)) .collect(Collectors.toList()); showAlbum = false; showTrack = false; } else if (model instanceof Album) { album = (Album) model; originalSongPaths = album.paths; showTrack = false; } else if (model instanceof Song) { song = (Song) model; originalSongPaths.add(song.path); } if (originalSongPaths == null || originalSongPaths.isEmpty()) { dismiss(); //Todo: refine & extract Toast.makeText(getContext(), R.string.tag_retrieve_error, Toast.LENGTH_LONG).show(); } } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { View customView = LayoutInflater.from(getContext()).inflate(R.layout.dialog_tagger, null, false); setupViews(customView); populateViews(); materialDialog = DialogUtils.getBuilder(getContext()) .title(R.string.edit_tags) .customView(customView, false) .positiveText(R.string.save) .onPositive((dialog, which) -> saveTags()) .negativeText(R.string.close) .onNegative((dialog, which) -> dismiss()) .autoDismiss(false) .build(); materialDialog.getActionButton(DialogAction.POSITIVE).setEnabled(false); return materialDialog; } private void setupViews(View rootView) { ScrollView scrollView = (ScrollView) rootView.findViewById(R.id.scrollview); ThemeUtils.themeScrollView(scrollView); titleEditText = (EditText) rootView.findViewById(R.id.new_track_name); titleInputLayout = getParent(titleEditText); ThemeUtils.themeEditText(titleEditText); albumEditText = (EditText) rootView.findViewById(R.id.new_album_name); albumInputLayout = getParent(albumEditText); ThemeUtils.themeEditText(albumEditText); artistEditText = (EditText) rootView.findViewById(R.id.new_artist_name); ThemeUtils.themeEditText(artistEditText); albumArtistEditText = (EditText) rootView.findViewById(R.id.new_album_artist_name); ThemeUtils.themeEditText(albumArtistEditText); genreEditText = (EditText) rootView.findViewById(R.id.new_genre_name); ThemeUtils.themeEditText(genreEditText); yearEditText = (EditText) rootView.findViewById(R.id.new_year_number); ThemeUtils.themeEditText(yearEditText); trackEditText = (EditText) rootView.findViewById(R.id.new_track_number); trackInputLayout = getParent(trackEditText); ThemeUtils.themeEditText(trackEditText); trackTotalEditText = (EditText) rootView.findViewById(R.id.new_track_total); ThemeUtils.themeEditText(trackTotalEditText); discEditText = (EditText) rootView.findViewById(R.id.new_disc_number); discInputLayout = getParent(discEditText); ThemeUtils.themeEditText(discEditText); discTotalEditText = (EditText) rootView.findViewById(R.id.new_disc_total); ThemeUtils.themeEditText(discTotalEditText); lyricsEditText = (EditText) rootView.findViewById(R.id.new_lyrics); lyricsInputLayout = getParent(lyricsEditText); ThemeUtils.themeEditText(lyricsEditText); commentEditText = (EditText) rootView.findViewById(R.id.new_comment); commentInputLayout = getParent(commentEditText); ThemeUtils.themeEditText(commentEditText); if (albumArtist != null || album != null) { titleInputLayout.setVisibility(View.GONE); titleEditText.setVisibility(View.GONE); trackInputLayout.setVisibility(View.GONE); trackEditText.setVisibility(View.GONE); trackTotalEditText.setVisibility(View.GONE); discInputLayout.setVisibility(View.GONE); discEditText.setVisibility(View.GONE); lyricsInputLayout.setVisibility(View.GONE); lyricsEditText.setVisibility(View.GONE); commentInputLayout.setVisibility(View.GONE); commentEditText.setVisibility(View.GONE); } if (albumArtist != null) { albumInputLayout.setVisibility(View.GONE); albumEditText.setVisibility(View.GONE); } } void populateViews() { if (originalSongPaths == null || originalSongPaths.isEmpty()) { return; } try { AudioFile mAudioFile = AudioFileIO.read(new File(originalSongPaths.get(0))); Tag tag = mAudioFile.getTag(); if (tag == null) { return; } title = tag.getFirst(FieldKey.TITLE); albumName = tag.getFirst(FieldKey.ALBUM); artistName = tag.getFirst(FieldKey.ARTIST); try { albumArtistName = tag.getFirst(FieldKey.ALBUM_ARTIST); } catch (UnsupportedOperationException ignored) { } genre = tag.getFirst(FieldKey.GENRE); year = tag.getFirst(FieldKey.YEAR); track = tag.getFirst(FieldKey.TRACK); try { trackTotal = tag.getFirst(FieldKey.TRACK_TOTAL); } catch (UnsupportedOperationException ignored) { } disc = tag.getFirst(FieldKey.DISC_NO); discTotal = tag.getFirst(FieldKey.DISC_TOTAL); lyrics = tag.getFirst(FieldKey.LYRICS); comment = tag.getFirst(FieldKey.COMMENT); } catch (IOException | InvalidAudioFrameException | TagException | ReadOnlyFileException | CannotReadException e) { Log.e(TAG, "Failed to read tags. " + e.toString()); } titleEditText.setText(title); titleEditText.setSelection(titleEditText.getText().length()); titleEditText.addTextChangedListener(new CustomTextWatcher(titleEditText, textChangeListener)); albumEditText.setText(albumName); albumEditText.setSelection(albumEditText.getText().length()); albumEditText.addTextChangedListener(new CustomTextWatcher(titleEditText, textChangeListener)); artistEditText.setText(artistName); artistEditText.setSelection(artistEditText.getText().length()); artistEditText.addTextChangedListener(new CustomTextWatcher(artistEditText, textChangeListener)); albumArtistEditText.setText(albumArtistName); albumArtistEditText.setSelection(albumArtistEditText.getText().length()); albumArtistEditText.addTextChangedListener(new CustomTextWatcher(albumArtistEditText, textChangeListener)); genreEditText.setText(genre); genreEditText.setSelection(genreEditText.getText().length()); genreEditText.addTextChangedListener(new CustomTextWatcher(genreEditText, textChangeListener)); yearEditText.setText(String.valueOf(year)); yearEditText.setSelection(yearEditText.getText().length()); yearEditText.addTextChangedListener(new CustomTextWatcher(yearEditText, textChangeListener)); trackEditText.setText(String.valueOf(track)); trackEditText.setSelection(trackEditText.getText().length()); trackEditText.addTextChangedListener(new CustomTextWatcher(trackEditText, textChangeListener)); trackTotalEditText.setText(String.valueOf(trackTotal)); trackTotalEditText.setSelection(trackTotalEditText.getText().length()); trackTotalEditText.addTextChangedListener(new CustomTextWatcher(trackTotalEditText, textChangeListener)); discEditText.setText(String.valueOf(disc)); discEditText.setSelection(discEditText.getText().length()); discEditText.addTextChangedListener(new CustomTextWatcher(discEditText, textChangeListener)); discTotalEditText.setText(String.valueOf(discTotal)); discTotalEditText.setSelection(discTotalEditText.getText().length()); discTotalEditText.addTextChangedListener(new CustomTextWatcher(discTotalEditText, textChangeListener)); lyricsEditText.setText(lyrics); lyricsEditText.setSelection(lyricsEditText.getText().length()); lyricsEditText.addTextChangedListener(new CustomTextWatcher(lyricsEditText, textChangeListener)); commentEditText.setText(comment); commentEditText.setSelection(commentEditText.getText().length()); commentEditText.addTextChangedListener(new CustomTextWatcher(commentEditText, textChangeListener)); } private void saveTags() { ProgressDialog progressDialog = new ProgressDialog(getContext()); progressDialog.setMessage(getString(R.string.tag_editor_check_permission)); progressDialog.setIndeterminate(true); progressDialog.setCancelable(false); progressDialog.show(); CheckDocumentPermissionsTask task = new CheckDocumentPermissionsTask( originalSongPaths, documentFiles, hasPermission -> { progressDialog.dismiss(); if (hasPermission) { final ProgressDialog saveProgressDialog = new ProgressDialog(getContext()); saveProgressDialog.setMessage(getResources().getString(R.string.saving_tags)); saveProgressDialog.setMax(originalSongPaths.size()); saveProgressDialog.setIndeterminate(false); saveProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); saveProgressDialog.setCancelable(false); saveProgressDialog.show(); TaggerTask.TagCompletionListener listener = new TaggerTask.TagCompletionListener() { @Override public void onSuccess() { CustomMediaScanner.scanFiles(originalSongPaths, null); if (getContext() != null && isResumed()) { saveProgressDialog.dismiss(); dismiss(); } } @Override public void onFailure() { if (getContext() != null && isResumed()) { saveProgressDialog.dismiss(); if (ShuttleUtils.hasKitKat() && !ShuttleUtils.hasLollipop()) { Toast.makeText(getContext(), R.string.tag_error_kitkat, Toast.LENGTH_LONG).show(); } else if (ShuttleUtils.hasLollipop()) { Toast.makeText(getContext(), R.string.tag_error_lollipop, Toast.LENGTH_LONG).show(); } else { Toast.makeText(getContext(), R.string.tag_edit_error, Toast.LENGTH_LONG).show(); } dismiss(); } } @Override public void onProgress(int progress) { saveProgressDialog.setProgress(progress); } }; TaggerTask taggerTask = new TaggerTask() .showAlbum(showAlbum) .showTrack(showTrack) .setPaths(originalSongPaths) .setDocumentfiles(documentFiles) .title(titleEditText.getText().toString()) .album(albumEditText.getText().toString()) .artist(artistEditText.getText().toString()) .albumArtist(albumArtistEditText.getText().toString()) .year(yearEditText.getText().toString()) .track(trackEditText.getText().toString()) .trackTotal(trackTotalEditText.getText().toString()) .disc(discEditText.getText().toString()) .discTotal(discTotalEditText.getText().toString()) .lyrics(lyricsEditText.getText().toString()) .comment(commentEditText.getText().toString()) .genre(genreEditText.getText().toString()) .listener(listener) .build(); taggerTask.execute(); } else { TaggerUtils.showChooseDocumentDialog(getContext(), (dialog1, which1) -> { if (ShuttleUtils.hasLollipop()) { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); if (intent.resolveActivity(ShuttleApplication.getInstance().getPackageManager()) != null) { getActivity().startActivityForResult(intent, DOCUMENT_TREE_REQUEST_CODE); } else { Toast.makeText(getContext(), R.string.R_string_toast_no_document_provider, Toast.LENGTH_LONG).show(); } } }, hasCheckedPermissions); hasCheckedPermissions = true; } }); task.execute(); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (ShuttleUtils.hasKitKat()) { switch (requestCode) { case DOCUMENT_TREE_REQUEST_CODE: if (resultCode == Activity.RESULT_OK) { Uri treeUri = data.getData(); ShuttleApplication.getInstance().getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); SettingsManager.getInstance().setDocumentTreeUri(data.getData().toString()); saveTags(); } break; } } } public void show(FragmentManager fragmentManager) { show(fragmentManager, TAG); } private static class CustomTextWatcher implements TextWatcher { private String originalText; private TextChangeListener textChangeListener; public CustomTextWatcher(EditText editText, TextChangeListener textChangeListener) { this.originalText = editText.getText().toString(); this.textChangeListener = textChangeListener; } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { textChangeListener.onTextChanged(!s.toString().equals(originalText)); } } private TextInputLayout getParent(EditText editText) { if (editText.getParent() instanceof TextInputLayout) { return (TextInputLayout) editText.getParent(); } else if (editText.getParent() instanceof FrameLayout) { return (TextInputLayout) editText.getParent().getParent(); } return null; } }