From c82ab2c403a575becc3fce29b8c2f4a00f133d15 Mon Sep 17 00:00:00 2001 From: andintheclouds Date: Wed, 18 Jan 2023 23:11:10 +0100 Subject: [PATCH] Improve performance of file access SDK > 25 --- app/src/main/AndroidManifest.xml | 1 - .../chordreader2/MainActivity.java | 2 +- .../chordreader2/helper/ChordDictionary.java | 301 +++---- .../chordreader2/helper/PreferenceHelper.java | 1 + .../chordreader2/helper/SaveFileHelper.java | 835 +++++++++++------- .../chordreader2/model/DataViewModel.java | 11 +- .../model/SongViewFragmentViewModel.java | 14 +- .../ui/DraggableListFragment.java | 7 +- .../chordreader2/ui/ListFragment.java | 76 +- .../chordreader2/ui/SettingsFragment.java | 6 + .../chordreader2/ui/SongViewFragment.java | 124 +-- app/src/main/res/raw/about_body_de.htm | 5 + app/src/main/res/raw/about_body_en.htm | 5 + app/src/main/res/raw/about_body_fr.htm | 5 + app/src/main/res/xml/settings.xml | 3 +- build.gradle | 2 +- .../org.hollowbamboo.chordreader2.yml | 15 +- 17 files changed, 848 insertions(+), 565 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 25908e7..3a9efff 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -14,7 +14,6 @@ > chordsToGuitarChords = null; - private static String customChordVars; - - public static void initialize(Context context) { - Map> dictionary = new HashMap<>(); - - // load custom chord variations - try { - customChordVars = SaveFileHelper.openFile(context,"customChordVariations_DO_NOT_EDIT.crd"); - - if (!customChordVars.isEmpty()) { - loadIntoChordDictionary(context, -1, NoteNaming.English, dictionary); - } else - throw new IOException(); - - } catch (IOException e) { - //no customChordVariations_DO_NOT_EDIT.crd - ignore - Log.e(LOG_TAG, e + " - No customChordVariations file, load default"); - - try { - loadIntoChordDictionary(context, R.raw.chords1, NoteNaming.English, dictionary); - loadIntoChordDictionary(context, R.raw.chords2, NoteNaming.NorthernEuropean, dictionary); - - } catch (IOException f) { - Log.e(LOG_TAG, f + " - unexpected exception, couldn't initialize ChordDictionary"); - } catch (Exception f) { - Log.e(LOG_TAG, f + " - unknown exception, couldn't initialize ChordDictionary"); - } - } - if (!dictionary.isEmpty()) - Log.i(LOG_TAG, "Chord Dictionary initialized"); - chordsToGuitarChords = dictionary; - } - - private static void loadIntoChordDictionary(Context context, int resId, NoteNaming noteNaming, Map> dictionary) throws IOException { - InputStream inputStream; - - if (resId == -1) { - inputStream = new ByteArrayInputStream(customChordVars.getBytes()); - } else { - inputStream = context.getResources().openRawResource(resId); - } - - BufferedReader bufferedReader = null; - - try { - bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); - while (bufferedReader.ready()) { - String line = bufferedReader.readLine(); - line = line.trim(); - String[] tokens = StringUtil.split(line, ":"); - - String chordText = tokens[0].trim(); - String guitarChord = tokens[1].trim(); - - Chord chord = ChordParser.parseChord(chordText, noteNaming); - - if (chord == null) { - Log.w(LOG_TAG, "Unable to parse chord text - skipping:" + chordText); - continue; - } - - // map chords to their string guitar chord representations - // note that there may be multiples - e.g. there are several ways - // to play a G chord - List existingValue = dictionary.get(chord); - if (existingValue == null) { - dictionary.put(chord, new ArrayList<>(Collections.singleton(guitarChord))); - } else if (!existingValue.contains(guitarChord)) { - existingValue.add(guitarChord); - } - - } - } finally { - if (bufferedReader != null) { - bufferedReader.close(); - } - } - } - - public static boolean isInitialized() { - return chordsToGuitarChords != null; - } - - public static List getGuitarChordsForChord(Chord chord) { - List result = chordsToGuitarChords.get(chord); - return result != null ? result : Collections.emptyList(); - } - - public static void setGuitarChordsForChord(Context context, Chord chord, List newGuitarChords) { - List existingValue = chordsToGuitarChords.get(chord); - if (existingValue != null) { - chordsToGuitarChords.remove(chord); - } - chordsToGuitarChords.put(chord, newGuitarChords); - - saveChordDictionaryToFile(context); - } - - private static void saveChordDictionaryToFile(Context context) { - StringBuilder stringBuilder = new StringBuilder(); - for (Object key : chordsToGuitarChords.keySet()) { - String chord = ((Chord) key).toPrintableString(NoteNaming.English); - List chordVarsList = chordsToGuitarChords.get(key); - - for (String chordVar : chordVarsList) { - stringBuilder - .append(chord) - .append(": ") - .append(chordVar) - .append('\n'); - } - } - - final String result = stringBuilder.substring(0, stringBuilder.length() - 1); // cut off final newline - - // do in background to avoid jankiness - HandlerThread handlerThread = new HandlerThread("SaveChordsHandlerThread"); - handlerThread.start(); - - Handler asyncHandler = new Handler(handlerThread.getLooper()) { - @Override - public void handleMessage(Message msg) { - super.handleMessage(msg); - Boolean successfullySavedLog = (Boolean) msg.obj; - - String toastMessage; - - if(successfullySavedLog) { - toastMessage = context.getString(R.string.file_saved); - } else { - toastMessage = context.getString(R.string.unable_to_save_file); - } - - // must be called on the main thread - Handler mainThread = new Handler(Looper.getMainLooper()); - mainThread.post(new Runnable() { - @Override - public void run() { - Toast.makeText(context, toastMessage, Toast.LENGTH_SHORT).show(); - } - }); - - - handlerThread.quit(); - } - }; - - Runnable runnable = () -> { - // your async code goes here. - Message message = new Message(); - message.obj = SaveFileHelper.saveFile(context, result, "customChordVariations_DO_NOT_EDIT.crd"); - - asyncHandler.sendMessage(message); - }; - - asyncHandler.post(runnable); - } + private static final String LOG_TAG = "ChordDictionary"; + + // maps chords to finger positions on guitar frets, e.g. 133211 + private static Map> chordsToGuitarChords = null; + private static String customChordVars; + + public static void initialize(Context context) { + Map> dictionary = new HashMap<>(); + + // load custom chord variations + try { + customChordVars = SaveFileHelper.openFile(context, "customChordVariations_DO_NOT_EDIT.crd"); + + if (!customChordVars.isEmpty()) { + loadIntoChordDictionary(context, -1, NoteNaming.English, dictionary); + } else + throw new IOException(); + + } catch (IOException e) { + //no customChordVariations_DO_NOT_EDIT.crd - ignore + Log.e(LOG_TAG, e + " - No customChordVariations file, load default"); + + try { + loadIntoChordDictionary(context, R.raw.chords1, NoteNaming.English, dictionary); + loadIntoChordDictionary(context, R.raw.chords2, NoteNaming.NorthernEuropean, dictionary); + + } catch (IOException f) { + Log.e(LOG_TAG, f + " - unexpected exception, couldn't initialize ChordDictionary"); + } catch (Exception f) { + Log.e(LOG_TAG, f + " - unknown exception, couldn't initialize ChordDictionary"); + } + } + if (!dictionary.isEmpty()) + Log.i(LOG_TAG, "Chord Dictionary initialized"); + chordsToGuitarChords = dictionary; + } + + private static void loadIntoChordDictionary(Context context, int resId, NoteNaming noteNaming, Map> dictionary) throws IOException { + InputStream inputStream; + + if (resId == -1) { + inputStream = new ByteArrayInputStream(customChordVars.getBytes()); + } else { + inputStream = context.getResources().openRawResource(resId); + } + + BufferedReader bufferedReader = null; + + try { + bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + while (bufferedReader.ready()) { + String line = bufferedReader.readLine(); + line = line.trim(); + String[] tokens = StringUtil.split(line, ":"); + + String chordText = tokens[0].trim(); + String guitarChord = tokens[1].trim(); + + Chord chord = ChordParser.parseChord(chordText, noteNaming); + + if (chord == null) { + Log.w(LOG_TAG, "Unable to parse chord text - skipping:" + chordText); + continue; + } + + // map chords to their string guitar chord representations + // note that there may be multiples - e.g. there are several ways + // to play a G chord + List existingValue = dictionary.get(chord); + if (existingValue == null) { + dictionary.put(chord, new ArrayList<>(Collections.singleton(guitarChord))); + } else if (!existingValue.contains(guitarChord)) { + existingValue.add(guitarChord); + } + + } + } finally { + if (bufferedReader != null) { + bufferedReader.close(); + } + } + } + + public static boolean isInitialized() { + return chordsToGuitarChords != null; + } + + public static List getGuitarChordsForChord(Chord chord) { + List result = chordsToGuitarChords.get(chord); + return result != null ? result : Collections.emptyList(); + } + + public static void setGuitarChordsForChord(Context context, Chord chord, List newGuitarChords) { + List existingValue = chordsToGuitarChords.get(chord); + if (existingValue != null) { + chordsToGuitarChords.remove(chord); + } + chordsToGuitarChords.put(chord, newGuitarChords); + + saveChordDictionaryToFile(context); + } + + private static void saveChordDictionaryToFile(Context context) { + StringBuilder stringBuilder = new StringBuilder(); + for (Object key : chordsToGuitarChords.keySet()) { + String chord = ((Chord) key).toPrintableString(NoteNaming.English); + List chordVarsList = chordsToGuitarChords.get(key); + + for (String chordVar : chordVarsList) { + stringBuilder + .append(chord) + .append(": ") + .append(chordVar) + .append('\n'); + } + } + + final String result = stringBuilder.substring(0, stringBuilder.length() - 1); // cut off final newline + + Boolean successfullySavedLog = SaveFileHelper.saveFile( + context, result, "customChordVariations_DO_NOT_EDIT.crd"); + + String toastMessage; + + if (successfullySavedLog) { + toastMessage = context.getString(R.string.file_saved); + } else { + toastMessage = context.getString(R.string.unable_to_save_file); + } + + // must be called on the main thread + Handler mainThread = new Handler(Looper.getMainLooper()); + mainThread.post(new Runnable() { + @Override + public void run() { + Toast.makeText(context, toastMessage, Toast.LENGTH_SHORT).show(); + } + }); + + } } diff --git a/app/src/main/java/org/hollowbamboo/chordreader2/helper/PreferenceHelper.java b/app/src/main/java/org/hollowbamboo/chordreader2/helper/PreferenceHelper.java index 889e141..e21f648 100644 --- a/app/src/main/java/org/hollowbamboo/chordreader2/helper/PreferenceHelper.java +++ b/app/src/main/java/org/hollowbamboo/chordreader2/helper/PreferenceHelper.java @@ -25,6 +25,7 @@ public static void clearCache() { colorScheme = null; noteNaming = null; searchEngineURL = null; + storageLocation = null; } private static void cacheTextsize(Context context, int dimenId) { diff --git a/app/src/main/java/org/hollowbamboo/chordreader2/helper/SaveFileHelper.java b/app/src/main/java/org/hollowbamboo/chordreader2/helper/SaveFileHelper.java index 22e7a13..0923958 100644 --- a/app/src/main/java/org/hollowbamboo/chordreader2/helper/SaveFileHelper.java +++ b/app/src/main/java/org/hollowbamboo/chordreader2/helper/SaveFileHelper.java @@ -2,13 +2,21 @@ import static android.os.Build.VERSION.SDK_INT; +import android.content.ContentResolver; import android.content.Context; +import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.Environment; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; import android.os.ParcelFileDescriptor; +import android.provider.DocumentsContract; import android.text.TextUtils; +import android.util.Log; +import androidx.annotation.RequiresApi; import androidx.documentfile.provider.DocumentFile; import org.hollowbamboo.chordreader2.util.UtilLogger; @@ -29,328 +37,517 @@ import java.util.Collections; import java.util.Date; import java.util.List; -import java.util.Objects; +import java.util.concurrent.CountDownLatch; public class SaveFileHelper { - private static UtilLogger log = new UtilLogger(SaveFileHelper.class); - - public static boolean checkIfSdCardExists(Context context) { - - if (SDK_INT < Build.VERSION_CODES.O) { - File sdcardDir = Environment.getExternalStorageDirectory(); - - return sdcardDir != null && sdcardDir.listFiles() != null; - } else { - return Objects.requireNonNull(DocumentFile.fromTreeUri(context, PreferenceHelper.getStorageLocation(context))).exists(); - } - } - - public static boolean fileExists(Context context, String filename) { - if (SDK_INT < Build.VERSION_CODES.O) { - File catlogDir = getBaseDirectory(); - File file = new File(catlogDir, filename); - return file.exists(); - } else { - DocumentFile file = Objects.requireNonNull(DocumentFile.fromTreeUri(context, PreferenceHelper.getStorageLocation(context))).findFile(filename); - return file != null && file.exists(); - - } - } - - public static void deleteFile(Context context,String filename) { - if (SDK_INT < Build.VERSION_CODES.O) { - - File catlogDir = getBaseDirectory(); - File file = new File(catlogDir, filename); - - if(file.exists()) - file.delete(); - - } else { - DocumentFile file = Objects.requireNonNull(DocumentFile.fromTreeUri(context, PreferenceHelper.getStorageLocation(context))).findFile(filename); - - if(file != null && file.exists()) - file.delete(); - } - } - - public static Date getLastModifiedDate(String filename) { - - File catlogDir = getBaseDirectory(); - - File file = new File(catlogDir, filename); - - if(file.exists()) { - return new Date(file.lastModified()); - } else { - // shouldn't happen - log.e("file last modified date not found: %s", filename); - return new Date(); - } - } - - public static List getSavedSongNames(Context context) { - List fileNames = getFilenamesInBaseDirectory(context); - -// Collections.sort(tempArrayList, new Comparator() { -// -// @Override -// public int compare(File object1, File object2) { -// return new Long(object2.lastModified()).compareTo(object1.lastModified()); -// } -// }); - - List result = new ArrayList(); - - for (String file : fileNames) { - if(file.endsWith(".txt")) - result.add(file.replace(".txt", "")); - } - return result; - } - - - public static List getSavedSetListNames(Context context) { - List fileNames = getFilenamesInBaseDirectory(context); - - -// Collections.sort(tempArrayList, new Comparator() { -// -// @Override -// public int compare(File object1, File object2) { -// return new Long(object2.lastModified()).compareTo(object1.lastModified()); -// } -// }); - - List result = new ArrayList(); - - for (String file : fileNames) { - if(file.endsWith(".pl")) - result.add(file.replace(".pl", "")); - } - - return result; - } - - public static List getFilenamesInBaseDirectory(Context context) { - - List result = new ArrayList<>(); - - if (SDK_INT < Build.VERSION_CODES.O) { - File baseDir = getBaseDirectory(); - File[] filesArray = baseDir.listFiles(); - - if (filesArray != null) { - for (File file : filesArray) { - String fileName = file.getName(); - result.add(fileName); - } - } - } else { - DocumentFile documentFile = DocumentFile.fromTreeUri(context, PreferenceHelper.getStorageLocation(context)); - DocumentFile[] documentFiles = new DocumentFile[0]; - if (documentFile != null) { - documentFiles = documentFile.listFiles(); - } - - for (DocumentFile d : documentFiles) { - result.add(d.getName()); - } - } - - if(result.isEmpty()) { - return Collections.emptyList(); - } - - return result; - } - - public static boolean isInvalidFilename(CharSequence filename) { - - String filenameAsString; - - return TextUtils.isEmpty(filename) - || (filenameAsString = filename.toString()).contains("/") - || filenameAsString.contains(":") - || filenameAsString.contains("\\") - || filenameAsString.contains("*") - || filenameAsString.contains("|") - || filenameAsString.contains("<") - || filenameAsString.contains(">") - || filenameAsString.contains("?"); - - } - - public static String openFile(Context context, String filename) { - - BufferedReader bufferedReader = null; - InputStream inputStream = null; - - if (SDK_INT < Build.VERSION_CODES.O) { - File baseDir = getBaseDirectory(); - File logFile; - - if (!(filename == null)) - logFile = new File(baseDir, filename); - else - return ""; - - try { - bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(logFile))); - } catch (IOException ex) { - log.e(ex, "couldn't read file"); - } - } else { - - DocumentFile logFile, documentFile; - - try { - documentFile = DocumentFile.fromTreeUri(context, PreferenceHelper.getStorageLocation(context)); - } catch (Exception e) { - return ""; - } - - if (!(documentFile == null)) - logFile = documentFile.findFile(filename); - else - return ""; - - try { - if (!(logFile == null)) - inputStream = context.getContentResolver().openInputStream(logFile.getUri()); - else - return ""; - - bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); - } catch (IOException ex) { - log.e(ex, "couldn't read file"); - } - } - - StringBuilder result = new StringBuilder(); - - try { - while (bufferedReader.ready()) { - result.append(bufferedReader.readLine()).append("\n"); - } - } catch (IOException ex) { - log.e(ex, "couldn't read file"); - - } finally { - if(bufferedReader != null) { - try { - bufferedReader.close(); - } catch (IOException e) { - log.e(e, "couldn't close buffered reader"); - } - } - } - - return result.toString(); - } - - public static List openSetList(Context context, String filename) { - - if (!filename.endsWith(".pl")) - filename = filename.concat(".pl"); - - String fileText = openFile(context,filename); - - ArrayList filesList = new ArrayList<>(); - - for (String file : fileText.split("\n")) { - if (fileExists(context, file)) - filesList.add(file.replace(".txt", "")); - } - - if (filesList.size() == 1 && filesList.get(0).equals("")) - filesList.remove(0); - return filesList; - } - - public static boolean saveFile(Context context, String fileText, String filename) { - - if (SDK_INT < Build.VERSION_CODES.O) { - - File baseDir = getBaseDirectory(); - - File newFile = new File(baseDir, filename); - - try { - if (!newFile.exists()) - newFile.createNewFile(); - } catch (IOException ex) { - log.e(ex, "couldn't create new file"); - return false; - } - - PrintStream out = null; - - try { - // specifying 8192 gets rid of an annoying warning message - out = new PrintStream(new BufferedOutputStream(new FileOutputStream(newFile, false), 8192)); - - out.print(fileText); - - } catch (FileNotFoundException ex) { - log.e(ex,"unexpected exception"); - return false; - } finally { - if(out != null) { - out.close(); - } - } - - } else { - - Uri uri; - - try { - DocumentFile documentFile = DocumentFile.fromTreeUri(context, PreferenceHelper.getStorageLocation(context)); - - DocumentFile logFile = documentFile.findFile(filename); - - if (logFile == null) - logFile = documentFile.createFile("application/txt",filename); - - uri = logFile.getUri(); - - } catch (Exception e) { - log.e(e, "couldn't create new file"); - return false; - } - - try { - ParcelFileDescriptor parcelFileDescriptor = context.getContentResolver().openFileDescriptor(uri,"w"); - - FileOutputStream fileOutputStream = new FileOutputStream(parcelFileDescriptor.getFileDescriptor()); - - fileOutputStream.write(fileText.getBytes(StandardCharsets.UTF_8)); - - fileOutputStream.close(); - parcelFileDescriptor.close(); - } catch (Exception ex) { - log.e(ex,"couldn't write to file"); - return false; - } - } - - return true; - } - - public static File getBaseDirectory() { + private static UtilLogger log = new UtilLogger(SaveFileHelper.class); - File sdcardDir = Environment.getExternalStorageDirectory(); - - File baseDir = new File(sdcardDir, "chord_reader_2"); - - if(!baseDir.exists()) { - baseDir.mkdir(); - } - - return baseDir; - - } + public static boolean checkIfSdCardExists() { + String state = Environment.getExternalStorageState(); + + return Environment.MEDIA_MOUNTED.equals(state); + } + + public static boolean fileExists(Context context, String filename) { + if (SDK_INT < Build.VERSION_CODES.O) { + File catalogDir = getBaseDirectory(); + File file = new File(catalogDir, filename); + return file.exists(); + } else { + ArrayList fileNames = new ArrayList<>(); + fileNames.add(filename); + + ArrayList fileUris = getFileUri(context, fileNames); + Uri fileUri = fileUris.isEmpty() ? null : fileUris.get(0); + + if(fileUri != null) { + InputStream inputStream = null; + try { + inputStream = context.getContentResolver().openInputStream(fileUri); + + return true; + } catch (Exception e) { + // file does not exist + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + return false; + } + } + + public static ArrayList getExistingFiles(Context context, String[] fileNames) { + if (SDK_INT < Build.VERSION_CODES.O) { + File catalogDir = getBaseDirectory(); + + ArrayList existingFileNames = new ArrayList<>(); + + for (String filename : fileNames) { + File file = new File(catalogDir, filename); + + if (file.exists()) + existingFileNames.add(filename); + } + + return existingFileNames; + } else { + ArrayList existingFileNames = new ArrayList<>(); + + ArrayList fileUris = getFileUri(context, Arrays.asList(fileNames)); + + for (Uri uri : fileUris) { + DocumentFile file = DocumentFile.fromSingleUri(context,uri); + if (file != null) { + existingFileNames.add(file.getName()); + } + } + + return existingFileNames; + } + } + + public static void deleteFile(Context context, String filename) { + if (SDK_INT < Build.VERSION_CODES.O) { + + File catalogDir = getBaseDirectory(); + File file = new File(catalogDir, filename); + + if (file.exists()) + file.delete(); + + } else { + ArrayList fileNames = new ArrayList<>(); + fileNames.add(filename); + + Uri fileUri = getFileUri(context, fileNames).get(0); + + DocumentFile file = null; + + if (fileUri != null) + file = DocumentFile.fromSingleUri( + context, + fileUri); + + if (file != null && file.exists()) { + file.delete(); + } + } + } + + public static Date getLastModifiedDate(String filename) { + + File catlogDir = getBaseDirectory(); + + File file = new File(catlogDir, filename); + + if (file.exists()) { + return new Date(file.lastModified()); + } else { + // shouldn't happen + log.e("file last modified date not found: %s", filename); + return new Date(); + } + } + + public static String[] getSavedFileNames(Context context, String fileExtension) { + + List fileNames = getFilenamesInBaseDirectory(context); + + List result = new ArrayList<>(); + + for (String file : fileNames) { + if (file.endsWith(fileExtension)) + result.add(file.replace(fileExtension, "")); + } + + return result.toArray(new String[0]); + } + + public static List getFilenamesInBaseDirectory(Context context) { + + List result = new ArrayList<>(); + + if (SDK_INT < Build.VERSION_CODES.O) { + File baseDir = getBaseDirectory(); + File[] filesArray = baseDir.listFiles(); + + if (filesArray != null) { + for (File file : filesArray) { + String fileName = file.getName(); + result.add(fileName); + } + } + } else { + DocumentFile documentFile = DocumentFile.fromTreeUri(context, PreferenceHelper.getStorageLocation(context)); + + if (documentFile != null) { + ContentResolver contentResolver = context.getContentResolver(); + Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(documentFile.getUri(), + DocumentsContract.getDocumentId(documentFile.getUri())); + Cursor cursor = null; + + try { + cursor = contentResolver.query(childrenUri, new String[]{ + DocumentsContract.Document.COLUMN_DOCUMENT_ID, + DocumentsContract.Document.COLUMN_DISPLAY_NAME + }, null, null, null); + + while (cursor.moveToNext()) { + final String fileName = cursor.getString(1); + + result.add(fileName); + } + + } catch (Exception e) { + Log.w("SaveFileHelper_ListFile", "Failed query get all file names: " + e); + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + } + + if (result.isEmpty()) + return new ArrayList<>(); + + return result; + } + + public static boolean isInvalidFilename(CharSequence filename) { + + String filenameAsString; + + return TextUtils.isEmpty(filename) + || (filenameAsString = filename.toString()).contains("/") + || filenameAsString.contains(":") + || filenameAsString.contains("\\") + || filenameAsString.contains("*") + || filenameAsString.contains("|") + || filenameAsString.contains("<") + || filenameAsString.contains(">") + || filenameAsString.contains("?"); + + } + + public static String openFile(Context context, String filename) { + + BufferedReader bufferedReader = null; + + if (SDK_INT < Build.VERSION_CODES.O) { + File baseDir = getBaseDirectory(); + File logFile; + + if (!(filename == null)) + logFile = new File(baseDir, filename); + else + return ""; + + try { + bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(logFile))); + } catch (IOException ex) { + log.e(ex, "couldn't read file, file opening error"); + } + } else { + ArrayList fileNames = new ArrayList<>(); + fileNames.add(filename); + + ArrayList fileUris = getFileUri(context, fileNames); + if (fileUris.isEmpty()) + return ""; + + Uri fileUri = fileUris.get(0); + + FileInputStream fileInputStream; + + try { + if (fileUri != null) { + ParcelFileDescriptor inputPFD = context.getContentResolver().openFileDescriptor(fileUri, "r"); + + fileInputStream = new FileInputStream(inputPFD.getFileDescriptor()); + + } else + return ""; + + bufferedReader = new BufferedReader(new InputStreamReader(fileInputStream)); + + } catch (IOException ex) { + log.e(ex, "couldn't read file, file opening error"); + } + } + + StringBuilder result = new StringBuilder(); + + try { + if (bufferedReader != null) { + while (bufferedReader.ready()) { + result.append(bufferedReader.readLine()).append("\n"); + } + } + } catch (IOException ex) { + log.e(ex, "couldn't read file, file reading error"); + + } finally { + if (bufferedReader != null) { + try { + bufferedReader.close(); + } catch (IOException e) { + log.e(e, "couldn't close buffered reader"); + } + } + } + + return result.toString(); + } + + public static List openSetlist(Context context, String setlist) { + + ArrayList filesList = new ArrayList<>(); + + // do in background to avoid jankiness + final CountDownLatch latch = new CountDownLatch(1); + + HandlerThread handlerThread = new HandlerThread("OpenSetlistHandlerThread"); + handlerThread.start(); + + Handler asyncHandler = new Handler(handlerThread.getLooper()) { + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + + latch.countDown(); + + handlerThread.quit(); + } + }; + + Runnable runnable = () -> { + // your async code goes here. + Message message = new Message(); + + String setlistContent = SaveFileHelper.openFile(context, setlist); + + String[] files = setlistContent.split("\n"); + + ArrayList existingFiles = SaveFileHelper.getExistingFiles(context, files); + + for (String file : existingFiles) { + filesList.add(file.replace(".txt", "")); + } + + if (filesList.size() == 1 && filesList.get(0).equals("")) + filesList.remove(0); + + message.obj = "ready"; + + asyncHandler.sendMessage(message); + }; + + asyncHandler.post(runnable); + + // wait for async saving result + try { + latch.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + return filesList.isEmpty() ? new ArrayList<>() : filesList; + } + + public static boolean saveFile(Context context, String fileText, String filename) { + + // do in background to avoid jankiness + final boolean[] successfullySavedLog = new boolean[1]; + final CountDownLatch latch = new CountDownLatch(1); + + HandlerThread handlerThread = new HandlerThread("SaveFileHandlerThread"); + handlerThread.start(); + + Handler asyncHandler = new Handler(handlerThread.getLooper()) { + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + successfullySavedLog[0] = (boolean) msg.obj; + + latch.countDown(); + + handlerThread.quit(); + } + }; + + Runnable runnable = () -> { + // your async code goes here. + Message message = new Message(); + message.obj = doSaving(context, fileText, filename); + + asyncHandler.sendMessage(message); + }; + + asyncHandler.post(runnable); + + // wait for async saving result + try { + latch.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + return successfullySavedLog[0]; + } + + private static boolean doSaving(Context context, String fileText, String filename) { + + if (SDK_INT < Build.VERSION_CODES.O) { + + File baseDir = getBaseDirectory(); + + File newFile = new File(baseDir, filename); + + try { + if (!newFile.exists()) + newFile.createNewFile(); + } catch (IOException ex) { + log.e(ex, "couldn't create new file"); + return false; + } + + PrintStream out = null; + + try { + // specifying 8192 gets rid of an annoying warning message + out = new PrintStream(new BufferedOutputStream(new FileOutputStream(newFile, false), 8192)); + + out.print(fileText); + + } catch (FileNotFoundException ex) { + log.e(ex, "unexpected exception"); + return false; + } finally { + if (out != null) { + out.close(); + } + } + + } else { + + Uri fileUri; + + try { + DocumentFile documentFile = DocumentFile.fromTreeUri( + context, + PreferenceHelper.getStorageLocation(context) + ); + + ArrayList fileNames = new ArrayList<>(); + fileNames.add(filename); + + ArrayList fileUris = getFileUri(context, fileNames); + fileUri = fileUris.isEmpty() ? null : fileUris.get(0); + + if (fileUri == null) { + assert documentFile != null; + fileUri = DocumentsContract.createDocument( + context.getContentResolver(), + documentFile.getUri(), + "application/txt", + filename + ); + } + + } catch (Exception e) { + log.e(e, "couldn't create new file"); + return false; + } + + try { + ParcelFileDescriptor parcelFileDescriptor = context.getContentResolver() + .openFileDescriptor(fileUri, "wt"); + + FileOutputStream fileOutputStream = new FileOutputStream(parcelFileDescriptor.getFileDescriptor()); + + fileOutputStream.write(fileText.getBytes(StandardCharsets.UTF_8)); + + fileOutputStream.close(); + + parcelFileDescriptor.close(); + } catch (Exception ex) { + log.e(ex, "couldn't write to file"); + return false; + } + } + + return true; + } + + private static File getBaseDirectory() { + + File sdcardDir = Environment.getExternalStorageDirectory(); + + File baseDir = new File(sdcardDir, "chord_reader_2"); + + if (!baseDir.exists()) { + baseDir.mkdir(); + } + + return baseDir; + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + private static ArrayList getFileUri(Context context, List requestedFileNames) { + + DocumentFile documentFile = DocumentFile.fromTreeUri(context, PreferenceHelper.getStorageLocation(context)); + + if (documentFile != null) { + ContentResolver contentResolver = context.getContentResolver(); + Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree( + PreferenceHelper.getStorageLocation(context), + DocumentsContract.getDocumentId(documentFile.getUri()) + ); + + Cursor cursor = null; + + try { + cursor = contentResolver.query(childrenUri, new String[]{ + DocumentsContract.Document.COLUMN_DOCUMENT_ID, + DocumentsContract.Document.COLUMN_DISPLAY_NAME + }, null, null, null); + + ArrayList existingFilesUris = new ArrayList<>(); + + while (cursor.moveToNext()) { + final String fileName = cursor.getString(1); + + if (requestedFileNames.contains(fileName)) { + final String documentId = cursor.getString(0); + + final Uri documentUri = + DocumentsContract.buildDocumentUriUsingTree( + PreferenceHelper.getStorageLocation(context), documentId); + + existingFilesUris.add(documentUri); + } + } + + return existingFilesUris; + + } catch (Exception e) { + Log.w("SaveFileHelper_getUri", "Failed query: " + e); + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + return new ArrayList<>(); + } } diff --git a/app/src/main/java/org/hollowbamboo/chordreader2/model/DataViewModel.java b/app/src/main/java/org/hollowbamboo/chordreader2/model/DataViewModel.java index edc557c..eeccad5 100644 --- a/app/src/main/java/org/hollowbamboo/chordreader2/model/DataViewModel.java +++ b/app/src/main/java/org/hollowbamboo/chordreader2/model/DataViewModel.java @@ -12,14 +12,15 @@ public class DataViewModel extends ViewModel { public String mode; public ArrayList setListSongs; - private MutableLiveData setListMLD; + private MutableLiveData setListMLD = new MutableLiveData<>(); public MutableLiveData> setListSongsMLD; public void setSetListMLD(String setlist) { - setListMLD = new MutableLiveData<>(); + if (setListMLD == null) + setListMLD = new MutableLiveData<>(); + setListMLD.setValue(setlist); - setListSongsMLD = new MutableLiveData<>(); setListSongsMLD.setValue(setListSongs); } @@ -31,6 +32,10 @@ public MutableLiveData getSetListMLD() { public void setSetListSongs(ArrayList setListSongs) { this.setListSongs = setListSongs; + + if (setListSongsMLD == null) + setListSongsMLD = new MutableLiveData<>(); + setListSongsMLD.setValue(setListSongs); } diff --git a/app/src/main/java/org/hollowbamboo/chordreader2/model/SongViewFragmentViewModel.java b/app/src/main/java/org/hollowbamboo/chordreader2/model/SongViewFragmentViewModel.java index 1445f8e..05dfed9 100644 --- a/app/src/main/java/org/hollowbamboo/chordreader2/model/SongViewFragmentViewModel.java +++ b/app/src/main/java/org/hollowbamboo/chordreader2/model/SongViewFragmentViewModel.java @@ -56,10 +56,9 @@ public class SongViewFragmentViewModel extends ViewModel { private final MutableLiveData bpmMLD = new MutableLiveData<>(); private final MutableLiveData scrollVelocityCorrFactorMLD = new MutableLiveData<>(); + private final MutableLiveData saveResultMLD = new MutableLiveData<>(); - public SongViewFragmentViewModel() { - - } + public SongViewFragmentViewModel() { } public MutableLiveData getTextSize() { return textSizeMLD; @@ -85,12 +84,12 @@ public MutableLiveData getScrollVelocityCorrFactorMLD() { return scrollVelocityCorrFactorMLD; } - public void setSongTitle(String songTitle) { - fragmentTitle.setValue(songTitle); + public MutableLiveData getSaveResultMLD() { + return saveResultMLD; } - public void setFilename(String filename) { - this.filename = filename; + public void setSongTitle(String songTitle) { + fragmentTitle.setValue(songTitle); } public void setChordText(String chordText) { @@ -251,7 +250,6 @@ public void updateChordsInTextForTransposition(int transposeDiff, int capoDiff) } - private static float extractAutoScrollParam(String text, String autoScrollParam) { Matcher matcher; diff --git a/app/src/main/java/org/hollowbamboo/chordreader2/ui/DraggableListFragment.java b/app/src/main/java/org/hollowbamboo/chordreader2/ui/DraggableListFragment.java index e440795..f12ab8d 100644 --- a/app/src/main/java/org/hollowbamboo/chordreader2/ui/DraggableListFragment.java +++ b/app/src/main/java/org/hollowbamboo/chordreader2/ui/DraggableListFragment.java @@ -18,13 +18,13 @@ */ import android.os.Bundle; +import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -63,6 +63,7 @@ public class DraggableListFragment extends Fragment implements OnItemClickListen @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + Log.d("SetList List", "Start DraggableListView"); dataViewModel = new ViewModelProvider(requireActivity()).get(DataViewModel.class); @@ -84,6 +85,7 @@ public void onChanged(@Nullable ArrayList setListSongs) { setUpMenu(); + Log.d("SetList List", "DraggableListView started"); return root; } @@ -171,8 +173,7 @@ private void startListView() { private void saveSetListToFile() { String fileName = dataViewModel.getSetListMLD().getValue(); - if(!fileName.endsWith(".pl")) - fileName = fileName + ".pl"; + if (fileName != null && !fileName.endsWith(".pl")) fileName = fileName + ".pl"; StringBuilder resultText = new StringBuilder(); for (String line : Objects.requireNonNull(dataViewModel.getSetListSongsMLD().getValue())) { diff --git a/app/src/main/java/org/hollowbamboo/chordreader2/ui/ListFragment.java b/app/src/main/java/org/hollowbamboo/chordreader2/ui/ListFragment.java index 54e8d1b..e853f81 100644 --- a/app/src/main/java/org/hollowbamboo/chordreader2/ui/ListFragment.java +++ b/app/src/main/java/org/hollowbamboo/chordreader2/ui/ListFragment.java @@ -17,6 +17,7 @@ */ +import android.app.ActionBar; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; @@ -24,6 +25,9 @@ import android.graphics.Color; import android.graphics.drawable.GradientDrawable; import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; import android.text.Editable; import android.text.InputType; import android.text.TextUtils; @@ -48,6 +52,7 @@ import androidx.activity.OnBackPressedCallback; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.widget.LinearLayoutCompat; import androidx.appcompat.widget.Toolbar; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.core.view.MenuHost; @@ -66,10 +71,12 @@ import org.hollowbamboo.chordreader2.model.DataViewModel; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Objects; +import java.util.concurrent.CountDownLatch; public class ListFragment extends Fragment implements TextWatcher { @@ -362,20 +369,23 @@ protected void setUpInstance() { } // Open files depending on listFragment mode - List filenames = new ArrayList<>(SaveFileHelper.getSavedSongNames(requireContext())); + + List filenames = null; switch (dataViewModel.mode) { case MODE_SONGS: setTitle("Songs"); + filenames = getFileNames(".txt"); textView.setText(R.string.no_local_songs); break; case MODE_SETLIST: setTitle("Setlists"); - filenames = new ArrayList<>(SaveFileHelper.getSavedSetListNames(requireContext())); + filenames = getFileNames(".pl"); textView.setText(R.string.no_setlists); break; case MODE_SETLIST_SONG_SELECTION: setTitle(getString(R.string.file_selection_for_setlist)); + filenames = getFileNames(".txt"); okButton.setVisibility(View.VISIBLE); okButton.setOnClickListener(new View.OnClickListener(){ @@ -390,7 +400,8 @@ public void onClick(View view) { break; } - Collections.sort(filenames, (Comparator) (first, second) -> first.toString().toLowerCase().compareTo(second.toString().toLowerCase())); + if (filenames != null) + Collections.sort(filenames, (Comparator) (first, second) -> first.toString().toLowerCase().compareTo(second.toString().toLowerCase())); fileListAdapter = new SelectableFilterAdapter(requireContext(), filenames) { @Override @@ -529,9 +540,58 @@ else if (dataViewModel.mode.equals(MODE_SETLIST)) setTitle("Setlists"); } + private List getFileNames(String fileExtension) { + + List result = new ArrayList<>(); + + // do in background to avoid jankiness + final CountDownLatch latch = new CountDownLatch(1); + + HandlerThread handlerThread = new HandlerThread("GetFileListHandlerThread"); + handlerThread.start(); + + Handler asyncHandler = new Handler(handlerThread.getLooper()) { + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + String[] fileList = (String[]) msg.obj; + + result.addAll(Arrays.asList(fileList)); + + latch.countDown(); + + handlerThread.quit(); + } + }; + + Runnable runnable = () -> { + // your async code goes here. + Message message = new Message(); + message.obj = SaveFileHelper.getSavedFileNames(requireContext(), fileExtension); + + asyncHandler.sendMessage(message); + }; + + asyncHandler.post(runnable); + + // wait for async saving result + try { + latch.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + if (result.isEmpty()) + return new ArrayList<>(); + + return result; + } + + + private boolean checkSdCard() { - boolean result = SaveFileHelper.checkIfSdCardExists(requireContext()); + boolean result = SaveFileHelper.checkIfSdCardExists(); if(!result) { Toast.makeText(getActivity(), getResources().getString(R.string.sd_card_not_found), Toast.LENGTH_SHORT).show(); @@ -583,7 +643,13 @@ else if(Objects.equals(dataViewModel.mode, MODE_SETLIST)) } private void startSetListList(String setlist) { - dataViewModel.setListSongs = (ArrayList) SaveFileHelper.openSetList(requireContext(),setlist); + + if (!setlist.endsWith(".pl")) + setlist = setlist.concat(".pl"); + + ArrayList filesList = (ArrayList) SaveFileHelper.openSetlist(requireContext(), setlist); + + dataViewModel.setSetListSongs(filesList); dataViewModel.setSetListMLD(setlist); Navigation.findNavController(getParentFragment().getView()).navigate(R.id.nav_drag_list_view); diff --git a/app/src/main/java/org/hollowbamboo/chordreader2/ui/SettingsFragment.java b/app/src/main/java/org/hollowbamboo/chordreader2/ui/SettingsFragment.java index bc7dd21..ee1b2bf 100644 --- a/app/src/main/java/org/hollowbamboo/chordreader2/ui/SettingsFragment.java +++ b/app/src/main/java/org/hollowbamboo/chordreader2/ui/SettingsFragment.java @@ -187,6 +187,12 @@ public void openDirectoryPicker() { } private void setStorageLocationPreference(Uri uri) { + + requireContext().grantUriPermission(requireContext().getPackageName(), uri, + Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + requireContext().getContentResolver().takePersistableUriPermission(uri, + Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + PreferenceHelper.setStorageLocation(requireContext(), uri); PreferenceHelper.clearCache(); storageLocationPreference.setSummary(uri.toString()); diff --git a/app/src/main/java/org/hollowbamboo/chordreader2/ui/SongViewFragment.java b/app/src/main/java/org/hollowbamboo/chordreader2/ui/SongViewFragment.java index 8834853..fa0e076 100644 --- a/app/src/main/java/org/hollowbamboo/chordreader2/ui/SongViewFragment.java +++ b/app/src/main/java/org/hollowbamboo/chordreader2/ui/SongViewFragment.java @@ -30,7 +30,6 @@ import android.os.CountDownTimer; import android.os.Handler; import android.os.HandlerThread; -import android.os.Looper; import android.os.Message; import android.text.InputType; import android.text.Layout; @@ -94,11 +93,13 @@ import org.hollowbamboo.chordreader2.views.ChordVisualisationView; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Timer; import java.util.TimerTask; +import java.util.concurrent.CountDownLatch; public class SongViewFragment extends Fragment implements View.OnClickListener { @@ -175,6 +176,7 @@ public void onResume() { @Override public void onDestroyView() { super.onDestroyView(); + binding = null; } @@ -206,7 +208,7 @@ public boolean onMenuItemSelected(@NonNull MenuItem menuItem) { if(songViewFragmentViewModel.isEditedTextToSave) { howToProceedAfterSaving = POST_SAVE_PROCEEDING_EXIT; if (songViewFragmentViewModel.autoSave) { - saveFile(songViewFragmentViewModel.getFragmentTitle().getValue(), songViewFragmentViewModel.chordText); + saveFile(songViewFragmentViewModel.filename, songViewFragmentViewModel.chordText); } else { showSavePromptDialog(); } @@ -395,6 +397,22 @@ public void onChanged(Chord chord) { showChordPopup(chord); } }); + + songViewFragmentViewModel.getSaveResultMLD().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(Boolean successfullySavedLog) { + + if(successfullySavedLog) { + Toast.makeText(getActivity(), getResources().getString(R.string.file_saved), Toast.LENGTH_SHORT).show(); + + songViewFragmentViewModel.isEditedTextToSave = false; + songViewFragmentViewModel.filename = filename; + + } else { + Toast.makeText(getActivity(), getResources().getString(R.string.unable_to_save_file), Toast.LENGTH_SHORT).show(); + } + } + }); } private void setInstanceData() { @@ -408,7 +426,7 @@ private void setInstanceData() { Transposition transposition = null; if(filename == null) { - songTitle = getResources().getString(R.string.new_file); + songTitle = filename = getResources().getString(R.string.new_file); songViewFragmentViewModel.isEditedTextToSave = true; } else if(filename.isEmpty() && !(songTitle != null && songTitle.isEmpty())) { // from web search filename = songTitle; @@ -422,7 +440,7 @@ private void setInstanceData() { songTitle = Character.toUpperCase(songTitle.charAt(0)) + songTitle.substring(1); songViewFragmentViewModel.setSongTitle(songTitle); - songViewFragmentViewModel.setFilename(filename); + songViewFragmentViewModel.filename = filename; songViewFragmentViewModel.setNoteNaming(getNoteNaming()); songViewFragmentViewModel.setTransposition(transposition); songViewFragmentViewModel.setLinkColor(PreferenceHelper.getColorScheme(getActivity().getBaseContext()).getLinkColor(getActivity().getBaseContext())); @@ -430,7 +448,6 @@ private void setInstanceData() { } - getParentFragmentManager().setFragmentResultListener("EditChordTextDialog", this, songViewFragmentViewModel.getFragmentResultListener()); } @@ -452,10 +469,9 @@ public void handleOnBackPressed() { if(songViewFragmentViewModel.isEditedTextToSave) { if (songViewFragmentViewModel.autoSave) - saveFile(songViewFragmentViewModel.getFragmentTitle().getValue(), songViewFragmentViewModel.chordText); + saveFile(songViewFragmentViewModel.filename, songViewFragmentViewModel.chordText); else { showSavePromptDialog(); - return; } } else proceedAfterSaving(); @@ -464,10 +480,11 @@ public void handleOnBackPressed() { requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), callback); } - private void saveFile(final String filename, final String filetext) { + private void saveFile(final String filename, final String fileText) { - // do in background to avoid jankiness - HandlerThread handlerThread = new HandlerThread("SaveFileHandlerThread"); + // Save text size + + HandlerThread handlerThread = new HandlerThread("SaveTextSizeHandlerThread"); handlerThread.start(); Handler asyncHandler = new Handler(handlerThread.getLooper()) { @@ -475,25 +492,6 @@ private void saveFile(final String filename, final String filetext) { public void handleMessage(Message msg) { super.handleMessage(msg); Boolean successfullySavedLog = (Boolean) msg.obj; - if(successfullySavedLog) { - ThreadUtils.showToastInUiThread(requireActivity(),R.string.file_saved); - - songViewFragmentViewModel.isEditedTextToSave = false; - songViewFragmentViewModel.filename = filename; - - } else { - ThreadUtils.showToastInUiThread(requireActivity(),R.string.unable_to_save_file); - } - - // must be called on the main thread - Handler mainThread = new Handler(Looper.getMainLooper()); - mainThread.post(new Runnable() { - @Override - public void run() { - proceedAfterSaving(); - } - }); - handlerThread.quit(); } @@ -507,27 +505,34 @@ public void run() { dbHelper.close(); Message message = new Message(); - message.obj = SaveFileHelper.saveFile(requireContext(), filetext, filename.concat(".txt")); - + message.obj = true; asyncHandler.sendMessage(message); }; asyncHandler.post(runnable); + + + // Save to file + boolean successfullySavedLog = SaveFileHelper.saveFile(requireContext(), fileText, filename.concat(".txt")); + + songViewFragmentViewModel.getSaveResultMLD().setValue(successfullySavedLog); + + proceedAfterSaving(); } private void proceedAfterSaving() { - if (howToProceedAfterSaving == POST_SAVE_PROCEEDING_EXIT) + if (howToProceedAfterSaving == POST_SAVE_PROCEEDING_EXIT) { if (getParentFragment() != null) { Navigation.findNavController(getParentFragment().requireView()).popBackStack(); } - else + } else setTitle(songViewFragmentViewModel.filename); } private boolean checkSdCard() { - boolean result = SaveFileHelper.checkIfSdCardExists(requireContext()); + boolean result = SaveFileHelper.checkIfSdCardExists(); if(!result) { Toast.makeText(getActivity(), getResources().getString(R.string.sd_card_not_found), Toast.LENGTH_SHORT).show(); @@ -801,6 +806,27 @@ private void startAutoscroll() { } } + private void appendSongToSetlist(String setlist) { + + + if (!setlist.endsWith(".pl")) + setlist = setlist.concat(".pl"); + + ArrayList filesList = (ArrayList) SaveFileHelper.openSetlist(requireContext(), setlist); + + filesList.add(songViewFragmentViewModel.filename); + + StringBuilder resultText = new StringBuilder(); + for (String line : filesList) { + if (!line.endsWith(".txt")) + resultText.append(line).append(".txt\n"); + else + resultText.append(line).append("\n"); + } + + SaveFileHelper.saveFile(requireContext(), resultText.toString(),setlist); + } + // Dialogs private void showSavePromptDialog() { @@ -857,9 +883,9 @@ public void run() { } }); - editText.setText(songViewFragmentViewModel.getFragmentTitle().getValue()); + editText.setText(songViewFragmentViewModel.filename); - editText.setSelection(0, songViewFragmentViewModel.getFragmentTitle().getValue().length()); + editText.setSelection(0, songViewFragmentViewModel.filename.length()); DialogInterface.OnClickListener onClickListener = (dialog, which) -> { @@ -1055,7 +1081,7 @@ private void createSetListDialog() { return; } - List setListNames = new ArrayList<>(SaveFileHelper.getSavedSetListNames(requireContext())); + List setListNames = Arrays.asList(SaveFileHelper.getSavedFileNames(requireContext(),".pl")); if (setListNames.isEmpty()) { Toast.makeText(requireContext(), R.string.no_setlists, Toast.LENGTH_SHORT).show(); @@ -1084,15 +1110,7 @@ private void createSetListDialog() { public void onClick(DialogInterface dialog, int which) { String setList = listItems[checkedItem[0]]; - List filenames = SaveFileHelper.openSetList(requireContext(),setList); - filenames.add(songViewFragmentViewModel.filename); - - StringBuilder resultText = new StringBuilder(); - for (String line : filenames) { - resultText.append(line).append(".txt\n"); - } - - SaveFileHelper.saveFile(requireContext(), resultText.toString(),setList.concat(".pl")); + appendSongToSetlist(setList); dialog.dismiss(); } @@ -1326,19 +1344,5 @@ public static class EditTextDialogViewModel extends ViewModel { protected String chordText; } } - public static class ThreadUtils { - - public static void showToastInUiThread(final Context ctx, - final int stringRes) { - - Handler mainThread = new Handler(Looper.getMainLooper()); - mainThread.post(new Runnable() { - @Override - public void run() { - Toast.makeText(ctx, ctx.getString(stringRes), Toast.LENGTH_SHORT).show(); - } - }); - } - } } \ No newline at end of file diff --git a/app/src/main/res/raw/about_body_de.htm b/app/src/main/res/raw/about_body_de.htm index 7e8f4e9..b8b517d 100644 --- a/app/src/main/res/raw/about_body_de.htm +++ b/app/src/main/res/raw/about_body_de.htm @@ -63,6 +63,11 @@ Changelog + 2.1.2 +
    +
  • Wechsel zu Android Storage Access Framework ab Android 8.1
  • +
+ 2.1.1
  • Aktiven Song per Menü einer Setlist hinzugefügen
  • diff --git a/app/src/main/res/raw/about_body_en.htm b/app/src/main/res/raw/about_body_en.htm index 2c5d43d..618b2e7 100644 --- a/app/src/main/res/raw/about_body_en.htm +++ b/app/src/main/res/raw/about_body_en.htm @@ -62,6 +62,11 @@ Changelog + 2.1.2 +
      +
    • Switch to Android Storage Access Framework as of Android 8.1
    • +
    + 2.1.1
    • Add active song to a setlist via menu
    • diff --git a/app/src/main/res/raw/about_body_fr.htm b/app/src/main/res/raw/about_body_fr.htm index 014b70f..fa678c2 100644 --- a/app/src/main/res/raw/about_body_fr.htm +++ b/app/src/main/res/raw/about_body_fr.htm @@ -62,6 +62,11 @@ Changelog + 2.1.2 +
        +
      • Passage à Android Storage Access Framework à partir d'Android 8.1
      • +
      + 2.1.1
      • Ajouter une chanson active à une setlist via le menu
      • diff --git a/app/src/main/res/xml/settings.xml b/app/src/main/res/xml/settings.xml index a17dc59..9770593 100644 --- a/app/src/main/res/xml/settings.xml +++ b/app/src/main/res/xml/settings.xml @@ -15,7 +15,8 @@ + android:persistent="true" + android:defaultValue="@string/pref_storage_location_default"/>