Merge branch 'develop'

This commit is contained in:
daniel oeh 2014-06-14 14:09:52 +02:00
commit 1673f6eaf8
48 changed files with 1070 additions and 695 deletions

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.danoeh.antennapod"
android:versionCode="37"
android:versionName="0.9.9.0">
android:versionCode="38"
android:versionName="0.9.9.1">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

View File

@ -1,6 +1,10 @@
Change Log
==========
Version 0.9.9.1
---------------
* Several bugfixes and improvements
Version 0.9.9.0
---------------
* New user interface

View File

@ -41,9 +41,9 @@
<div id="header" align="center">
<img src="logo.png" alt="Logo" width="100px" height="100px"/>
<p>AntennaPod, Version 0.9.9.0</p>
<p>AntennaPod, Version 0.9.9.1</p>
<p>Copyright © 2012 Daniel Oeh</p>
<p>Copyright © 2014 Daniel Oeh</p>
<p>Licensed under the MIT License <a href="LICENSE.html">(View)</a></p>
</div>

View File

@ -3,7 +3,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.10.+'
classpath 'com.android.tools.build:gradle:0.11.+'
}
}
apply plugin: 'android'
@ -28,16 +28,18 @@ dependencies {
compile 'commons-io:commons-io:2.4'
compile 'com.nineoldandroids:library:2.4.0'
compile project(':submodules:dslv:library')
compile 'com.jayway.android.robotium:robotium-solo:5.1'
}
android {
compileSdkVersion 19
buildToolsVersion "19.0.3"
buildToolsVersion "19.1.0"
defaultConfig {
minSdkVersion 10
targetSdkVersion 19
testPackageName "de.test.antennapod"
testApplicationId "de.test.antennapod"
testInstrumentationRunner "instrumentationTest.de.test.antennapod.AntennaPodTestRunner"
}
@ -80,7 +82,7 @@ android {
buildTypes {
debug {
packageNameSuffix ".debug"
applicationIdSuffix ".debug"
}
release {
runProguard true

View File

@ -5,7 +5,7 @@
<groupId>de.danoeh</groupId>
<artifactId>antennapod</artifactId>
<packaging>apk</packaging>
<version>0.9.9.0</version>
<version>0.9.9.1</version>
<name>AntennaPod</name>

View File

@ -1,11 +1,9 @@
-printmapping out.map
-renamesourcefileattribute SourceFile
-keepattributes SourceFile,LineNumberTable
-dontpreverify
-repackageclasses ''
-allowaccessmodification
-optimizations !code/simplification/arithmetic
-keepattributes *Annotation*
#-injars libs/presto_client-0.8.5.jar

View File

@ -18,7 +18,6 @@
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginBottom="4dp"
android:textColor="?android:attr/textColorTertiary"
android:textSize="@dimen/text_size_micro"/>
@ -28,7 +27,6 @@
android:layout_height="wrap_content"
android:layout_below="@id/txtvPublished"
android:layout_marginBottom="8dp"
android:layout_marginRight="4dp"
style="@style/AntennaPod.TextView.ListItemPrimaryTitle"/>
@ -59,7 +57,6 @@
android:layout_alignParentLeft="true"
android:layout_below="@id/txtvItemname"
android:maxLines="2"
android:layout_marginBottom="2dp"
android:textColor="?android:attr/textColorTertiary"
android:textSize="@dimen/text_size_micro"/>
@ -77,22 +74,11 @@
<TextView
android:id="@+id/statusUnread"
android:contentDescription="@string/status_unread_label"
android:layout_width="wrap_content"
android:layout_height="18dp"
android:layout_height="wrap_content"
style="@style/AntennaPod.TextView.UnreadIndicator"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="8dp"
android:layout_marginBottom="8dp"
android:background="@color/status_unread"
android:gravity="center"
android:minWidth="@dimen/status_indicator_width"
android:text="@string/new_label"
android:textAlignment="center"
android:textColor="@color/white"
android:textSize="@dimen/text_size_micro"
android:textStyle="bold"/>
android:layout_alignParentTop="true"/>
</RelativeLayout>
<View

View File

@ -1,129 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingRight="8dp" >
<ImageView
android:id="@+id/imgvFeedimage"
android:contentDescription="@string/cover_label"
android:layout_width="@dimen/thumbnail_length"
android:layout_height="@dimen/thumbnail_length"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_marginRight="4dip"
android:adjustViewBounds="true"
android:cropToPadding="true"
android:scaleType="fitXY" />
<LinearLayout
android:id="@+id/lEpisodeCounts"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:orientation="vertical" >
<RelativeLayout
android:id="@+id/lNewStatusLabel"
android:contentDescription="@string/new_episodes_count_label"
android:layout_width="@dimen/status_indicator_width"
android:layout_height="0dip"
android:layout_marginBottom="8dp"
android:layout_marginTop="4dp"
android:layout_weight="1"
android:background="@color/status_unread" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="4dp"
android:src="@drawable/white_circle"
tools:ignore="ContentDescription"/>
<TextView
android:id="@+id/txtvNewEps"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_marginLeft="4dp"
android:layout_marginRight="8dp"
android:maxLines="1"
android:minEms="2"
android:textColor="@color/white"
android:textSize="@dimen/text_size_micro"
android:textStyle="bold" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/lProgressStatusLabel"
android:contentDescription="@string/in_progress_episodes_count_label"
android:layout_width="@dimen/status_indicator_width"
android:layout_height="0dip"
android:layout_marginBottom="8dp"
android:layout_weight="1"
android:background="@color/ics_gray" >
<TextView
android:id="@+id/txtvProgressEps"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:background="@color/status_progress"
android:maxLines="1"
android:minEms="2"
android:paddingLeft="4dp"
android:textColor="@color/white"
android:textSize="@dimen/text_size_micro"
android:textStyle="bold" />
<ImageView
android:layout_width="10dp"
android:layout_height="8dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="2dp"
android:layout_marginLeft="2dp"
android:src="@drawable/av_play_dark"
tools:ignore="ContentDescription"/>
</RelativeLayout>
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_centerVertical="true"
android:layout_toLeftOf="@id/lEpisodeCounts"
android:layout_toRightOf="@id/imgvFeedimage"
android:orientation="vertical" >
<TextView
android:id="@+id/txtvFeedname"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_size_small" />
<TextView
android:id="@+id/txtvNumEpisodes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/text_size_micro" />
<TextView
android:id="@+id/txtvLastUpdate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/text_size_micro" />
</LinearLayout>
</RelativeLayout>

View File

@ -1,138 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/imgvFeedimage"
android:contentDescription="@string/cover_label"
android:layout_width="@dimen/thumbnail_length"
android:layout_height="@dimen/thumbnail_length"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_margin="8dp"
android:adjustViewBounds="true"
android:cropToPadding="true"
android:scaleType="fitXY"/>
<LinearLayout
android:id="@+id/lEpisodeCounts"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_alignRight="@id/imgvFeedimage"
android:layout_below="@id/imgvFeedimage"
android:layout_centerVertical="true"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/lNewStatusLabel"
android:contentDescription="@string/new_episodes_count_label"
android:layout_width="@dimen/status_indicator_width"
android:layout_height="0dip"
android:layout_marginBottom="8dp"
android:layout_marginTop="4dp"
android:layout_weight="1"
android:background="@color/status_unread">
<ImageView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="4dp"
android:src="@drawable/white_circle"
tools:ignore="ContentDescription"/>
<TextView
android:id="@+id/txtvNewEps"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_marginLeft="4dp"
android:layout_marginRight="8dp"
android:maxLines="1"
android:minEms="2"
android:textColor="@color/white"
android:textSize="@dimen/text_size_micro"
android:textStyle="bold"/>
</RelativeLayout>
<RelativeLayout
android:id="@+id/lProgressStatusLabel"
android:contentDescription="@string/in_progress_episodes_count_label"
android:layout_width="@dimen/status_indicator_width"
android:layout_height="0dip"
android:layout_marginBottom="8dp"
android:layout_weight="1"
android:background="@color/ics_gray">
<TextView
android:id="@+id/txtvProgressEps"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:background="@color/status_progress"
android:maxLines="1"
android:minEms="2"
android:paddingLeft="4dp"
android:textColor="@color/white"
android:textSize="@dimen/text_size_micro"
android:textStyle="bold"/>
<ImageView
android:layout_width="10dp"
android:layout_height="8dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:src="@drawable/av_play_dark"
tools:ignore="ContentDescription"/>
</RelativeLayout>
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/imgvFeedimage"
android:layout_below="@id/imgvFeedimage"
android:layout_toLeftOf="@id/lEpisodeCounts"
android:orientation="vertical">
<TextView
android:id="@+id/txtvFeedname"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginRight="8dp"
android:ellipsize="end"
android:lines="1"
android:textSize="@dimen/text_size_small"
android:textColor="?android:attr/textColorPrimary"
android:textStyle="bold"/>
<TextView
android:id="@+id/txtvLastUpdate"
android:lines="2"
android:ellipsize="end"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/text_size_micro"
android:textColor="?android:attr/textColorTertiary"/>
<TextView
android:id="@+id/txtvNumEpisodes"
android:lines="1"
android:ellipsize="end"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginRight="8dp"
android:textSize="@dimen/text_size_micro"
android:textColor="?android:attr/textColorTertiary"/>
</LinearLayout>
</RelativeLayout>

View File

@ -24,6 +24,7 @@
android:id="@+id/txtvTitle"
android:lines="1"
android:ellipsize="end"
android:singleLine="true"
android:layout_centerVertical="true"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_size_navdrawer"

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imgvCover"
@ -24,6 +24,7 @@
android:id="@+id/txtvTitle"
android:lines="1"
android:ellipsize="end"
android:singleLine="true"
android:layout_centerVertical="true"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_size_navdrawer"
@ -35,4 +36,18 @@
android:layout_marginRight="48dp"
android:layout_toRightOf="@id/imgvCover"
/>
<TextView
android:id="@+id/txtvCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:lines="1"
android:textColor="?android:attr/textColorTertiary"
android:textSize="@dimen/text_size_navdrawer"
android:layout_marginLeft="12dp"
android:layout_marginTop="14dp"
android:layout_marginBottom="14dp"
android:layout_marginRight="16dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"/>
</RelativeLayout>

View File

@ -6,11 +6,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.SwipeRefreshLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/swipeRefreshLayout">
<com.mobeta.android.dslv.DragSortListView
android:id="@android:id/list"
android:scrollbarStyle="outsideOverlay"
@ -29,8 +24,6 @@
dslv:float_background_color="?attr/dragview_float_background"
dslv:use_default_controller="true"/>
</android.support.v4.widget.SwipeRefreshLayout>
<TextView
android:id="@id/android:empty"
android:layout_width="match_parent"

View File

@ -6,29 +6,25 @@
android:orientation="horizontal"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imgvImage"
android:contentDescription="@string/cover_label"
android:layout_width="@dimen/thumbnail_length_itemlist"
android:layout_height="@dimen/thumbnail_length_itemlist"
android:scaleType="centerCrop"/>
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:layout_marginRight="8dp">
<ImageView
android:id="@+id/imgvImage"
android:contentDescription="@string/cover_label"
android:layout_width="@dimen/thumbnail_length_itemlist"
android:layout_height="@dimen/thumbnail_length_itemlist"
android:layout_alignParentLeft="true"
android:scaleType="centerCrop"/>
android:layout_margin="4dp">
<TextView
android:id="@+id/txtvPublished"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="4dp"
android:layout_alignParentTop="true"
android:layout_toRightOf="@id/imgvImage"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:ellipsize="end"
android:maxLines="1"
@ -40,18 +36,24 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_below="@id/txtvPublished"
android:layout_marginLeft="8dp"
android:layout_marginTop="2dp"
android:layout_toRightOf="@id/imgvImage"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
style="@style/AntennaPod.TextView.ListItemPrimaryTitle"/>
<TextView
android:id="@+id/statusUnread"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
style="@style/AntennaPod.TextView.UnreadIndicator"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_toRightOf="@id/imgvImage"
android:layout_alignParentLeft="true"
android:orientation="vertical">
<RelativeLayout
@ -74,7 +76,6 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_toLeftOf="@id/imgvInPlaylist"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_alignParentLeft="true"/>
@ -83,8 +84,6 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_toLeftOf="@id/imgvInPlaylist"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_alignParentLeft="true"
android:textColor="?android:attr/textColorTertiary"
android:textSize="@dimen/text_size_micro"/>

View File

@ -15,30 +15,27 @@
android:layout_margin="8dp"
android:contentDescription="@string/drag_handle_content_description"/>
<ImageView
android:id="@+id/imgvImage"
android:contentDescription="@string/cover_label"
android:layout_width="@dimen/thumbnail_length_queue_item"
android:layout_height="@dimen/thumbnail_length_queue_item"
android:scaleType="centerCrop"/>
<RelativeLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:layout_marginRight="8dp">
<ImageView
android:id="@+id/imgvImage"
android:contentDescription="@string/cover_label"
android:layout_width="@dimen/thumbnail_length_queue_item"
android:layout_height="@dimen/thumbnail_length_queue_item"
android:layout_alignParentLeft="true"
android:scaleType="centerCrop"/>
android:layout_margin="8dp">
<TextView
android:id="@+id/txtvTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="8dp"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:layout_toRightOf="@id/imgvImage"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:layout_alignParentLeft="true"
android:ellipsize="end"
android:lines="2"
android:textColor="?android:attr/textColorPrimary"
@ -49,7 +46,7 @@
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_toRightOf="@id/imgvImage"
android:layout_alignParentLeft="true"
android:orientation="vertical">
<RelativeLayout
@ -57,6 +54,14 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/txtvPosition"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:textColor="?android:attr/textColorTertiary"
android:textSize="@dimen/text_size_micro"/>
<ProgressBar
android:id="@+id/pbar_download_progress"
style="?android:attr/progressBarStyleHorizontal"
@ -65,8 +70,7 @@
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_alignParentLeft="true"/>
android:layout_toRightOf="@id/txtvPosition"/>
</RelativeLayout>
</LinearLayout>

View File

@ -3,13 +3,6 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/mark_all_read_item"
android:title="@string/mark_all_read_label"
android:menuCategory="container"
custom:showAsAction="ifRoom|collapseActionView"
android:icon="?attr/navigation_accept"/>
<item
android:id="@+id/refresh_item"
android:title="@string/refresh_label"
@ -17,6 +10,13 @@
custom:showAsAction="ifRoom|collapseActionView"
android:icon="?attr/navigation_refresh"/>
<item
android:id="@+id/mark_all_read_item"
android:title="@string/mark_all_read_label"
android:menuCategory="container"
custom:showAsAction="collapseActionView"
android:icon="?attr/navigation_accept"/>
<item
android:id="@+id/episode_filter_item"
android:title="@string/episode_filter_label"

View File

@ -289,7 +289,7 @@
<!--Directory chooser-->
<string name="selected_folder_label">Ausgewählter Ordner</string>
<string name="create_folder_label">Neuer Ordner</string>
<string name="choose_data_directory">Wähle Datenordner</string>
<string name="choose_data_directory">Datenordner auswählen</string>
<string name="create_folder_msg">Neuen Ordner mit Namen \"%1$s\" erstellen?</string>
<string name="create_folder_success">Neuer Ordner angelegt</string>
<string name="create_folder_error_no_write_access">Kann in diesem Ordner nicht schreiben</string>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item type="layout" name="feedlist_item">@layout/feedlist_item_grid</item>
</resources>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AntennaPod.TextView.UnreadIndicator" parent="@android:style/TextAppearance.Small">
<item name="android:textSize">@dimen/text_size_micro</item>
<item name="android:textColor">@color/new_indicator_green</item>
<item name="android:text">@string/new_label</item>
<item name="android:textAllCaps">true</item>
</style>
</resources>

View File

@ -3,19 +3,26 @@
<!--Activitiy and fragment titles-->
<string name="app_name">AntennaPod</string>
<string name="feeds_label">订阅</string>
<string name="add_feed_label">添加博客</string>
<string name="podcasts_label">播客</string>
<string name="episodes_label">曲目</string>
<string name="new_episodes_label">新曲目</string>
<string name="all_episodes_label">所有曲目</string>
<string name="new_label">最新</string>
<string name="waiting_list_label">等待列表</string>
<string name="settings_label">设置</string>
<string name="add_new_feed_label">添加播客</string>
<string name="downloads_label">下载</string>
<string name="downloads_running_label">正在运行</string>
<string name="downloads_completed_label">已完成</string>
<string name="downloads_log_label">日志</string>
<string name="cancel_download_label">取消下载</string>
<string name="playback_history_label">播放历史</string>
<string name="gpodnet_main_label">gpodder.net</string>
<string name="gpodnet_auth_label">gpodder.net 登录</string>
<!--New episodes fragment-->
<string name="recently_published_episodes_label">最近发布</string>
<string name="episode_filter_label">仅显示新曲目</string>
<!--Main activity-->
<string name="drawer_open">打开菜单</string>
<string name="drawer_close">关闭菜单</string>
@ -53,9 +60,11 @@
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">订阅 URL</string>
<string name="txtvfeedurl_label">添加播客 URL</string>
<string name="podcastdirectories_descr">您可以在 gpodder.net 通过名称、类别或热门来搜索新播客</string>
<string name="browse_gpoddernet_label">浏览 gpodder.net</string>
<!--Actions on feeds-->
<string name="mark_all_read_label">全部标识已读</string>
<string name="mark_all_read_msg">将所有曲目标记为已读</string>
<string name="show_info_label">查看信息</string>
<string name="remove_feed_label">删除播客</string>
<string name="share_link_label">分享网站链接</string>
@ -68,6 +77,7 @@
<string name="pause_label">暂停</string>
<string name="stream_label">流媒体</string>
<string name="remove_label">删除</string>
<string name="remove_episode_lable">移除曲目</string>
<string name="mark_read_label">标记已读</string>
<string name="mark_unread_label">标记未读</string>
<string name="add_to_queue_label">添加到播放列表</string>
@ -91,6 +101,7 @@
<string name="download_error_unsupported_type">未提供的订阅类型</string>
<string name="download_error_connection_error">链接错误</string>
<string name="download_error_unknown_host">未知主机</string>
<string name="download_error_unauthorized">认证错误</string>
<string name="cancel_all_downloads_label">取消所有下载</string>
<string name="download_cancelled_msg">已取消下载</string>
<string name="download_report_title">下载完成</string>
@ -99,6 +110,7 @@
<string name="download_error_request_error">请求出错</string>
<string name="download_error_db_access">数据库访问错误</string>
<string name="downloads_left">\u0020 下载剩余</string>
<string name="downloads_processing">正在处理下载</string>
<string name="download_notification_title">下载播客数据</string>
<string name="download_report_content">%1$d 下载成功, %2$d 失败</string>
<string name="download_log_title_unknown">未知标题</string>
@ -106,6 +118,8 @@
<string name="download_type_media">媒体文件</string>
<string name="download_type_image">图片</string>
<string name="download_request_error_dialog_message_prefix">尝试下载文件:\u0020 时出错</string>
<string name="authentication_notification_title">需要认证</string>
<string name="authentication_notification_msg">您所请求的资源需要用户名和密码</string>
<!--Mediaplayer messages-->
<string name="player_error_msg">错误!</string>
<string name="player_stopped_msg">没有可播放媒体</string>
@ -293,7 +307,10 @@
<string name="in_queue_label">曲目已经在播放列表中</string>
<string name="new_episodes_count_label">新曲目数</string>
<string name="in_progress_episodes_count_label">已收听曲目数</string>
<string name="drag_handle_content_description">拖动以变更本项目的位置</string>
<!--Feed information screen-->
<string name="authentication_label">验证</string>
<string name="authentication_descr">给本播客及曲目变更用户名及密码</string>
<!--AntennaPodSP-->
<string name="sp_apps_importing_feeds_msg">正在从选定的应用中导入订阅...</string>
</resources>

View File

@ -9,14 +9,13 @@
<color name="actionbar_gray">#DDDDDD</color>
<color name="download_success_green">#669900</color>
<color name="download_failed_red">#CC0000</color>
<color name="status_unread">#E099CC00</color>
<color name="status_progress">#E033B5E5</color>
<color name="status_playing">#E0EE5F52</color>
<color name="overlay_dark">#262C31</color>
<color name="overlay_light">#DDDDDD</color>
<color name="swipe_refresh_secondary_color_light">#EDEDED</color>
<color name="swipe_refresh_secondary_color_dark">#060708</color>
<color name="new_indicator_green">#669900</color>
<!-- Use Gingerbread-orange -->
<color name="selection_background_color_dark">#FEBB20</color>

View File

@ -11,7 +11,7 @@
<dimen name="text_size_medium">18sp</dimen>
<dimen name="text_size_large">22sp</dimen>
<dimen name="status_indicator_width">32dp</dimen>
<dimen name="thumbnail_length_itemlist">80dp</dimen>
<dimen name="thumbnail_length_itemlist">85dp</dimen>
<dimen name="thumbnail_length_queue_item">70dp</dimen>
<dimen name="thumbnail_length_downloaded_item">70dp</dimen>
<dimen name="thumbnail_length_onlinefeedview">110dp</dimen>

View File

@ -127,7 +127,7 @@
</style>
<style name="AntennaPod.TextView.ListItemPrimaryTitle" parent="@android:style/TextAppearance.Small">
<item name="android:textSize">15sp</item>
<item name="android:textSize">14sp</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:lines">2</item>
<item name="android:ellipsize">end</item>
@ -139,4 +139,10 @@
<item name="android:maxLines">2</item>
<item name="android:ellipsize">end</item>
</style>
<style name="AntennaPod.TextView.UnreadIndicator" parent="@android:style/TextAppearance.Small">
<item name="android:textSize">@dimen/text_size_micro</item>
<item name="android:textColor">@color/new_indicator_green</item>
<item name="android:text">@string/new_label</item>
</style>
</resources>

View File

@ -2,5 +2,6 @@ package de.danoeh.antennapod;
public final class AppConfig {
/** Should be used when setting User-Agent header for HTTP-requests. */
public final static String USER_AGENT = "AntennaPod/0.9.9.0";
public final static String USER_AGENT = "AntennaPod/0.9.9.1";
}

View File

@ -34,8 +34,6 @@ import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.util.playback.ExternalMedia;
import de.danoeh.antennapod.util.playback.Playable;
import java.util.List;
/**
* Activity for playing audio files.
*/
@ -624,20 +622,20 @@ public class AudioplayerActivity extends MediaplayerActivity {
}
}
private List<Feed> feeds;
private AsyncTask<Void, Void, List<Feed>> loadTask;
private DBReader.NavDrawerData navDrawerData;
private AsyncTask<Void, Void, DBReader.NavDrawerData> loadTask;
private void loadData() {
loadTask = new AsyncTask<Void, Void, List<Feed>>() {
loadTask = new AsyncTask<Void, Void, DBReader.NavDrawerData>() {
@Override
protected List<Feed> doInBackground(Void... params) {
return DBReader.getFeedList(AudioplayerActivity.this);
protected DBReader.NavDrawerData doInBackground(Void... params) {
return DBReader.getNavDrawerData(AudioplayerActivity.this);
}
@Override
protected void onPostExecute(List<Feed> result) {
protected void onPostExecute(DBReader.NavDrawerData result) {
super.onPostExecute(result);
feeds = result;
navDrawerData = result;
if (navAdapter != null) {
navAdapter.notifyDataSetChanged();
}
@ -667,8 +665,8 @@ public class AudioplayerActivity extends MediaplayerActivity {
private final NavListAdapter.ItemAccess itemAccess = new NavListAdapter.ItemAccess() {
@Override
public int getCount() {
if (feeds != null) {
return feeds.size();
if (navDrawerData != null) {
return navDrawerData.feeds.size();
} else {
return 0;
}
@ -676,8 +674,8 @@ public class AudioplayerActivity extends MediaplayerActivity {
@Override
public Feed getItem(int position) {
if (feeds != null && position < feeds.size()) {
return feeds.get(position);
if (navDrawerData != null && position < navDrawerData.feeds.size()) {
return navDrawerData.feeds.get(position);
} else {
return null;
}
@ -687,5 +685,15 @@ public class AudioplayerActivity extends MediaplayerActivity {
public int getSelectedItemIndex() {
return -1;
}
@Override
public int getQueueSize() {
return (navDrawerData != null) ? navDrawerData.queueSize : 0;
}
@Override
public int getNumberOfUnreadItems() {
return (navDrawerData != null) ? navDrawerData.numUnreadItems : 0;
}
};
}

View File

@ -166,6 +166,7 @@ public class FeedInfoActivity extends ActionBarActivity {
menu.findItem(R.id.support_item).setVisible(
feed != null && feed.getPaymentLink() != null);
menu.findItem(R.id.share_link_item).setVisible(feed != null &&feed.getLink() != null);
menu.findItem(R.id.visit_website_item).setVisible(feed != null && feed.getLink() != null);
return true;
}

View File

@ -39,10 +39,11 @@ public class MainActivity extends ActionBarActivity {
private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED
| EventDistributor.DOWNLOAD_QUEUED
| EventDistributor.FEED_LIST_UPDATE
| EventDistributor.UNREAD_ITEMS_UPDATE;
| EventDistributor.UNREAD_ITEMS_UPDATE
| EventDistributor.QUEUE_UPDATE;
private static final String PREF_NAME = "MainActivityPrefs";
private static final String PREF_IS_FIRST_LAUNCH = "prefMainActivityIsFirstLaunch";
public static final String PREF_NAME = "MainActivityPrefs";
public static final String PREF_IS_FIRST_LAUNCH = "prefMainActivityIsFirstLaunch";
public static final String EXTRA_NAV_INDEX = "nav_index";
public static final String EXTRA_NAV_TYPE = "nav_type";
@ -147,7 +148,7 @@ public class MainActivity extends ActionBarActivity {
}
public List<Feed> getFeeds() {
return feeds;
return (navDrawerData != null) ? navDrawerData.feeds : null;
}
private void loadFragment(int viewType, int relPos, Bundle args) {
@ -207,9 +208,9 @@ public class MainActivity extends ActionBarActivity {
}
public void loadFeedFragment(long feedID) {
if (feeds != null) {
for (int i = 0; i < feeds.size(); i++) {
if (feeds.get(i).getId() == feedID) {
if (navDrawerData != null) {
for (int i = 0; i < navDrawerData.feeds.size(); i++) {
if (navDrawerData.feeds.get(i).getId() == feedID) {
loadFragment(NavListAdapter.VIEW_TYPE_SUBSCRIPTION, i, null);
break;
}
@ -279,7 +280,7 @@ public class MainActivity extends ActionBarActivity {
EventDistributor.getInstance().register(contentUpdate);
Intent intent = getIntent();
if (feeds != null && intent.hasExtra(EXTRA_NAV_INDEX) && intent.hasExtra(EXTRA_NAV_TYPE)) {
if (navDrawerData != null && intent.hasExtra(EXTRA_NAV_INDEX) && intent.hasExtra(EXTRA_NAV_TYPE)) {
handleNavIntent();
}
@ -322,15 +323,15 @@ public class MainActivity extends ActionBarActivity {
return true;
}
private List<Feed> feeds;
private AsyncTask<Void, Void, List<Feed>> loadTask;
private DBReader.NavDrawerData navDrawerData;
private AsyncTask<Void, Void, DBReader.NavDrawerData> loadTask;
private int selectedNavListIndex = 0;
private NavListAdapter.ItemAccess itemAccess = new NavListAdapter.ItemAccess() {
@Override
public int getCount() {
if (feeds != null) {
return feeds.size();
if (navDrawerData != null) {
return navDrawerData.feeds.size();
} else {
return 0;
}
@ -338,8 +339,8 @@ public class MainActivity extends ActionBarActivity {
@Override
public Feed getItem(int position) {
if (feeds != null && position < feeds.size()) {
return feeds.get(position);
if (navDrawerData != null && position < navDrawerData.feeds.size()) {
return navDrawerData.feeds.get(position);
} else {
return null;
}
@ -350,23 +351,33 @@ public class MainActivity extends ActionBarActivity {
return selectedNavListIndex;
}
@Override
public int getQueueSize() {
return (navDrawerData != null) ? navDrawerData.queueSize : 0;
}
@Override
public int getNumberOfUnreadItems() {
return (navDrawerData != null) ? navDrawerData.numUnreadItems : 0;
}
};
private void loadData() {
cancelLoadTask();
loadTask = new AsyncTask<Void, Void, List<Feed>>() {
loadTask = new AsyncTask<Void, Void, DBReader.NavDrawerData>() {
@Override
protected List<Feed> doInBackground(Void... params) {
return DBReader.getFeedList(MainActivity.this);
protected DBReader.NavDrawerData doInBackground(Void... params) {
return DBReader.getNavDrawerData(MainActivity.this);
}
@Override
protected void onPostExecute(List<Feed> result) {
super.onPostExecute(result);
boolean handleIntent = (feeds == null);
protected void onPostExecute(DBReader.NavDrawerData result) {
super.onPostExecute(navDrawerData);
boolean handleIntent = (navDrawerData == null);
feeds = result;
navDrawerData = result;
navAdapter.notifyDataSetChanged();
if (handleIntent) {

View File

@ -61,6 +61,8 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
private Map<String, String> alternateFeedUrls;
private Downloader downloader;
private boolean isPaused;
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
@ -96,6 +98,18 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
}
}
@Override
protected void onResume() {
super.onResume();
isPaused = false;
}
@Override
protected void onPause() {
super.onPause();
isPaused = true;
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
@ -126,9 +140,11 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
if (status.isSuccessful()) {
parseFeed();
} else if (status.getReason() == DownloadError.ERROR_UNAUTHORIZED) {
Dialog dialog = new FeedViewAuthenticationDialog(OnlineFeedViewActivity.this,
R.string.authentication_notification_title, downloader.getDownloadRequest().getSource());
dialog.show();
if (!isFinishing() && !isPaused) {
Dialog dialog = new FeedViewAuthenticationDialog(OnlineFeedViewActivity.this,
R.string.authentication_notification_title, downloader.getDownloadRequest().getSource());
dialog.show();
}
} else {
String errorMsg = status.getReason().getErrorString(
OnlineFeedViewActivity.this);
@ -276,32 +292,30 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
}
private void showErrorDialog(String errorMsg) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.error_label);
if (errorMsg != null) {
builder.setMessage(getString(R.string.error_msg_prefix) + errorMsg);
} else {
builder.setMessage(R.string.error_msg_prefix);
}
builder.setNeutralButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
}
);
builder.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
setResult(RESULT_ERROR);
finish();
if (!isFinishing() && !isPaused) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.error_label);
if (errorMsg != null) {
builder.setMessage(getString(R.string.error_msg_prefix) + errorMsg);
} else {
builder.setMessage(R.string.error_msg_prefix);
}
});
builder.setNeutralButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
if (!isFinishing()) {
builder.show();
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
}
);
builder.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
setResult(RESULT_ERROR);
finish();
}
});
}
}

View File

@ -15,6 +15,7 @@ import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceScreen;
import android.support.v4.app.NavUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
@ -46,7 +47,7 @@ public class PreferenceActivity extends android.preference.PreferenceActivity {
private static final String PREF_FLATTR_THIS_APP = "prefFlattrThisApp";
private static final String PREF_FLATTR_AUTH = "pref_flattr_authenticate";
private static final String PREF_FLATTR_REVOKE = "prefRevokeAccess";
private static final String PREF_AUTO_FLATTR = "pref_auto_flattr";
private static final String PREF_AUTO_FLATTR = "pref_auto_flattr";
private static final String PREF_OPML_EXPORT = "prefOpmlExport";
private static final String PREF_ABOUT = "prefAbout";
private static final String PREF_CHOOSE_DATA_DIR = "prefChooseDataDir";
@ -89,7 +90,8 @@ public class PreferenceActivity extends android.preference.PreferenceActivity {
return true;
}
});
}
);
findPreference(PREF_FLATTR_REVOKE).setOnPreferenceClickListener(
new OnPreferenceClickListener() {
@ -101,7 +103,8 @@ public class PreferenceActivity extends android.preference.PreferenceActivity {
return true;
}
});
}
);
findPreference(PREF_ABOUT).setOnPreferenceClickListener(
new OnPreferenceClickListener() {
@ -113,7 +116,8 @@ public class PreferenceActivity extends android.preference.PreferenceActivity {
return true;
}
});
}
);
findPreference(PREF_OPML_EXPORT).setOnPreferenceClickListener(
new OnPreferenceClickListener() {
@ -125,7 +129,8 @@ public class PreferenceActivity extends android.preference.PreferenceActivity {
return true;
}
});
}
);
findPreference(PREF_CHOOSE_DATA_DIR).setOnPreferenceClickListener(
new OnPreferenceClickListener() {
@ -135,10 +140,12 @@ public class PreferenceActivity extends android.preference.PreferenceActivity {
startActivityForResult(
new Intent(PreferenceActivity.this,
DirectoryChooserActivity.class),
DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED);
DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED
);
return true;
}
});
}
);
findPreference(UserPreferences.PREF_THEME)
.setOnPreferenceChangeListener(
new OnPreferenceChangeListener() {
@ -153,18 +160,19 @@ public class PreferenceActivity extends android.preference.PreferenceActivity {
startActivity(i);
return true;
}
});
}
);
findPreference(UserPreferences.PREF_ENABLE_AUTODL)
.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (newValue instanceof Boolean) {
findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER).setEnabled((Boolean) newValue);
setSelectedNetworksEnabled((Boolean) newValue && UserPreferences.isEnableAutodownloadWifiFilter());
}
return true;
}
});
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (newValue instanceof Boolean) {
findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER).setEnabled((Boolean) newValue);
setSelectedNetworksEnabled((Boolean) newValue && UserPreferences.isEnableAutodownloadWifiFilter());
}
return true;
}
});
findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER)
.setOnPreferenceChangeListener(
new OnPreferenceChangeListener() {
@ -179,7 +187,8 @@ public class PreferenceActivity extends android.preference.PreferenceActivity {
return false;
}
}
});
}
);
findPreference(UserPreferences.PREF_EPISODE_CACHE_SIZE)
.setOnPreferenceChangeListener(
new OnPreferenceChangeListener() {
@ -190,7 +199,8 @@ public class PreferenceActivity extends android.preference.PreferenceActivity {
}
return true;
}
});
}
);
findPreference(PREF_PLAYBACK_SPEED_LAUNCHER)
.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
@ -343,12 +353,14 @@ public class PreferenceActivity extends android.preference.PreferenceActivity {
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
Intent destIntent = new Intent(this, MainActivity.class);
destIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(destIntent);
finish();
break;
return true;
default:
return false;
}
return true;
}
@Override
@ -389,7 +401,8 @@ public class PreferenceActivity extends android.preference.PreferenceActivity {
String key = preference.getKey();
ArrayList<String> prefValuesList = new ArrayList<String>(
Arrays.asList(UserPreferences
.getAutodownloadSelectedNetworks()));
.getAutodownloadSelectedNetworks())
);
boolean newValue = ((CheckBoxPreference) preference)
.isChecked();
if (BuildConfig.DEBUG)
@ -406,8 +419,9 @@ public class PreferenceActivity extends android.preference.PreferenceActivity {
UserPreferences.setAutodownloadSelectedNetworks(
PreferenceActivity.this, prefValuesList
.toArray(new String[prefValuesList
.size()]));
.toArray(new String[prefValuesList
.size()])
);
return true;
} else {
return false;
@ -461,7 +475,17 @@ public class PreferenceActivity extends android.preference.PreferenceActivity {
.setBackgroundDrawable(
this.getWindow().getDecorView()
.getBackground().getConstantState()
.newDrawable());
.newDrawable()
);
return false;
}
@Override
public void onBackPressed() {
// The default back button behavior has to be overwritten because changing the theme clears the back stack
Intent destIntent = new Intent(this, MainActivity.class);
destIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(destIntent);
finish();
}
}

View File

@ -0,0 +1,57 @@
package de.danoeh.antennapod.adapter;
import android.content.res.Resources;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.feed.FeedItem;
import de.danoeh.antennapod.feed.FeedMedia;
import de.danoeh.antennapod.util.Converter;
/**
* Utility methods for adapters
*/
public class AdapterUtils {
private AdapterUtils() {
}
/**
* Updates the contents of the TextView that shows the current playback position and the ProgressBar.
*/
public static void updateEpisodePlaybackProgress(FeedItem item, Resources res, TextView txtvPos, ProgressBar episodeProgress) {
FeedMedia media = item.getMedia();
episodeProgress.setVisibility(View.GONE);
if (media == null) {
txtvPos.setVisibility(View.GONE);
return;
} else {
txtvPos.setVisibility(View.VISIBLE);
}
FeedItem.State state = item.getState();
if (state == FeedItem.State.PLAYING
|| state == FeedItem.State.IN_PROGRESS) {
if (media.getDuration() > 0) {
episodeProgress.setVisibility(View.VISIBLE);
episodeProgress
.setProgress((int) (((double) media
.getPosition()) / media.getDuration() * 100));
txtvPos.setText(Converter
.getDurationStringLong(media.getDuration()
- media.getPosition()));
}
} else if (!media.isDownloaded()) {
txtvPos.setText(res.getString(
R.string.size_prefix)
+ Converter.byteToString(media.getSize()));
} else {
txtvPos.setText(res.getString(
R.string.length_prefix)
+ Converter.getDurationStringLong(media
.getDuration()));
}
}
}

View File

@ -134,28 +134,8 @@ public class FeedItemlistAdapter extends BaseAdapter {
holder.lenSize.setVisibility(View.INVISIBLE);
} else {
if (state == FeedItem.State.PLAYING
|| state == FeedItem.State.IN_PROGRESS) {
if (media.getDuration() > 0) {
holder.episodeProgress
.setProgress((int) (((double) media
.getPosition()) / media.getDuration() * 100));
holder.lenSize.setText(Converter
.getDurationStringLong(media.getDuration()
- media.getPosition()));
}
} else if (!media.isDownloaded()) {
holder.lenSize.setText(context.getString(
R.string.size_prefix)
+ Converter.byteToString(media.getSize()));
} else {
holder.lenSize.setText(context.getString(
R.string.length_prefix)
+ Converter.getDurationStringLong(media
.getDuration()));
}
AdapterUtils.updateEpisodePlaybackProgress(item, context.getResources(), holder.lenSize, holder.episodeProgress);
holder.lenSize.setVisibility(View.VISIBLE);
if (((ItemAccess) itemAccess).isInQueue(item)) {
holder.inPlaylist.setVisibility(View.VISIBLE);
} else {
@ -166,9 +146,6 @@ public class FeedItemlistAdapter extends BaseAdapter {
item.getMedia())) {
holder.episodeProgress.setVisibility(View.VISIBLE);
holder.episodeProgress.setProgress(((ItemAccess) itemAccess).getItemDownloadProgressPercent(item));
} else if (!(state == FeedItem.State.IN_PROGRESS
|| state == FeedItem.State.PLAYING)) {
holder.episodeProgress.setVisibility(View.GONE);
}
TypedArray typeDrawables = context.obtainStyledAttributes(

View File

@ -1,174 +0,0 @@
package de.danoeh.antennapod.adapter;
import android.content.Context;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.ImageLoader;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.storage.DownloadRequester;
import de.danoeh.antennapod.storage.FeedItemStatistics;
import de.danoeh.antennapod.util.ThemeUtils;
public class FeedlistAdapter extends BaseAdapter {
private static final String TAG = "FeedlistAdapter";
private Context context;
protected ItemAccess itemAccess;
private int selectedItemIndex;
private ImageLoader imageLoader;
public static final int SELECTION_NONE = -1;
public FeedlistAdapter(Context context, ItemAccess itemAccess) {
super();
if (context == null) {
throw new IllegalArgumentException("context must not be null");
}
if (itemAccess == null) {
throw new IllegalArgumentException("itemAccess must not be null");
}
this.context = context;
this.itemAccess = itemAccess;
selectedItemIndex = SELECTION_NONE;
imageLoader = ImageLoader.getInstance();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final Holder holder;
final Feed feed = getItem(position);
final FeedItemStatistics feedItemStatistics = itemAccess.getFeedItemStatistics(position);
// Inflate Layout
if (convertView == null) {
holder = new Holder();
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.feedlist_item, null);
holder.title = (TextView) convertView
.findViewById(R.id.txtvFeedname);
holder.newEpisodes = (TextView) convertView
.findViewById(R.id.txtvNewEps);
holder.inProgressEpisodes = (TextView) convertView
.findViewById(R.id.txtvProgressEps);
holder.newEpisodesLabel = (View) convertView
.findViewById(R.id.lNewStatusLabel);
holder.inProgressEpisodesLabel = (View) convertView
.findViewById(R.id.lProgressStatusLabel);
holder.image = (ImageView) convertView
.findViewById(R.id.imgvFeedimage);
holder.lastUpdate = (TextView) convertView
.findViewById(R.id.txtvLastUpdate);
holder.numberOfEpisodes = (TextView) convertView
.findViewById(R.id.txtvNumEpisodes);
convertView.setTag(holder);
} else {
holder = (Holder) convertView.getTag();
}
if (position == selectedItemIndex) {
convertView.setBackgroundColor(convertView.getResources().getColor(
ThemeUtils.getSelectionBackgroundColor()));
} else {
convertView.setBackgroundResource(0);
}
holder.title.setText(feed.getTitle());
if (feedItemStatistics != null) {
if (DownloadRequester.getInstance().isDownloadingFile(feed)) {
holder.lastUpdate.setText(R.string.refreshing_label);
} else {
if (feedItemStatistics.lastUpdateKnown()) {
holder.lastUpdate.setText(convertView.getResources().getString(
R.string.most_recent_prefix)
+ DateUtils.getRelativeTimeSpanString(
feedItemStatistics.getLastUpdate().getTime(),
System.currentTimeMillis(), 0, 0));
} else {
holder.lastUpdate.setText("");
}
}
holder.numberOfEpisodes.setText(feedItemStatistics.getNumberOfItems()
+ convertView.getResources()
.getString(R.string.episodes_suffix));
if (feedItemStatistics.getNumberOfNewItems() > 0) {
holder.newEpisodes.setText(Integer.toString(feedItemStatistics.getNumberOfNewItems()));
holder.newEpisodesLabel.setVisibility(View.VISIBLE);
} else {
holder.newEpisodesLabel.setVisibility(View.INVISIBLE);
}
if (feedItemStatistics.getNumberOfInProgressItems() > 0) {
holder.inProgressEpisodes
.setText(Integer.toString(feedItemStatistics.getNumberOfInProgressItems()));
holder.inProgressEpisodesLabel.setVisibility(View.VISIBLE);
} else {
holder.inProgressEpisodesLabel.setVisibility(View.INVISIBLE);
}
}
final String imageUrl = (feed.getImage() != null && feed.getImage().isDownloaded()) ? feed.getImage()
.getFile_url() : null;
imageLoader.loadThumbnailBitmap(
feed.getImage(),
holder.image,
(int) convertView.getResources().getDimension(
R.dimen.thumbnail_length));
return convertView;
}
static class Holder {
TextView title;
TextView lastUpdate;
TextView numberOfEpisodes;
TextView newEpisodes;
TextView inProgressEpisodes;
ImageView image;
View newEpisodesLabel;
View inProgressEpisodesLabel;
}
public int getSelectedItemIndex() {
return selectedItemIndex;
}
public void setSelectedItemIndex(int selectedItemIndex) {
this.selectedItemIndex = selectedItemIndex;
notifyDataSetChanged();
}
@Override
public int getCount() {
return itemAccess.getCount();
}
@Override
public Feed getItem(int position) {
return itemAccess.getItem(position);
}
@Override
public long getItemId(int position) {
return position;
}
public interface ItemAccess {
int getCount();
Feed getItem(int position);
FeedItemStatistics getFeedItemStatistics(int position);
}
}

View File

@ -36,9 +36,9 @@ public class NavListAdapter extends BaseAdapter {
this.itemAccess = itemAccess;
this.context = context;
TypedArray ta = context.obtainStyledAttributes(new int[] {R.attr.ic_new, R.attr.stat_playlist,
TypedArray ta = context.obtainStyledAttributes(new int[]{R.attr.ic_new, R.attr.stat_playlist,
R.attr.av_download, R.attr.device_access_time, R.attr.content_new});
drawables = new Drawable[] {ta.getDrawable(0), ta.getDrawable(1), ta.getDrawable(2),
drawables = new Drawable[]{ta.getDrawable(0), ta.getDrawable(1), ta.getDrawable(2),
ta.getDrawable(3), ta.getDrawable(4)};
ta.recycle();
}
@ -113,6 +113,7 @@ public class NavListAdapter extends BaseAdapter {
convertView = inflater.inflate(R.layout.nav_listitem, null);
holder.title = (TextView) convertView.findViewById(R.id.txtvTitle);
holder.count = (TextView) convertView.findViewById(R.id.txtvCount);
holder.image = (ImageView) convertView.findViewById(R.id.imgvCover);
convertView.setTag(holder);
} else {
@ -120,6 +121,27 @@ public class NavListAdapter extends BaseAdapter {
}
holder.title.setText(title);
if (NAV_TITLES[position] == R.string.queue_label) {
int queueSize = itemAccess.getQueueSize();
if (queueSize > 0) {
holder.count.setVisibility(View.VISIBLE);
holder.count.setText(String.valueOf(queueSize));
} else {
holder.count.setVisibility(View.GONE);
}
} else if (NAV_TITLES[position] == R.string.all_episodes_label) {
int unreadItems = itemAccess.getNumberOfUnreadItems();
if (unreadItems > 0) {
holder.count.setVisibility(View.VISIBLE);
holder.count.setText(String.valueOf(unreadItems));
} else {
holder.count.setVisibility(View.GONE);
}
} else {
holder.count.setVisibility(View.GONE);
}
holder.image.setImageDrawable(drawables[position]);
return convertView;
@ -174,6 +196,7 @@ public class NavListAdapter extends BaseAdapter {
static class NavHolder {
TextView title;
TextView count;
ImageView image;
}
@ -193,6 +216,10 @@ public class NavListAdapter extends BaseAdapter {
public Feed getItem(int position);
public int getSelectedItemIndex();
public int getQueueSize();
public int getNumberOfUnreadItems();
}
}

View File

@ -66,6 +66,7 @@ public class NewEpisodesListAdapter extends BaseAdapter {
holder.title = (TextView) convertView.findViewById(R.id.txtvTitle);
holder.pubDate = (TextView) convertView
.findViewById(R.id.txtvPublished);
holder.statusUnread = convertView.findViewById(R.id.statusUnread);
holder.butSecondary = (ImageButton) convertView
.findViewById(R.id.butSecondaryAction);
holder.queueStatus = (ImageView) convertView
@ -81,6 +82,11 @@ public class NewEpisodesListAdapter extends BaseAdapter {
holder.title.setText(item.getTitle());
holder.pubDate.setText(DateUtils.formatDateTime(context, item.getPubDate().getTime(), DateUtils.FORMAT_SHOW_DATE));
if (item.isRead()) {
holder.statusUnread.setVisibility(View.GONE);
} else {
holder.statusUnread.setVisibility(View.VISIBLE);
}
FeedMedia media = item.getMedia();
if (media != null) {
@ -140,6 +146,7 @@ public class NewEpisodesListAdapter extends BaseAdapter {
static class Holder {
TextView title;
TextView pubDate;
View statusUnread;
ImageView queueStatus;
ImageView imageView;
ProgressBar downloadProgress;

View File

@ -61,7 +61,8 @@ public class QueueListAdapter extends BaseAdapter {
holder.title = (TextView) convertView.findViewById(R.id.txtvTitle);
holder.butSecondary = (ImageButton) convertView
.findViewById(R.id.butSecondaryAction);
holder.downloadProgress = (ProgressBar) convertView
holder.position = (TextView) convertView.findViewById(R.id.txtvPosition);
holder.progress = (ProgressBar) convertView
.findViewById(R.id.pbar_download_progress);
holder.imageView = (ImageView) convertView.findViewById(R.id.imgvImage);
convertView.setTag(holder);
@ -71,20 +72,17 @@ public class QueueListAdapter extends BaseAdapter {
holder.title.setText(item.getTitle());
AdapterUtils.updateEpisodePlaybackProgress(item, context.getResources(), holder.position, holder.progress);
FeedMedia media = item.getMedia();
if (media != null) {
final boolean isDownloadingMedia = DownloadRequester.getInstance().isDownloadingFile(media);
if (isDownloadingMedia) {
holder.downloadProgress.setVisibility(View.VISIBLE);
} else {
holder.downloadProgress.setVisibility(View.GONE);
}
if (!media.isDownloaded()) {
if (isDownloadingMedia) {
// item is being downloaded
holder.downloadProgress.setProgress(itemAccess.getItemDownloadProgressPercent(item));
holder.progress.setVisibility(View.VISIBLE);
holder.progress.setProgress(itemAccess.getItemDownloadProgressPercent(item));
}
}
}
@ -116,7 +114,8 @@ public class QueueListAdapter extends BaseAdapter {
static class Holder {
TextView title;
ImageView imageView;
ProgressBar downloadProgress;
TextView position;
ProgressBar progress;
ImageButton butSecondary;
}

View File

@ -189,6 +189,10 @@ public class ImageDiskCache {
* The image will be stored in the thumbnail cache.
*/
public void loadThumbnailBitmap(final String url, final ImageView target, final int length) {
if (url == null) {
Log.w(TAG, "loadThumbnailBitmap: Call was ignored because url = null");
return;
}
final ImageLoader il = ImageLoader.getInstance();
target.setTag(R.id.image_disk_cache_key, url);
if (diskCache != null) {
@ -217,6 +221,10 @@ public class ImageDiskCache {
* The image will be stored in the cover cache.
*/
public void loadCoverBitmap(final String url, final ImageView target, final int length) {
if (url == null) {
Log.w(TAG, "loadCoverBitmap: Call was ignored because url = null");
return;
}
final ImageLoader il = ImageLoader.getInstance();
target.setTag(R.id.image_disk_cache_key, url);
if (diskCache != null) {

View File

@ -7,7 +7,6 @@ import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.SearchView;
import android.view.*;
@ -55,7 +54,6 @@ public class NewEpisodesFragment extends Fragment {
private DragSortListView listView;
private SwipeRefreshLayout swipeRefreshLayout;
private NewEpisodesListAdapter listAdapter;
private TextView txtvEmpty;
private ProgressBar progLoading;
@ -202,7 +200,6 @@ public class NewEpisodesFragment extends Fragment {
View root = inflater.inflate(R.layout.new_episodes_fragment, container, false);
swipeRefreshLayout = (SwipeRefreshLayout) root.findViewById(R.id.swipeRefreshLayout);
listView = (DragSortListView) root.findViewById(android.R.id.list);
txtvEmpty = (TextView) root.findViewById(android.R.id.empty);
progLoading = (ProgressBar) root.findViewById(R.id.progLoading);
@ -220,16 +217,6 @@ public class NewEpisodesFragment extends Fragment {
});
final int secondColor = (UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark) ? R.color.swipe_refresh_secondary_color_dark : R.color.swipe_refresh_secondary_color_light;
swipeRefreshLayout.setColorScheme(R.color.bright_blue, secondColor, R.color.bright_blue, secondColor);
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
List<Feed> feeds = ((MainActivity) getActivity()).getFeeds();
if (feeds != null) {
DBTasks.refreshAllFeeds(getActivity(), feeds);
}
}
});
if (!itemsLoaded) {
progLoading.setVisibility(View.VISIBLE);
@ -331,18 +318,9 @@ public class NewEpisodesFragment extends Fragment {
if (!viewsCreated) {
return;
}
if (DownloadService.isRunning
&& DownloadRequester.getInstance().isDownloadingFeeds()) {
swipeRefreshLayout.setRefreshing(true);
} else {
swipeRefreshLayout.setRefreshing(false);
// if case other fragments have set this to true, this fragment should remove the progress indicator
((ActionBarActivity) getActivity())
.setSupportProgressBarIndeterminateVisibility(false);
}
((ActionBarActivity) getActivity())
.setSupportProgressBarIndeterminateVisibility(DownloadService.isRunning
&& DownloadRequester.getInstance().isDownloadingFeeds());
}
private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {

View File

@ -890,6 +890,7 @@ public class DownloadService extends Service {
if (successful) {
return savedFeed;
} else {
numberOfDownloads.decrementAndGet();
saveDownloadStatus(new DownloadStatus(savedFeed,
savedFeed.getHumanReadableIdentifier(), reason, successful,
reasonDetailed));

View File

@ -541,6 +541,7 @@ public class PlaybackService extends Service {
(nextMedia.getMediaType() == MediaType.VIDEO) ? EXTRA_CODE_VIDEO : EXTRA_CODE_AUDIO);
} else {
sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0);
mediaPlayer.stop();
//stopSelf();
}
}

View File

@ -77,7 +77,8 @@ public class PlaybackServiceMediaPlayer {
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
if (BuildConfig.DEBUG) Log.d(TAG, "Rejected execution of runnable");
}
});
}
);
mediaPlayer = null;
statusBeforeSeeking = null;
@ -150,6 +151,8 @@ public class PlaybackServiceMediaPlayer {
if (media != null) {
if (!forceReset && media.getIdentifier().equals(playable.getIdentifier())) {
// episode is already playing -> ignore method call
if (BuildConfig.DEBUG)
Log.d(TAG, "Method call to playMediaObject was ignored: media file already playing.");
return;
} else {
// stop playback of this episode
@ -284,7 +287,8 @@ public class PlaybackServiceMediaPlayer {
reinit();
}
} else {
if (BuildConfig.DEBUG) Log.d(TAG, "Ignoring call to pause: Player is in " + playerStatus + " state");
if (BuildConfig.DEBUG)
Log.d(TAG, "Ignoring call to pause: Player is in " + playerStatus + " state");
}
playerLock.unlock();
@ -385,9 +389,10 @@ public class PlaybackServiceMediaPlayer {
/**
* Seeks to the specified position. If the PSMP object is in an invalid state, this method will do nothing.
*
* @param t The position to seek to in milliseconds. t < 0 will be interpreted as t = 0
* <p/>
* This method is executed on the caller's thread.
* <p/>
* This method is executed on the caller's thread.
*/
private void seekToSync(int t) {
if (t < 0) {
@ -757,6 +762,29 @@ public class PlaybackServiceMediaPlayer {
});
}
/**
* Moves the PlaybackServiceMediaPlayer into STOPPED state. This call is only valid if the player is currently in
* INDETERMINATE state, for example after a call to endPlayback.
* This method will only take care of changing the PlayerStatus of this object! Other tasks like
* abandoning audio focus have to be done with other methods.
*/
public void stop() {
executor.submit(new Runnable() {
@Override
public void run() {
playerLock.lock();
if (playerStatus == PlayerStatus.INDETERMINATE) {
setPlayerStatus(PlayerStatus.STOPPED, null);
} else {
if (BuildConfig.DEBUG) Log.d(TAG, "Ignored call to stop: Current player state is: " + playerStatus);
}
playerLock.unlock();
}
});
}
/**
* Holds information about a PSMP object.
*/

View File

@ -56,6 +56,14 @@ public final class DBReader {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
List<Feed> result = getFeedList(adapter);
adapter.close();
return result;
}
private static List<Feed> getFeedList(PodDBAdapter adapter) {
if (BuildConfig.DEBUG)
Log.d(TAG, "Extracting Feedlist");
Cursor feedlistCursor = adapter.getAllFeedsCursor();
List<Feed> feeds = new ArrayList<Feed>(feedlistCursor.getCount());
@ -509,8 +517,8 @@ public final class DBReader {
* Loads a list of FeedItems sorted by pubDate in descending order.
*
* @param context A context that is used for opening a database connection.
* @param limit The maximum number of episodes that should be loaded.
* */
* @param limit The maximum number of episodes that should be loaded.
*/
public static List<FeedItem> getRecentlyPublishedEpisodes(Context context, int limit) {
if (BuildConfig.DEBUG)
Log.d(TAG, "Extracting recently published items list");
@ -596,7 +604,8 @@ public final class DBReader {
.getString(PodDBAdapter.KEY_DOWNLOADSTATUS_TITLE_INDEX);
Date completionDate = new Date(
logCursor
.getLong(PodDBAdapter.KEY_COMPLETION_DATE_INDEX));
.getLong(PodDBAdapter.KEY_COMPLETION_DATE_INDEX)
);
downloadLog.add(new DownloadStatus(id, title, feedfileId,
feedfileType, successful, DownloadError.fromCode(reason), completionDate,
reasonDetailed));
@ -787,7 +796,8 @@ public final class DBReader {
cursor.getString(cursor
.getColumnIndex(PodDBAdapter.KEY_DOWNLOAD_URL)),
cursor.getInt(cursor
.getColumnIndex(PodDBAdapter.KEY_DOWNLOADED)) > 0);
.getColumnIndex(PodDBAdapter.KEY_DOWNLOADED)) > 0
);
cursor.close();
return image;
}
@ -865,4 +875,34 @@ public final class DBReader {
adapter.close();
return empty;
}
/**
* Returns data necessary for displaying the navigation drawer. This includes
* the list of subscriptions, the number of items in the queue and the number of unread
* items.
*
* @param context A context that is used for opening a database connection.
*/
public static NavDrawerData getNavDrawerData(Context context) {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
List<Feed> feeds = getFeedList(adapter);
int queueSize = adapter.getQueueSize();
int numUnreadItems = adapter.getNumberOfUnreadItems();
NavDrawerData result = new NavDrawerData(feeds, queueSize, numUnreadItems);
adapter.close();
return result;
}
public static class NavDrawerData {
public List<Feed> feeds;
public int queueSize;
public int numUnreadItems;
public NavDrawerData(List<Feed> feeds, int queueSize, int numUnreadItems) {
this.feeds = feeds;
this.queueSize = queueSize;
this.numUnreadItems = numUnreadItems;
}
}
}

View File

@ -1,6 +1,5 @@
package de.danoeh.antennapod.storage;
import android.app.backup.BackupManager;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@ -1107,6 +1106,17 @@ public class PodDBAdapter {
}
public int getQueueSize() {
final String query = String.format("SELECT COUNT(%s) FROM %s", KEY_ID, TABLE_NAME_QUEUE);
Cursor c = db.rawQuery(query, null);
int result = 0;
if (c.moveToFirst()) {
result = c.getInt(0);
}
c.close();
return result;
}
public final int getNumberOfUnreadItems() {
final String query = "SELECT COUNT(DISTINCT " + KEY_ID + ") AS count FROM " + TABLE_NAME_FEED_ITEMS +
" WHERE " + KEY_READ + " = 0";

View File

@ -22,7 +22,6 @@ public class HttpDownloaderTest extends InstrumentationTestCase {
private File destDir;
private HTTPBin httpServer;
private static final String BASE_URL = "http://127.0.0.1:" + HTTPBin.PORT;
public HttpDownloaderTest() {
super();
@ -79,15 +78,15 @@ public class HttpDownloaderTest extends InstrumentationTestCase {
}
private static final String URL_404 = BASE_URL + "/status/404";
private static final String URL_AUTH = BASE_URL + "/basic-auth/user/passwd";
private static final String URL_404 = HTTPBin.BASE_URL + "/status/404";
private static final String URL_AUTH = HTTPBin.BASE_URL + "/basic-auth/user/passwd";
public void testPassingHttp() {
download(BASE_URL + "/status/200", "test200", true);
download(HTTPBin.BASE_URL + "/status/200", "test200", true);
}
public void testRedirect() {
download(BASE_URL + "/redirect/4", "testRedirect", true);
download(HTTPBin.BASE_URL + "/redirect/4", "testRedirect", true);
}
public void testGzip() {
@ -99,7 +98,7 @@ public class HttpDownloaderTest extends InstrumentationTestCase {
}
public void testCancel() {
final String url = BASE_URL + "/delay/3";
final String url = HTTPBin.BASE_URL + "/delay/3";
FeedFileImpl feedFile = setupFeedFile(url, "delay", true);
final Downloader downloader = new HttpDownloader(new DownloadRequest(feedFile.getFile_url(), url, "delay", 0, feedFile.getTypeAsInt()));
Thread t = new Thread() {

View File

@ -365,4 +365,44 @@ public class DBReaderTest extends InstrumentationTestCase {
assertEquals("Wrong entry at index " + i, feeds.get(i).getId(), statistics.get(i).getFeedID());
}
}
public void testGetNavDrawerDataQueueEmptyNoUnreadItems() {
final Context context = getInstrumentation().getTargetContext();
final int NUM_FEEDS = 10;
final int NUM_ITEMS = 10;
List<Feed> feeds = DBTestUtils.saveFeedlist(context, NUM_FEEDS, NUM_ITEMS, true);
DBReader.NavDrawerData navDrawerData = DBReader.getNavDrawerData(context);
assertEquals(NUM_FEEDS, navDrawerData.feeds.size());
assertEquals(0, navDrawerData.numUnreadItems);
assertEquals(0, navDrawerData.queueSize);
}
public void testGetNavDrawerDataQueueNotEmptyWithUnreadItems() {
final Context context = getInstrumentation().getTargetContext();
final int NUM_FEEDS = 10;
final int NUM_ITEMS = 10;
final int NUM_QUEUE = 1;
final int NUM_UNREAD = 2;
List<Feed> feeds = DBTestUtils.saveFeedlist(context, NUM_FEEDS, NUM_ITEMS, true);
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
for (int i = 0; i < NUM_UNREAD; i++) {
FeedItem item = feeds.get(0).getItems().get(i);
item.setRead(false);
adapter.setSingleFeedItem(item);
}
List<FeedItem> queue = new ArrayList<FeedItem>();
for (int i = 0; i < NUM_QUEUE; i++) {
FeedItem item = feeds.get(1).getItems().get(i);
queue.add(item);
}
adapter.setQueue(queue);
adapter.close();
DBReader.NavDrawerData navDrawerData = DBReader.getNavDrawerData(context);
assertEquals(NUM_FEEDS, navDrawerData.feeds.size());
assertEquals(NUM_UNREAD, navDrawerData.numUnreadItems);
assertEquals(NUM_QUEUE, navDrawerData.queueSize);
}
}

View File

@ -0,0 +1,118 @@
package instrumentationTest.de.test.antennapod.ui;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.test.ActivityInstrumentationTestCase2;
import android.view.View;
import com.robotium.solo.Solo;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.DefaultOnlineFeedViewActivity;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.activity.PreferenceActivity;
import de.danoeh.antennapod.feed.EventDistributor;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.storage.PodDBAdapter;
/**
* User interface tests for MainActivity
*/
public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
private Solo solo;
private UITestUtils uiTestUtils;
public MainActivityTest() {
super(MainActivity.class);
}
@Override
protected void setUp() throws Exception {
super.setUp();
solo = new Solo(getInstrumentation(), getActivity());
uiTestUtils = new UITestUtils(getInstrumentation().getTargetContext());
uiTestUtils.setup();
// create database
PodDBAdapter adapter = new PodDBAdapter(getInstrumentation().getTargetContext());
adapter.open();
adapter.close();
// override first launch preference
SharedPreferences prefs = getInstrumentation().getTargetContext().getSharedPreferences(MainActivity.PREF_NAME, Context.MODE_PRIVATE);
prefs.edit().putBoolean(MainActivity.PREF_IS_FIRST_LAUNCH, false).commit();
}
@Override
protected void tearDown() throws Exception {
uiTestUtils.tearDown();
solo.finishOpenedActivities();
PodDBAdapter.deleteDatabase(getInstrumentation().getTargetContext());
super.tearDown();
}
public void testAddFeed() throws Exception {
uiTestUtils.addHostedFeedData();
final Feed feed = uiTestUtils.hostedFeeds.get(0);
solo.setNavigationDrawer(Solo.OPENED);
solo.clickOnText(solo.getString(R.string.add_feed_label));
solo.enterText(0, feed.getDownload_url());
solo.clickOnButton(0);
solo.waitForActivity(DefaultOnlineFeedViewActivity.class);
solo.waitForView(R.id.butSubscribe);
assertEquals(solo.getString(R.string.subscribe_label), solo.getButton(0).getText().toString());
solo.clickOnButton(0);
solo.waitForText(solo.getString(R.string.subscribed_label));
}
public void testClickNavDrawer() throws Exception {
uiTestUtils.addLocalFeedData(false);
final View home = solo.getView(android.R.id.home);
// all episodes
solo.waitForView(android.R.id.list);
assertEquals(solo.getString(R.string.all_episodes_label), getActionbarTitle());
// queue
solo.clickOnView(home);
solo.clickOnText(solo.getString(R.string.queue_label));
solo.waitForView(android.R.id.list);
assertEquals(solo.getString(R.string.queue_label), getActionbarTitle());
// downloads
solo.clickOnView(home);
solo.clickOnText(solo.getString(R.string.downloads_label));
solo.waitForView(android.R.id.list);
assertEquals(solo.getString(R.string.downloads_label), getActionbarTitle());
// playback history
solo.clickOnView(home);
solo.clickOnText(solo.getString(R.string.playback_history_label));
solo.waitForView(android.R.id.list);
assertEquals(solo.getString(R.string.playback_history_label), getActionbarTitle());
// add podcast
solo.clickOnView(home);
solo.clickOnText(solo.getString(R.string.add_feed_label));
solo.waitForView(R.id.txtvFeedurl);
assertEquals(solo.getString(R.string.add_feed_label), getActionbarTitle());
// podcasts
for (int i = 0; i < uiTestUtils.hostedFeeds.size(); i++) {
Feed f = uiTestUtils.hostedFeeds.get(i);
solo.clickOnView(home);
solo.clickOnText(f.getTitle());
solo.waitForView(android.R.id.list);
assertEquals("", getActionbarTitle());
}
}
private String getActionbarTitle() {
return ((MainActivity)solo.getCurrentActivity()).getMainActivtyActionBar().getTitle().toString();
}
public void testGoToPreferences() {
solo.setNavigationDrawer(Solo.CLOSED);
solo.clickOnMenuItem(solo.getString(R.string.settings_label));
solo.waitForActivity(PreferenceActivity.class);
}
}

View File

@ -0,0 +1,149 @@
package instrumentationTest.de.test.antennapod.ui;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.test.ActivityInstrumentationTestCase2;
import android.widget.TextView;
import com.robotium.solo.Solo;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.AudioplayerActivity;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.feed.FeedItem;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.service.playback.PlaybackService;
import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.storage.DBWriter;
import de.danoeh.antennapod.storage.PodDBAdapter;
import java.util.List;
/**
* Test cases for starting and ending playback from the MainActivity and AudioPlayerActivity
*/
public class PlaybackTest extends ActivityInstrumentationTestCase2<MainActivity> {
private Solo solo;
private UITestUtils uiTestUtils;
public PlaybackTest() {
super(MainActivity.class);
}
@Override
public void setUp() throws Exception {
super.setUp();
solo = new Solo(getInstrumentation(), getActivity());
uiTestUtils = new UITestUtils(getInstrumentation().getTargetContext());
uiTestUtils.setup();
// create database
PodDBAdapter adapter = new PodDBAdapter(getInstrumentation().getTargetContext());
adapter.open();
adapter.close();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getInstrumentation().getTargetContext());
prefs.edit().putBoolean(UserPreferences.PREF_PAUSE_ON_HEADSET_DISCONNECT, false).commit();
}
@Override
public void tearDown() throws Exception {
uiTestUtils.tearDown();
solo.finishOpenedActivities();
PodDBAdapter.deleteDatabase(getInstrumentation().getTargetContext());
// shut down playback service
skipEpisode();
getInstrumentation().getTargetContext().sendBroadcast(
new Intent(PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
super.tearDown();
}
private void setContinuousPlaybackPreference(boolean value) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getInstrumentation().getTargetContext());
prefs.edit().putBoolean(UserPreferences.PREF_FOLLOW_QUEUE, value).commit();
}
private void skipEpisode() {
Intent skipIntent = new Intent(PlaybackService.ACTION_SKIP_CURRENT_EPISODE);
getInstrumentation().getTargetContext().sendBroadcast(skipIntent);
}
private void startLocalPlayback() {
assertTrue(solo.waitForActivity(MainActivity.class));
solo.setNavigationDrawer(Solo.CLOSED);
solo.clickOnView(solo.getView(R.id.butSecondaryAction));
assertTrue(solo.waitForActivity(AudioplayerActivity.class));
assertTrue(solo.waitForView(solo.getView(R.id.butPlay)));
}
private void startLocalPlaybackFromQueue() {
assertTrue(solo.waitForActivity(MainActivity.class));
solo.clickOnView(solo.getView(android.R.id.home));
solo.clickOnText(solo.getString(R.string.queue_label));
assertTrue(solo.waitForView(solo.getView(R.id.butSecondaryAction)));
solo.clickOnImageButton(0);
assertTrue(solo.waitForActivity(AudioplayerActivity.class));
assertTrue(solo.waitForView(solo.getView(R.id.butPlay)));
}
public void testStartLocal() throws Exception {
uiTestUtils.addLocalFeedData(true);
DBWriter.clearQueue(getInstrumentation().getTargetContext()).get();
startLocalPlayback();
solo.clickOnView(solo.getView(R.id.butPlay));
}
public void testContinousPlaybackOffSingleEpisode() throws Exception {
setContinuousPlaybackPreference(false);
uiTestUtils.addLocalFeedData(true);
DBWriter.clearQueue(getInstrumentation().getTargetContext()).get();
startLocalPlayback();
assertTrue(solo.waitForActivity(MainActivity.class));
}
public void testContinousPlaybackOffMultipleEpisodes() throws Exception {
setContinuousPlaybackPreference(false);
uiTestUtils.addLocalFeedData(true);
List<FeedItem> queue = DBReader.getQueue(getInstrumentation().getTargetContext());
FeedItem second = queue.get(1);
startLocalPlaybackFromQueue();
assertTrue(solo.waitForText(second.getTitle()));
}
public void testContinuousPlaybackOnMultipleEpisodes() throws Exception {
setContinuousPlaybackPreference(true);
uiTestUtils.addLocalFeedData(true);
List<FeedItem> queue = DBReader.getQueue(getInstrumentation().getTargetContext());
FeedItem second = queue.get(1);
startLocalPlaybackFromQueue();
assertTrue(solo.waitForText(second.getTitle()));
}
/**
* Check if an episode can be played twice without problems.
*/
private void replayEpisodeCheck(boolean followQueue) throws Exception {
setContinuousPlaybackPreference(followQueue);
uiTestUtils.addLocalFeedData(true);
DBWriter.clearQueue(getInstrumentation().getTargetContext()).get();
String title = ((TextView) solo.getView(R.id.txtvTitle)).getText().toString();
startLocalPlayback();
assertTrue(solo.waitForText(title));
assertTrue(solo.waitForActivity(MainActivity.class));
startLocalPlayback();
assertTrue(solo.waitForText(title));
assertTrue(solo.waitForActivity(MainActivity.class));
}
public void testReplayEpisodeContinuousPlaybackOn() throws Exception {
replayEpisodeCheck(true);
}
public void testReplayEpisodeContinuousPlaybackOff() throws Exception {
replayEpisodeCheck(false);
}
}

View File

@ -0,0 +1,194 @@
package instrumentationTest.de.test.antennapod.ui;
import android.content.Context;
import android.graphics.Bitmap;
import de.danoeh.antennapod.feed.*;
import de.danoeh.antennapod.storage.DBWriter;
import de.danoeh.antennapod.storage.PodDBAdapter;
import instrumentationTest.de.test.antennapod.util.service.download.HTTPBin;
import instrumentationTest.de.test.antennapod.util.syndication.feedgenerator.RSS2Generator;
import junit.framework.Assert;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* Utility methods for UI tests.
* Starts a web server that hosts feeds, episodes and images.
*/
public class UITestUtils {
private static final String DATA_FOLDER = "test/UITestUtils";
public static final int NUM_FEEDS = 5;
public static final int NUM_ITEMS_PER_FEED = 10;
private Context context;
private HTTPBin server = new HTTPBin();
private File destDir;
private File hostedFeedDir;
private File hostedMediaDir;
public List<Feed> hostedFeeds = new ArrayList<Feed>();
public UITestUtils(Context context) {
this.context = context;
}
public void setup() throws IOException {
destDir = context.getExternalFilesDir(DATA_FOLDER);
destDir.mkdir();
hostedFeedDir = new File(destDir, "hostedFeeds");
hostedFeedDir.mkdir();
hostedMediaDir = new File(destDir, "hostedMediaDir");
hostedMediaDir.mkdir();
Assert.assertTrue(destDir.exists());
Assert.assertTrue(hostedFeedDir.exists());
Assert.assertTrue(hostedMediaDir.exists());
server.start();
}
public void tearDown() throws IOException {
FileUtils.deleteDirectory(destDir);
FileUtils.deleteDirectory(hostedMediaDir);
FileUtils.deleteDirectory(hostedFeedDir);
server.stop();
if (localFeedDataAdded) {
PodDBAdapter.deleteDatabase(context);
}
}
private String hostFeed(Feed feed) throws IOException {
File feedFile = new File(hostedFeedDir, feed.getTitle());
FileOutputStream out = new FileOutputStream(feedFile);
RSS2Generator generator = new RSS2Generator();
generator.writeFeed(feed, out, "UTF-8", 0);
out.close();
int id = server.serveFile(feedFile);
Assert.assertTrue(id != -1);
return String.format("%s/files/%d", HTTPBin.BASE_URL, id);
}
private String hostFile(File file) {
int id = server.serveFile(file);
Assert.assertTrue(id != -1);
return String.format("%s/files/%d", HTTPBin.BASE_URL, id);
}
private File newBitmapFile(String name) throws IOException {
File imgFile = new File(destDir, name);
Bitmap bitmap = Bitmap.createBitmap(128, 128, Bitmap.Config.ARGB_8888);
FileOutputStream out = new FileOutputStream(imgFile);
bitmap.compress(Bitmap.CompressFormat.PNG, 1, out);
out.close();
return imgFile;
}
private File newMediaFile(String name) throws IOException {
File mediaFile = new File(hostedMediaDir, name);
Assert.assertFalse(mediaFile.exists());
InputStream in = context.getAssets().open("testfile.mp3");
Assert.assertNotNull(in);
FileOutputStream out = new FileOutputStream(mediaFile);
IOUtils.copy(in, out);
out.close();
return mediaFile;
}
private boolean feedDataHosted = false;
/**
* Adds feeds, images and episodes to the webserver for testing purposes.
*/
public void addHostedFeedData() throws IOException {
if (feedDataHosted) throw new IllegalStateException("addHostedFeedData was called twice on the same instance");
for (int i = 0; i < NUM_FEEDS; i++) {
File bitmapFile = newBitmapFile("image" + i);
FeedImage image = new FeedImage(0, "image " + i, null, hostFile(bitmapFile), false);
Feed feed = new Feed(0, new Date(), "Title " + i, "http://example.com/" + i, "Description of feed " + i,
"http://example.com/pay/feed" + i, "author " + i, "en", Feed.TYPE_RSS2, "feed" + i, image, null,
"http://example.com/feed/src/" + i, false);
image.setOwner(feed);
// create items
List<FeedItem> items = new ArrayList<FeedItem>();
for (int j = 0; j < NUM_ITEMS_PER_FEED; j++) {
FeedItem item = new FeedItem(0, "item" + j, "item" + j, "http://example.com/feed" + i + "/item/" + j, new Date(), true, feed);
items.add(item);
File mediaFile = newMediaFile("feed-" + i + "-episode-" + j + ".mp3");
item.setMedia(new FeedMedia(0, item, 0, 0, mediaFile.length(), "audio/mp3", null, hostFile(mediaFile), false, null, 0));
}
feed.setItems(items);
feed.setDownload_url(hostFeed(feed));
hostedFeeds.add(feed);
}
feedDataHosted = true;
}
private boolean localFeedDataAdded = false;
/**
* Adds feeds, images and episodes to the local database. This method will also call addHostedFeedData if it has not
* been called yet.
*
* Adds one item of each feed to the queue and to the playback history.
*
* This method should NOT be called if the testing class wants to download the hosted feed data.
*
* @param downloadEpisodes true if episodes should also be marked as downloaded.
*/
public void addLocalFeedData(boolean downloadEpisodes) throws Exception {
if (localFeedDataAdded) throw new IllegalStateException("addLocalFeedData was called twice on the same instance");
if (!feedDataHosted) {
addHostedFeedData();
}
List<FeedItem> queue = new ArrayList<FeedItem>();
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
for (Feed feed : hostedFeeds) {
feed.setDownloaded(true);
if (feed.getImage() != null) {
FeedImage image = feed.getImage();
image.setFile_url(image.getDownload_url());
image.setDownloaded(true);
}
if (downloadEpisodes) {
for (FeedItem item : feed.getItems()) {
if (item.hasMedia()) {
FeedMedia media = item.getMedia();
int fileId = Integer.parseInt(StringUtils.substringAfter(media.getDownload_url(), "files/"));
media.setFile_url(server.accessFile(fileId).getAbsolutePath());
media.setDownloaded(true);
}
}
}
queue.add(feed.getItems().get(0));
feed.getItems().get(1).getMedia().setPlaybackCompletionDate(new Date());
}
adapter.setCompleteFeed(hostedFeeds.toArray(new Feed[hostedFeeds.size()]));
adapter.setQueue(queue);
adapter.close();
EventDistributor.getInstance().sendFeedUpdateBroadcast();
EventDistributor.getInstance().sendQueueUpdateBroadcast();
}
}

View File

@ -0,0 +1,94 @@
package instrumentationTest.de.test.antennapod.ui;
import android.test.InstrumentationTestCase;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedItem;
import org.apache.http.HttpStatus;
import java.io.File;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
/**
* Test for the UITestUtils. Makes sure that all URLs are reachable and that the class does not cause any crashes.
*/
public class UITestUtilsTest extends InstrumentationTestCase {
private UITestUtils uiTestUtils;
@Override
protected void setUp() throws Exception {
super.setUp();
uiTestUtils = new UITestUtils(getInstrumentation().getTargetContext());
uiTestUtils.setup();
}
@Override
public void tearDown() throws Exception {
super.tearDown();
uiTestUtils.tearDown();
}
public void testAddHostedFeeds() throws Exception {
uiTestUtils.addHostedFeedData();
final List<Feed> feeds = uiTestUtils.hostedFeeds;
assertNotNull(feeds);
assertFalse(feeds.isEmpty());
for (Feed feed : feeds) {
testUrlReachable(feed.getDownload_url());
if (feed.getImage() != null) {
testUrlReachable(feed.getImage().getDownload_url());
}
for (FeedItem item : feed.getItems()) {
if (item.hasMedia()) {
testUrlReachable(item.getMedia().getDownload_url());
}
}
}
}
private void testUrlReachable(String strUtl) throws Exception {
URL url = new URL(strUtl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.connect();
int rc = conn.getResponseCode();
assertEquals(HttpStatus.SC_OK, rc);
conn.disconnect();
}
private void addLocalFeedDataCheck(boolean downloadEpisodes) throws Exception {
uiTestUtils.addLocalFeedData(downloadEpisodes);
assertNotNull(uiTestUtils.hostedFeeds);
assertFalse(uiTestUtils.hostedFeeds.isEmpty());
for (Feed feed : uiTestUtils.hostedFeeds) {
assertTrue(feed.getId() != 0);
if (feed.getImage() != null) {
assertTrue(feed.getImage().getId() != 0);
}
for (FeedItem item : feed.getItems()) {
assertTrue(item.getId() != 0);
if (item.hasMedia()) {
assertTrue(item.getMedia().getId() != 0);
if (downloadEpisodes) {
assertTrue(item.getMedia().isDownloaded());
assertNotNull(item.getMedia().getFile_url());
File file = new File(item.getMedia().getFile_url());
assertTrue(file.exists());
}
}
}
}
}
public void testAddLocalFeedDataNoDownload() throws Exception {
addLocalFeedDataCheck(false);
}
public void testAddLocalFeedDataDownload() throws Exception {
addLocalFeedDataCheck(true);
}
}

View File

@ -26,6 +26,8 @@ import java.util.zip.GZIPOutputStream;
public class HTTPBin extends NanoHTTPD {
private static final String TAG = "HTTPBin";
public static final int PORT = 8124;
public static final String BASE_URL = "http://127.0.0.1:" + HTTPBin.PORT;
private static final String MIME_HTML = "text/html";
private static final String MIME_PLAIN = "text/plain";
@ -70,7 +72,7 @@ public class HTTPBin extends NanoHTTPD {
}
}
private synchronized File accessFile(int id) {
public synchronized File accessFile(int id) {
if (id < 0 || id >= servedFiles.size()) {
return null;
} else {