/* * TV-Browser for Android * Copyright (C) 2013 René Mach (rene@tvbrowser.org) * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software * and associated documentation files (the "Software"), to use, copy, modify or merge the Software, * furthermore to publish and distribute the Software free of charge without modifications and to * permit persons to whom the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package org.tvbrowser.view; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import org.tvbrowser.settings.SettingConstants; import org.tvbrowser.utils.IOUtils; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.os.Handler; import android.support.v4.content.ContextCompat; import android.text.Spannable; import android.text.TextPaint; import android.view.View; public class ProgramPanel extends View { private Date mStartTime; private long mEndTime; private String mTitle; private String mEpisode; private String mGenre; private String mPictureCopyright; private ColorLine[] mCategoriesString; private BitmapDrawable mPicture; private boolean mIsExpired; private int mChannelID; private Rect mStartTimeBounds = new Rect(); private int mBigRowCount; private int mSmallRowCount; private int mSuperSmallCount; private String mStartTimeString; public ProgramPanel(Context context, final long startTime, final long endTime, final String title, final int channelID) { super(context); mBigRowCount = 0; mSmallRowCount = 0; mSuperSmallCount = 0; mEndTime = endTime; mChannelID = channelID; setStartTime(startTime); setTitle(title); } private void setTitle(String title) { Object[] result = getBreakerText(title, getTextWidth() - mStartTimeBounds.width() - ProgramTableLayoutConstants.TIME_TITLE_GAP, ProgramTableLayoutConstants.NOT_EXPIRED_TITLE_PAINT); mTitle = result[0].toString(); mBigRowCount = (Integer)result[1]; } private void setStartTime(long startTime) { mStartTime = new Date(startTime); mStartTimeString = ProgramTableLayoutConstants.TIME_FORMAT.format(mStartTime); ProgramTableLayoutConstants.NOT_EXPIRED_TITLE_PAINT.getTextBounds(mStartTimeString, 0, mStartTimeString.length(), mStartTimeBounds); } public void setInfoString(Spannable value) { if(value != null && value.toString().trim().length() > 0) { Object[] result = getBreakerText(value.toString(), getTextWidth() - mStartTimeBounds.width() - ProgramTableLayoutConstants.TIME_TITLE_GAP, ProgramTableLayoutConstants.NOT_EXPIRED_PICTURE_COPYRIGHT_PAINT); String all = result[0].toString(); String[] lines = all.split("\n"); mCategoriesString = new ColorLine[lines.length]; HashMap<String, Integer> categoryColorMap = IOUtils.loadCategoryColorMap(getContext()); for(int i = 0; i < lines.length; i++) { mCategoriesString[i] = new ColorLine(); String[] lineParts = lines[i].split(","); for(int j = 0; j < lineParts.length-1; j++) { mCategoriesString[i].addEntry(new ColorEntry(categoryColorMap.get(lineParts[j].trim()), lineParts[j], true)); } if(lineParts.length > 0) { mCategoriesString[i].addEntry(new ColorEntry(categoryColorMap.get(lineParts[lineParts.length-1].trim()), lineParts[lineParts.length-1], (i != lines.length-1))); } } mSuperSmallCount += (Integer)result[1]; } } /* * Breaks the given String into string with line breaks at needed positions. */ private static final Object[] getBreakerText(String temp, int width, Paint toCheck) { StringBuilder parts = new StringBuilder(); temp = temp.trim().replace("\u00AD", ""); int rowCount = 0; do { int length = toCheck.breakText(temp, true, width, null); float measured = toCheck.measureText(temp); if(length < temp.length() && measured >= width) { int bestBreak = temp.lastIndexOf("-", length-1); if(bestBreak == -1) { bestBreak = temp.lastIndexOf(" ", length-1); } if(bestBreak == -1) { bestBreak = temp.lastIndexOf(",", length-1); } if(bestBreak == -1) { bestBreak = temp.lastIndexOf("/", length-1); } if(bestBreak > 0) { parts.append(temp.substring(0, bestBreak+1).trim()).append("\n"); temp = temp.substring(bestBreak+1).trim(); } else { parts.append(temp.substring(0, length-1).trim()).append("\n"); temp = temp.substring(length-1).trim(); } } else { parts.append(temp); temp = ""; } rowCount++; }while(!temp.trim().isEmpty()); return new Object[] {parts.toString(),rowCount}; } public void setGenre(String genre) { if(genre != null && !genre.trim().isEmpty()) { Object[] result = getBreakerText(genre.trim(), getTextWidth() - mStartTimeBounds.width() - ProgramTableLayoutConstants.TIME_TITLE_GAP, ProgramTableLayoutConstants.NOT_EXPIRED_GENRE_EPISODE_PAINT); mGenre = result[0].toString(); mSmallRowCount += (Integer)result[1]; } } public int getTextWidth() { return ProgramTableLayoutConstants.COLUMN_WIDTH - ProgramTableLayoutConstants.PADDING_SIDE * 3; } public void setEpisode(String episode) { if(episode != null && !episode.trim().isEmpty()) { Object[] result = getBreakerText(episode.trim(), getTextWidth() - mStartTimeBounds.width() - ProgramTableLayoutConstants.TIME_TITLE_GAP, ProgramTableLayoutConstants.NOT_EXPIRED_GENRE_EPISODE_PAINT); mEpisode = result[0].toString(); mSmallRowCount += (Integer)result[1]; } } public void setPicture(String copyright, BitmapDrawable picture) { if(copyright != null && !copyright.trim().isEmpty() && picture != null) { Object[] result = getBreakerText(copyright.trim(), ProgramTableLayoutConstants.COLUMN_WIDTH - mStartTimeBounds.width() - ProgramTableLayoutConstants.TIME_TITLE_GAP, ProgramTableLayoutConstants.NOT_EXPIRED_PICTURE_COPYRIGHT_PAINT); mPictureCopyright = result[0].toString(); mSuperSmallCount += (Integer)result[1]; mPicture = picture; float percent = mPicture.getBounds().width() / (float)(getTextWidth() - mStartTimeBounds.width() - ProgramTableLayoutConstants.TIME_TITLE_GAP); if(percent > 1) { mPicture.setBounds(0, 0, (int)(mPicture.getBounds().width() / percent), (int)(mPicture.getBounds().height() / percent)); } if(isExpired()) { if(SettingConstants.IS_DARK_THEME) { mPicture.setColorFilter(ContextCompat.getColor(getContext(), org.tvbrowser.tvbrowser.R.color.dark_gray), PorterDuff.Mode.DARKEN); } else { mPicture.setColorFilter(ContextCompat.getColor(getContext(), android.R.color.darker_gray), PorterDuff.Mode.LIGHTEN); } } } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //super.onMeasure(widthMeasureSpec, heightMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); int pictureHeight = 0; if(mPictureCopyright != null && !mPictureCopyright.trim().isEmpty() && mPicture != null) { pictureHeight = mPicture.getBounds().height(); } height = Math.max(height, ProgramTableLayoutConstants.BIG_MAX_FONT_HEIGHT * mBigRowCount + ProgramTableLayoutConstants.SMALL_MAX_FONT_HEIGHT * mSmallRowCount + pictureHeight + mSuperSmallCount * ProgramTableLayoutConstants.SUPER_SMALL_MAX_FONT_HEIGHT); setMeasuredDimension(ProgramTableLayoutConstants.COLUMN_WIDTH, height); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.translate(ProgramTableLayoutConstants.PADDING_SIDE, 0); TextPaint toUseForTimeAndTitle = ProgramTableLayoutConstants.NOT_EXPIRED_TITLE_PAINT; TextPaint toUseForGenreAndEpisode = ProgramTableLayoutConstants.NOT_EXPIRED_GENRE_EPISODE_PAINT; TextPaint toUseForPictureCopyright = ProgramTableLayoutConstants.NOT_EXPIRED_PICTURE_COPYRIGHT_PAINT; if(isExpired()) { toUseForTimeAndTitle = ProgramTableLayoutConstants.EXPIRED_TITLE_PAINT; toUseForGenreAndEpisode = ProgramTableLayoutConstants.EXPIRED_GENRE_EPISODE_PAINT; toUseForPictureCopyright = ProgramTableLayoutConstants.EXPIRED_PICTURE_COPYRIGHT_PAINT; } // draw start time canvas.drawText(mStartTimeString, 0, ProgramTableLayoutConstants.BIG_MAX_FONT_HEIGHT - ProgramTableLayoutConstants.BIG_FONT_DESCEND, toUseForTimeAndTitle); canvas.translate(mStartTimeBounds.width() + ProgramTableLayoutConstants.TIME_TITLE_GAP, 0); String[] lines = mTitle.split("\n"); // draw title for(int i = 0; i < lines.length; i++) { canvas.drawText(lines[i], 0, (i+1) * ProgramTableLayoutConstants.BIG_MAX_FONT_HEIGHT - ProgramTableLayoutConstants.BIG_FONT_DESCEND, toUseForTimeAndTitle); } canvas.translate(0, lines.length * ProgramTableLayoutConstants.BIG_MAX_FONT_HEIGHT); // draw picture copyright and picture if(mPictureCopyright != null && mPicture != null) { mPicture.draw(canvas); canvas.translate(0, mPicture.getBounds().height()); lines = mPictureCopyright.split("\n"); for(int i = 0; i < lines.length; i++) { canvas.drawText(lines[i], 0, (i+1) * ProgramTableLayoutConstants.SUPER_SMALL_MAX_FONT_HEIGHT - ProgramTableLayoutConstants.SUPER_SMALL_FONT_DESCEND, toUseForPictureCopyright); } canvas.translate(0, lines.length * ProgramTableLayoutConstants.SUPER_SMALL_MAX_FONT_HEIGHT); } // draw additonal infos if(mCategoriesString != null) { //lines = mInfoString.split("\n"); final int oldColor = toUseForPictureCopyright.getColor(); final String separator = ","; final float separatorWidth = toUseForPictureCopyright.measureText(separator); for(int i = 0; i < mCategoriesString.length; i++) { canvas.save(); for(Iterator<ColorEntry> it = mCategoriesString[i].getEntries(); it.hasNext();) { ColorEntry entry = it.next(); Integer color = entry.getColor(); if(color != null && !isExpired()) { toUseForPictureCopyright.setColor(color); } else { toUseForPictureCopyright.setColor(oldColor); } canvas.drawText(entry.getText(), 0, (i+1) * ProgramTableLayoutConstants.SUPER_SMALL_MAX_FONT_HEIGHT - ProgramTableLayoutConstants.SUPER_SMALL_FONT_DESCEND, toUseForPictureCopyright); canvas.translate(entry.measure(toUseForPictureCopyright), 0); if(entry.needsSeparator()) { toUseForPictureCopyright.setColor(oldColor); canvas.drawText(separator, 0, (i+1) * ProgramTableLayoutConstants.SUPER_SMALL_MAX_FONT_HEIGHT - ProgramTableLayoutConstants.SUPER_SMALL_FONT_DESCEND, toUseForPictureCopyright); canvas.translate(separatorWidth, 0); } } canvas.restore(); /* String first = lines[i].substring(0, lines[i].length()/2); String second = lines[i].substring(lines[i].length()/2); canvas.save(); toUseForPictureCopyright.setColor(Color.BLUE); canvas.drawText(first, 0, (i+1) * ProgramTableLayoutConstants.SUPER_SMALL_MAX_FONT_HEIGHT - ProgramTableLayoutConstants.SUPER_SMALL_FONT_DESCEND, toUseForPictureCopyright); canvas.translate(toUseForPictureCopyright.measureText(first), 0); toUseForPictureCopyright.setColor(Color.RED); canvas.drawText(second, 0, (i+1) * ProgramTableLayoutConstants.SUPER_SMALL_MAX_FONT_HEIGHT - ProgramTableLayoutConstants.SUPER_SMALL_FONT_DESCEND, toUseForPictureCopyright); canvas.restore();*/ } toUseForPictureCopyright.setColor(oldColor); canvas.translate(0, mCategoriesString.length * ProgramTableLayoutConstants.SUPER_SMALL_MAX_FONT_HEIGHT); } // draw genre if(mGenre != null) { lines = mGenre.split("\n"); for(int i = 0; i < lines.length; i++) { canvas.drawText(lines[i], 0, (i+1) * ProgramTableLayoutConstants.SMALL_MAX_FONT_HEIGHT - ProgramTableLayoutConstants.SMALL_FONT_DESCEND, toUseForGenreAndEpisode); } canvas.translate(0, lines.length * ProgramTableLayoutConstants.SMALL_MAX_FONT_HEIGHT); } // draw episode title if(mEpisode != null) { lines = mEpisode.split("\n"); for(int i = 0; i < lines.length; i++) { canvas.drawText(lines[i], 0, (i+1) * ProgramTableLayoutConstants.SMALL_MAX_FONT_HEIGHT - ProgramTableLayoutConstants.SMALL_FONT_DESCEND, toUseForGenreAndEpisode); } } } public void checkExpired(Handler handler) { if(!mIsExpired && isExpired()) { if(mPicture != null) { mPicture.setColorFilter(ContextCompat.getColor(getContext(), android.R.color.darker_gray), PorterDuff.Mode.LIGHTEN); } handler.post(new Runnable() { @Override public void run() { try { invalidate(); }catch(Throwable t) {} } }); } } public boolean isOnAir() { return mStartTime.getTime() <= System.currentTimeMillis() && mEndTime > System.currentTimeMillis(); } public boolean isExpired() { if(!mIsExpired) { mIsExpired = mEndTime < System.currentTimeMillis(); } return mIsExpired; } public int getChannelID() { return mChannelID; } public int getEndHour(Calendar test) { Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(mEndTime); int hour = cal.get(Calendar.HOUR_OF_DAY); if(test.get(Calendar.DAY_OF_YEAR) + 1 == cal.get(Calendar.DAY_OF_YEAR)) { hour += 24; } return hour; } public int getStartHour(Calendar test) { Calendar cal = Calendar.getInstance(); cal.setTime(mStartTime); int hour = cal.get(Calendar.HOUR_OF_DAY); if(test.get(Calendar.DAY_OF_YEAR) + 1 == cal.get(Calendar.DAY_OF_YEAR)) { hour += 24; } return hour; } public void clear() { mEpisode = null; mGenre = null; mPicture = null; mPictureCopyright = null; mStartTimeBounds = null; mStartTimeString = null; mTitle = null; } @Override public String toString() { return mStartTime + " '" + mTitle + "' on " + mChannelID; } private static final class ColorEntry { private Integer mColor; private String mText; private boolean mNeedsSeparator; public ColorEntry(Integer color, String text, boolean needsSeparator) { mColor = color; mText = text; mNeedsSeparator = needsSeparator; } public float measure(Paint paint) { return paint.measureText(mText); } public String getText() { return mText; } public Integer getColor() { return mColor; } public boolean needsSeparator() { return mNeedsSeparator; } } private static final class ColorLine { private ArrayList<ColorEntry> mEntryList; public ColorLine() { mEntryList = new ArrayList<ProgramPanel.ColorEntry>(); } public Iterator<ColorEntry> getEntries() { return mEntryList.iterator(); } public void addEntry(ColorEntry entry) { mEntryList.add(entry); } } }