From a66d225b9feff54bdd5bb597102839fe2cdbc75f Mon Sep 17 00:00:00 2001 From: Michael Kaiser Date: Mon, 15 Apr 2013 17:42:38 +0200 Subject: Make removal of queued items undoable --- .../danoeh/antennapod/util/UndoBarController.java | 135 +++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 src/de/danoeh/antennapod/util/UndoBarController.java (limited to 'src/de/danoeh/antennapod/util') diff --git a/src/de/danoeh/antennapod/util/UndoBarController.java b/src/de/danoeh/antennapod/util/UndoBarController.java new file mode 100644 index 000000000..e726717a1 --- /dev/null +++ b/src/de/danoeh/antennapod/util/UndoBarController.java @@ -0,0 +1,135 @@ +/* + * Copyright 2012 Roman Nurik + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.danoeh.antennapod.util; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.os.Bundle; +import android.os.Handler; +import android.os.Parcelable; +import android.text.TextUtils; +import android.view.View; +import android.view.ViewPropertyAnimator; +import android.widget.TextView; + +import de.danoeh.antennapod.R; + +public class UndoBarController { + private View mBarView; + private TextView mMessageView; + private ViewPropertyAnimator mBarAnimator; + private Handler mHideHandler = new Handler(); + + private UndoListener mUndoListener; + + // State objects + private Parcelable mUndoToken; + private CharSequence mUndoMessage; + + public interface UndoListener { + void onUndo(Parcelable token); + } + + public UndoBarController(View undoBarView, UndoListener undoListener) { + mBarView = undoBarView; + mBarAnimator = mBarView.animate(); + mUndoListener = undoListener; + + mMessageView = (TextView) mBarView.findViewById(R.id.undobar_message); + mBarView.findViewById(R.id.undobar_button) + .setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + hideUndoBar(false); + mUndoListener.onUndo(mUndoToken); + } + }); + + hideUndoBar(true); + } + + public void showUndoBar(boolean immediate, CharSequence message, Parcelable undoToken) { + mUndoToken = undoToken; + mUndoMessage = message; + mMessageView.setText(mUndoMessage); + + mHideHandler.removeCallbacks(mHideRunnable); + mHideHandler.postDelayed(mHideRunnable, + mBarView.getResources().getInteger(R.integer.undobar_hide_delay)); + + mBarView.setVisibility(View.VISIBLE); + if (immediate) { + mBarView.setAlpha(1); + } else { + mBarAnimator.cancel(); + mBarAnimator + .alpha(1) + .setDuration( + mBarView.getResources() + .getInteger(android.R.integer.config_shortAnimTime)) + .setListener(null); + } + } + + public void hideUndoBar(boolean immediate) { + mHideHandler.removeCallbacks(mHideRunnable); + if (immediate) { + mBarView.setVisibility(View.GONE); + mBarView.setAlpha(0); + mUndoMessage = null; + mUndoToken = null; + + } else { + mBarAnimator.cancel(); + mBarAnimator + .alpha(0) + .setDuration(mBarView.getResources() + .getInteger(android.R.integer.config_shortAnimTime)) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mBarView.setVisibility(View.GONE); + mUndoMessage = null; + mUndoToken = null; + } + }); + } + } + + public void onSaveInstanceState(Bundle outState) { + outState.putCharSequence("undo_message", mUndoMessage); + outState.putParcelable("undo_token", mUndoToken); + } + + public void onRestoreInstanceState(Bundle savedInstanceState) { + if (savedInstanceState != null) { + mUndoMessage = savedInstanceState.getCharSequence("undo_message"); + mUndoToken = savedInstanceState.getParcelable("undo_token"); + + if (mUndoToken != null || !TextUtils.isEmpty(mUndoMessage)) { + showUndoBar(true, mUndoMessage, mUndoToken); + } + } + } + + private Runnable mHideRunnable = new Runnable() { + @Override + public void run() { + hideUndoBar(false); + } + }; +} -- cgit v1.2.3 From b5f47898655985cf25fc92cf384b7901a1cc05e6 Mon Sep 17 00:00:00 2001 From: Hanno Zulla Date: Tue, 16 Apr 2013 14:24:20 +0200 Subject: changed dates in lists to relative timespans (e.g. "3 days ago") --- src/de/danoeh/antennapod/util/Converter.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'src/de/danoeh/antennapod/util') diff --git a/src/de/danoeh/antennapod/util/Converter.java b/src/de/danoeh/antennapod/util/Converter.java index f02e8ea69..7ca291919 100644 --- a/src/de/danoeh/antennapod/util/Converter.java +++ b/src/de/danoeh/antennapod/util/Converter.java @@ -1,6 +1,9 @@ package de.danoeh.antennapod.util; +import android.content.Context; +import android.text.format.DateUtils; import android.util.Log; +import de.danoeh.antennapod.R; /** Provides methods for converting various units. */ public final class Converter { @@ -78,4 +81,16 @@ public final class Converter { return String.format("%02d:%02d", h, m); } + + /** Converts milliseconds to a relative time span, + * will return "a moment ago" if it's less than a minute ago */ + public static String getRelativeTimeSpanString(Context context, long millis) { + long now = System.currentTimeMillis(); + if (now - millis <= 60 * 1000) { + return context.getString(R.string.a_moment_ago); + } else { + return DateUtils.getRelativeTimeSpanString( + millis, now, 0, 0).toString(); + } + } } -- cgit v1.2.3 From 63cd69d06be983ee2575d941f33efa0fc0190f35 Mon Sep 17 00:00:00 2001 From: Hanno Zulla Date: Tue, 16 Apr 2013 15:49:32 +0200 Subject: removed a_moment_ago (looks silly and is hard to translate properly) --- src/de/danoeh/antennapod/util/Converter.java | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) (limited to 'src/de/danoeh/antennapod/util') diff --git a/src/de/danoeh/antennapod/util/Converter.java b/src/de/danoeh/antennapod/util/Converter.java index 7ca291919..995eed119 100644 --- a/src/de/danoeh/antennapod/util/Converter.java +++ b/src/de/danoeh/antennapod/util/Converter.java @@ -82,15 +82,10 @@ public final class Converter { return String.format("%02d:%02d", h, m); } - /** Converts milliseconds to a relative time span, - * will return "a moment ago" if it's less than a minute ago */ + /** Converts milliseconds to a relative time span, */ public static String getRelativeTimeSpanString(Context context, long millis) { long now = System.currentTimeMillis(); - if (now - millis <= 60 * 1000) { - return context.getString(R.string.a_moment_ago); - } else { - return DateUtils.getRelativeTimeSpanString( - millis, now, 0, 0).toString(); - } + return DateUtils.getRelativeTimeSpanString( + millis, now, 0, 0).toString(); } } -- cgit v1.2.3 From 52dfc8a9b8a77db98082edf5820b5bd8896332dc Mon Sep 17 00:00:00 2001 From: Hanno Zulla Date: Tue, 16 Apr 2013 15:50:28 +0200 Subject: comment typo fixed --- src/de/danoeh/antennapod/util/Converter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/de/danoeh/antennapod/util') diff --git a/src/de/danoeh/antennapod/util/Converter.java b/src/de/danoeh/antennapod/util/Converter.java index 995eed119..3f0419a4f 100644 --- a/src/de/danoeh/antennapod/util/Converter.java +++ b/src/de/danoeh/antennapod/util/Converter.java @@ -82,7 +82,7 @@ public final class Converter { return String.format("%02d:%02d", h, m); } - /** Converts milliseconds to a relative time span, */ + /** Converts milliseconds to a relative time span */ public static String getRelativeTimeSpanString(Context context, long millis) { long now = System.currentTimeMillis(); return DateUtils.getRelativeTimeSpanString( -- cgit v1.2.3 From ec1ff077022eec893c844dce0a7cba7a6cea1420 Mon Sep 17 00:00:00 2001 From: Hanno Zulla Date: Wed, 17 Apr 2013 10:40:39 +0200 Subject: without a_moment_ago there is no need for a Converter method anymore --- src/de/danoeh/antennapod/util/Converter.java | 9 --------- 1 file changed, 9 deletions(-) (limited to 'src/de/danoeh/antennapod/util') diff --git a/src/de/danoeh/antennapod/util/Converter.java b/src/de/danoeh/antennapod/util/Converter.java index 3f0419a4f..6ef47af31 100644 --- a/src/de/danoeh/antennapod/util/Converter.java +++ b/src/de/danoeh/antennapod/util/Converter.java @@ -1,9 +1,6 @@ package de.danoeh.antennapod.util; -import android.content.Context; -import android.text.format.DateUtils; import android.util.Log; -import de.danoeh.antennapod.R; /** Provides methods for converting various units. */ public final class Converter { @@ -82,10 +79,4 @@ public final class Converter { return String.format("%02d:%02d", h, m); } - /** Converts milliseconds to a relative time span */ - public static String getRelativeTimeSpanString(Context context, long millis) { - long now = System.currentTimeMillis(); - return DateUtils.getRelativeTimeSpanString( - millis, now, 0, 0).toString(); - } } -- cgit v1.2.3 From 960dd3425ff43bf2bfe8f2b7fd5ea9100cbe21b6 Mon Sep 17 00:00:00 2001 From: daniel oeh Date: Sat, 20 Apr 2013 11:03:31 +0200 Subject: Don't autodownload episodes when item is removed from queue via drag&drop --- src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/de/danoeh/antennapod/util') diff --git a/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java b/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java index 64bcb1cf2..472124bf7 100644 --- a/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java +++ b/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java @@ -140,7 +140,7 @@ public class FeedItemMenuHandler { manager.addQueueItem(context, selectedItem); break; case R.id.remove_from_queue_item: - manager.removeQueueItem(context, selectedItem); + manager.removeQueueItem(context, selectedItem, true); break; case R.id.stream_item: manager.playMedia(context, selectedItem.getMedia(), true, true, -- cgit v1.2.3 From 02586d30262c7be62a6668500ceeaeb84e55f39b Mon Sep 17 00:00:00 2001 From: daniel oeh Date: Sun, 9 Jun 2013 10:51:59 +0200 Subject: Added support for Links in MP3 chapters (using WXXX frame) --- .../antennapod/util/id3reader/ChapterReader.java | 32 ++++++++++++--- .../antennapod/util/id3reader/ID3Reader.java | 46 ++++++++++++++++------ 2 files changed, 61 insertions(+), 17 deletions(-) (limited to 'src/de/danoeh/antennapod/util') diff --git a/src/de/danoeh/antennapod/util/id3reader/ChapterReader.java b/src/de/danoeh/antennapod/util/id3reader/ChapterReader.java index e1cafe85d..a04763bcd 100644 --- a/src/de/danoeh/antennapod/util/id3reader/ChapterReader.java +++ b/src/de/danoeh/antennapod/util/id3reader/ChapterReader.java @@ -5,15 +5,20 @@ import java.io.InputStream; import java.util.ArrayList; import java.util.List; +import android.util.Log; + +import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.feed.Chapter; import de.danoeh.antennapod.feed.ID3Chapter; import de.danoeh.antennapod.util.id3reader.model.FrameHeader; import de.danoeh.antennapod.util.id3reader.model.TagHeader; public class ChapterReader extends ID3Reader { + private static final String TAG = "ID3ChapterReader"; private static final String FRAME_ID_CHAPTER = "CHAP"; private static final String FRAME_ID_TITLE = "TIT2"; + private static final String FRAME_ID_LINK = "WXXX"; private List chapters; private ID3Chapter currentChapter; @@ -33,27 +38,42 @@ public class ChapterReader extends ID3Reader { if (currentChapter != null) { if (!hasId3Chapter(currentChapter)) { chapters.add(currentChapter); - System.out.println("Found chapter: " + currentChapter); + if (AppConfig.DEBUG) Log.d(TAG, "Found chapter: " + currentChapter); currentChapter = null; } } - String elementId = readISOString(input, Integer.MAX_VALUE); + StringBuffer elementId = new StringBuffer(); + readISOString(elementId, input, Integer.MAX_VALUE); char[] startTimeSource = readBytes(input, 4); long startTime = ((int) startTimeSource[0] << 24) | ((int) startTimeSource[1] << 16) | ((int) startTimeSource[2] << 8) | startTimeSource[3]; - currentChapter = new ID3Chapter(elementId, startTime); + currentChapter = new ID3Chapter(elementId.toString(), startTime); skipBytes(input, 12); return ID3Reader.ACTION_DONT_SKIP; } else if (header.getId().equals(FRAME_ID_TITLE)) { if (currentChapter != null && currentChapter.getTitle() == null) { + StringBuffer title = new StringBuffer(); + readString(title, input, header.getSize()); currentChapter - .setTitle(readString(input, header.getSize())); - System.out.println("Found title: " + currentChapter.getTitle()); + .setTitle(title.toString()); + if (AppConfig.DEBUG) Log.d(TAG, "Found title: " + currentChapter.getTitle()); return ID3Reader.ACTION_DONT_SKIP; } - } + } else if (header.getId().equals(FRAME_ID_LINK)) { + if (currentChapter != null) { + // skip description + int descriptionLength = readString(null, input, header.getSize()); + StringBuffer link = new StringBuffer(); + readISOString(link, input, header.getSize() - descriptionLength); + currentChapter.setLink(link.toString()); + if (AppConfig.DEBUG) Log.d(TAG, "Found link: " + currentChapter.getLink()); + return ID3Reader.ACTION_DONT_SKIP; + } + } else if (header.getId().equals("APIC")) { + Log.d(TAG, header.toString()); + } return super.onStartFrameHeader(header, input); } diff --git a/src/de/danoeh/antennapod/util/id3reader/ID3Reader.java b/src/de/danoeh/antennapod/util/id3reader/ID3Reader.java index dff6d77e8..8541d667d 100644 --- a/src/de/danoeh/antennapod/util/id3reader/ID3Reader.java +++ b/src/de/danoeh/antennapod/util/id3reader/ID3Reader.java @@ -150,40 +150,64 @@ public class ID3Reader { return new FrameHeader(id, size, flags); } - protected String readString(InputStream input, int max) throws IOException, + protected int readString(StringBuffer buffer, InputStream input, int max) throws IOException, ID3ReaderException { if (max > 0) { char[] encoding = readBytes(input, 1); max--; if (encoding[0] == ENCODING_UNICODE) { - return readUnicodeString(input, max); + return readUnicodeString(buffer, input, max) + 1; // take encoding byte into account } else { - return readISOString(input, max); + return readISOString(buffer, input, max) + 1; // take encoding byte into account } } else { - return ""; + if (buffer != null) { + buffer.append(""); + } + return 0; } } - protected String readISOString(InputStream input, int max) + protected int readISOString(StringBuffer buffer, InputStream input, int max) throws IOException, ID3ReaderException { int bytesRead = 0; - StringBuilder builder = new StringBuilder(); char c; while (++bytesRead <= max && (c = (char) input.read()) > 0) { - builder.append(c); + if (buffer != null) { + buffer.append(c); + } } - return builder.toString(); + return bytesRead; } - private String readUnicodeString(InputStream input, int max) + private int readUnicodeString(StringBuffer strBuffer, InputStream input, int max) throws IOException, ID3ReaderException { byte[] buffer = new byte[max]; - IOUtils.readFully(input, buffer); + int c, cZero = -1; + int i = 0; + for (; i < max; i++) { + c = input.read(); + if (c == -1) { + break; + } else if (c == 0) { + if (cZero == 0) { + // termination character found + break; + } else { + cZero = 0; + } + } else { + buffer[i] = (byte) c; + cZero = -1; + } + } Charset charset = Charset.forName("UTF-16"); - return charset.newDecoder().decode(ByteBuffer.wrap(buffer)).toString(); + if (strBuffer != null) { + strBuffer.append(charset.newDecoder().decode(ByteBuffer.wrap(buffer)).toString()); + } + return i; } public int onStartTagHeader(TagHeader header) { -- cgit v1.2.3 From 8b3c7d67234025978c0b27c00898f94b01fab238 Mon Sep 17 00:00:00 2001 From: daniel oeh Date: Fri, 21 Jun 2013 18:00:58 +0200 Subject: Added support for ID3 2.4 tag, resolved problems with frame size calculation --- .../antennapod/util/id3reader/ChapterReader.java | 6 ++- .../antennapod/util/id3reader/ID3Reader.java | 47 ++++++++++++++++------ .../util/id3reader/model/FrameHeader.java | 5 +-- 3 files changed, 41 insertions(+), 17 deletions(-) (limited to 'src/de/danoeh/antennapod/util') diff --git a/src/de/danoeh/antennapod/util/id3reader/ChapterReader.java b/src/de/danoeh/antennapod/util/id3reader/ChapterReader.java index a04763bcd..f897f886c 100644 --- a/src/de/danoeh/antennapod/util/id3reader/ChapterReader.java +++ b/src/de/danoeh/antennapod/util/id3reader/ChapterReader.java @@ -2,6 +2,7 @@ package de.danoeh.antennapod.util.id3reader; import java.io.IOException; import java.io.InputStream; +import java.net.URLDecoder; import java.util.ArrayList; import java.util.List; @@ -67,7 +68,10 @@ public class ChapterReader extends ID3Reader { int descriptionLength = readString(null, input, header.getSize()); StringBuffer link = new StringBuffer(); readISOString(link, input, header.getSize() - descriptionLength); - currentChapter.setLink(link.toString()); + String decodedLink = URLDecoder.decode(link.toString(), "UTF-8"); + + currentChapter.setLink(decodedLink); + if (AppConfig.DEBUG) Log.d(TAG, "Found link: " + currentChapter.getLink()); return ID3Reader.ACTION_DONT_SKIP; } diff --git a/src/de/danoeh/antennapod/util/id3reader/ID3Reader.java b/src/de/danoeh/antennapod/util/id3reader/ID3Reader.java index 8541d667d..92f817363 100644 --- a/src/de/danoeh/antennapod/util/id3reader/ID3Reader.java +++ b/src/de/danoeh/antennapod/util/id3reader/ID3Reader.java @@ -24,7 +24,11 @@ public class ID3Reader { protected int readerPosition; - private static final byte ENCODING_UNICODE = 1; + private static final byte ENCODING_UTF16_WITH_BOM = 1; + private static final byte ENCODING_UTF16_WITHOUT_BOM = 2; + private static final byte ENCODING_UTF8 = 3; + + private TagHeader tagHeader; public ID3Reader() { } @@ -34,7 +38,7 @@ public class ID3Reader { int rc; readerPosition = 0; char[] tagHeaderSource = readBytes(input, HEADER_LENGTH); - TagHeader tagHeader = createTagHeader(tagHeaderSource); + tagHeader = createTagHeader(tagHeaderSource); if (tagHeader == null) { onNoTagHeaderFound(); } else { @@ -124,12 +128,12 @@ public class ID3Reader { + HEADER_LENGTH); } if (hasTag) { - String id = null; - id = new String(source, 0, ID3_LENGTH); + String id = new String(source, 0, ID3_LENGTH); char version = (char) ((source[3] << 8) | source[4]); byte flags = (byte) source[5]; int size = (source[6] << 24) | (source[7] << 16) | (source[8] << 8) | source[9]; + size = unsynchsafe(size); return new TagHeader(id, size, version, flags); } else { return null; @@ -142,23 +146,41 @@ public class ID3Reader { throw new ID3ReaderException("Length of header must be " + HEADER_LENGTH); } - String id = null; - id = new String(source, 0, FRAME_ID_LENGTH); - int size = (((int) source[4]) << 24) | (((int) source[5]) << 16) - | (((int) source[6]) << 8) | source[7]; + String id = new String(source, 0, FRAME_ID_LENGTH); + + int size = (((int) source[4]) << 24) | (((int) source[5]) << 16) + | (((int) source[6]) << 8) | source[7]; + if (tagHeader != null && tagHeader.getVersion() >= 0x0400) { + size = unsynchsafe(size); + } char flags = (char) ((source[8] << 8) | source[9]); return new FrameHeader(id, size, flags); } + private int unsynchsafe(int in) { + int out = 0; + int mask = 0x7F000000; + + while (mask != 0) { + out >>= 1; + out |= in & mask; + mask >>= 8; + } + + return out; + } + protected int readString(StringBuffer buffer, InputStream input, int max) throws IOException, ID3ReaderException { if (max > 0) { char[] encoding = readBytes(input, 1); max--; - if (encoding[0] == ENCODING_UNICODE) { - return readUnicodeString(buffer, input, max) + 1; // take encoding byte into account - } else { + if (encoding[0] == ENCODING_UTF16_WITH_BOM || encoding[0] == ENCODING_UTF16_WITHOUT_BOM) { + return readUnicodeString(buffer, input, max, Charset.forName("UTF-16")) + 1; // take encoding byte into account + } else if (encoding[0] == ENCODING_UTF8) { + return readUnicodeString(buffer, input, max, Charset.forName("UTF-8")) + 1; // take encoding byte into account + } else { return readISOString(buffer, input, max) + 1; // take encoding byte into account } } else { @@ -182,7 +204,7 @@ public class ID3Reader { return bytesRead; } - private int readUnicodeString(StringBuffer strBuffer, InputStream input, int max) + private int readUnicodeString(StringBuffer strBuffer, InputStream input, int max, Charset charset) throws IOException, ID3ReaderException { byte[] buffer = new byte[max]; int c, cZero = -1; @@ -203,7 +225,6 @@ public class ID3Reader { cZero = -1; } } - Charset charset = Charset.forName("UTF-16"); if (strBuffer != null) { strBuffer.append(charset.newDecoder().decode(ByteBuffer.wrap(buffer)).toString()); } diff --git a/src/de/danoeh/antennapod/util/id3reader/model/FrameHeader.java b/src/de/danoeh/antennapod/util/id3reader/model/FrameHeader.java index 2c0d8e5ba..df73393a5 100644 --- a/src/de/danoeh/antennapod/util/id3reader/model/FrameHeader.java +++ b/src/de/danoeh/antennapod/util/id3reader/model/FrameHeader.java @@ -11,8 +11,7 @@ public class FrameHeader extends Header { @Override public String toString() { - return "FrameHeader [flags=" + Integer.toString(flags) + ", id=" + id + ", size=" + size - + "]"; - } + return String.format("FrameHeader [flags=%s, id=%s, size=%s]", Integer.toBinaryString(flags), id, Integer.toBinaryString(size)); + } } -- cgit v1.2.3