diff options
64 files changed, 2395 insertions, 280 deletions
diff --git a/subsonic-android/AndroidManifest.xml b/subsonic-android/AndroidManifest.xml index 43cff05f..76677dbe 100644 --- a/subsonic-android/AndroidManifest.xml +++ b/subsonic-android/AndroidManifest.xml @@ -2,8 +2,8 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="github.daneren2005.dsub"
android:installLocation="internalOnly"
- android:versionCode="48"
- android:versionName="4.0.0">
+ android:versionCode="52"
+ android:versionName="4.0.3">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
diff --git a/subsonic-android/res/drawable-hdpi-v4/ic_menu_chat_dark.png b/subsonic-android/res/drawable-hdpi-v4/ic_menu_chat_dark.png Binary files differnew file mode 100644 index 00000000..be04b06e --- /dev/null +++ b/subsonic-android/res/drawable-hdpi-v4/ic_menu_chat_dark.png diff --git a/subsonic-android/res/drawable-hdpi-v4/ic_menu_chat_light.png b/subsonic-android/res/drawable-hdpi-v4/ic_menu_chat_light.png Binary files differnew file mode 100644 index 00000000..3f58695c --- /dev/null +++ b/subsonic-android/res/drawable-hdpi-v4/ic_menu_chat_light.png diff --git a/subsonic-android/res/drawable-hdpi-v4/ic_menu_chat_send_dark.png b/subsonic-android/res/drawable-hdpi-v4/ic_menu_chat_send_dark.png Binary files differnew file mode 100644 index 00000000..bd37dc59 --- /dev/null +++ b/subsonic-android/res/drawable-hdpi-v4/ic_menu_chat_send_dark.png diff --git a/subsonic-android/res/drawable-hdpi-v4/ic_menu_chat_send_light.png b/subsonic-android/res/drawable-hdpi-v4/ic_menu_chat_send_light.png Binary files differnew file mode 100644 index 00000000..0c870d2c --- /dev/null +++ b/subsonic-android/res/drawable-hdpi-v4/ic_menu_chat_send_light.png diff --git a/subsonic-android/res/layout/chat.xml b/subsonic-android/res/layout/chat.xml new file mode 100644 index 00000000..fdeb5b36 --- /dev/null +++ b/subsonic-android/res/layout/chat.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical" > + + <include layout="@layout/tab_progress" /> + + <ListView + android:id="@+id/chat_entries" + android:layout_width="fill_parent" + android:layout_height="0dip" + android:layout_weight="1.0" + android:textFilterEnabled="true" /> + + <LinearLayout + android:layout_height="4dip" + android:layout_width="fill_parent" + android:layout_marginTop="4dip"/> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:gravity="bottom" > + + <EditText + android:id="@+id/chat_edittext" + android:layout_width="0dip" + android:layout_height="40dip" + android:layout_weight="1" + android:autoLink="all" + android:hint="@string/chat.send_a_message" + android:inputType="textEmailAddress|textMultiLine" + android:linksClickable="true" + android:paddingBottom="10dip" + android:paddingTop="10dip" /> + + <ImageButton + android:id="@+id/chat_send" + android:layout_width="60dip" + android:layout_height="40dip" + android:src="?attr/chat_send" /> + + </LinearLayout> +</LinearLayout>
\ No newline at end of file diff --git a/subsonic-android/res/layout/chat_item.xml b/subsonic-android/res/layout/chat_item.xml new file mode 100644 index 00000000..b44631d1 --- /dev/null +++ b/subsonic-android/res/layout/chat_item.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="vertical" > + + <TextView + android:id="@+id/chat_username" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="6dip" + android:layout_marginRight="6dip" + android:ellipsize="marquee" + android:singleLine="true" + android:text="User" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textColor="?android:textColorSecondary"/> + + <LinearLayout + android:id="@+id/chat_message_layout" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="2dip" + android:orientation="horizontal" > + + <TextView + android:id="@+id/chat_time" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="6dip" + android:singleLine="true" + android:text="00:00" + android:textAppearance="?android:attr/textAppearanceMedium" /> + + <TextView + android:id="@+id/chat_message" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="6dip" + android:layout_marginRight="6dip" + android:autoLink="all" + android:linksClickable="true" + android:singleLine="false" + android:text="Message Text Goes Here" + android:textAppearance="?android:attr/textAppearanceMedium" /> + </LinearLayout> +</LinearLayout>
\ No newline at end of file diff --git a/subsonic-android/res/layout/chat_item_reverse.xml b/subsonic-android/res/layout/chat_item_reverse.xml new file mode 100644 index 00000000..62695521 --- /dev/null +++ b/subsonic-android/res/layout/chat_item_reverse.xml @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="vertical" > + + <TextView + android:id="@+id/chat_username" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginRight="6dip" + android:gravity="right" + android:layout_gravity="right" + android:ellipsize="marquee" + android:singleLine="true" + android:text="User" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textColor="?android:textColorSecondary"/> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="2dip" + android:orientation="horizontal" + android:layout_gravity="right" > + + <TextView + android:id="@+id/chat_time" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="6dip" + android:singleLine="true" + android:gravity="right" + android:text="00:00" + android:textAppearance="?android:attr/textAppearanceMedium" /> + + <TextView + android:id="@+id/chat_message" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="6dip" + android:layout_marginRight="6dip" + android:autoLink="all" + android:linksClickable="true" + android:singleLine="false" + android:gravity="right" + android:text="Chat message" + android:textAppearance="?android:attr/textAppearanceMedium" /> + </LinearLayout> +</LinearLayout>
\ No newline at end of file diff --git a/subsonic-android/res/layout/main.xml b/subsonic-android/res/layout/main.xml index fe79716c..a2a46a1e 100644 --- a/subsonic-android/res/layout/main.xml +++ b/subsonic-android/res/layout/main.xml @@ -25,10 +25,10 @@ <ImageView android:id="@+id/album_art" - android:layout_width="wrap_content" - android:layout_height="fill_parent" + android:layout_width="50dip" + android:layout_height="50dip" android:layout_gravity="left|center" - android:scaleType="fitXY" + android:scaleType="fitStart" android:src="@drawable/unknown_album"/> <LinearLayout diff --git a/subsonic-android/res/layout/main_buttons.xml b/subsonic-android/res/layout/main_buttons.xml index e8be57f0..1e60838d 100644 --- a/subsonic-android/res/layout/main_buttons.xml +++ b/subsonic-android/res/layout/main_buttons.xml @@ -128,6 +128,18 @@ android:paddingLeft="6dip"
android:paddingRight="6dip"
android:minHeight="50dip"/>
+ <TextView
+ android:id="@+id/main_albums_genres"
+ android:text="@string/main.albums_genres"
+ android:drawableRight="@drawable/list_item_more"
+ android:drawablePadding="6dip"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:gravity="center_vertical"
+ android:paddingLeft="6dip"
+ android:paddingRight="6dip"
+ android:minHeight="50dip"/>
<TextView
android:id="@+id/main_albums_random"
android:text="@string/main.albums_random"
diff --git a/subsonic-android/res/layout/select_genres.xml b/subsonic-android/res/layout/select_genres.xml new file mode 100644 index 00000000..95f9d415 --- /dev/null +++ b/subsonic-android/res/layout/select_genres.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/select_genre_layout" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical" > + + <include layout="@layout/tab_progress" /> + + <TextView + android:id="@+id/select_genre_empty" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:padding="10dip" + android:text="@string/select_genre.empty" + android:visibility="gone" /> + + <ListView + android:id="@+id/select_genre_list" + android:layout_width="fill_parent" + android:layout_height="0dip" + android:layout_weight="1.0" + android:textFilterEnabled="true" + android:fastScrollEnabled="true"/> + </LinearLayout> +</FrameLayout>
\ No newline at end of file diff --git a/subsonic-android/res/layout/shuffle_dialog.xml b/subsonic-android/res/layout/shuffle_dialog.xml index 2a21dc11..e78aba33 100644 --- a/subsonic-android/res/layout/shuffle_dialog.xml +++ b/subsonic-android/res/layout/shuffle_dialog.xml @@ -66,6 +66,14 @@ android:layout_height="wrap_content" android:layout_weight="1" android:layout_marginLeft="4dp" - android:hint="@string/shuffle.genre" /> + android:hint="@string/shuffle.genre"/> + + <Button + android:id="@+id/genre_combo" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_marginLeft="4dp" + android:text="@string/shuffle.genre"/> </LinearLayout> </LinearLayout>
\ No newline at end of file diff --git a/subsonic-android/res/menu/chat.xml b/subsonic-android/res/menu/chat.xml new file mode 100644 index 00000000..e0f9a718 --- /dev/null +++ b/subsonic-android/res/menu/chat.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:id="@+id/menu_refresh" + android:icon="@drawable/action_refresh" + android:title="@string/menu.refresh" + android:showAsAction="always|withText"/> + + <item + android:id="@+id/menu_settings" + android:icon="@drawable/action_settings" + android:title="@string/menu.settings"/> + + <item + android:id="@+id/menu_exit" + android:icon="@drawable/action_exit" + android:title="@string/menu.exit"/> +</menu>
\ No newline at end of file diff --git a/subsonic-android/res/menu/select_genres.xml b/subsonic-android/res/menu/select_genres.xml new file mode 100644 index 00000000..e0f9a718 --- /dev/null +++ b/subsonic-android/res/menu/select_genres.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:id="@+id/menu_refresh" + android:icon="@drawable/action_refresh" + android:title="@string/menu.refresh" + android:showAsAction="always|withText"/> + + <item + android:id="@+id/menu_settings" + android:icon="@drawable/action_settings" + android:title="@string/menu.settings"/> + + <item + android:id="@+id/menu_exit" + android:icon="@drawable/action_exit" + android:title="@string/menu.exit"/> +</menu>
\ No newline at end of file diff --git a/subsonic-android/res/raw/changelog.xml b/subsonic-android/res/raw/changelog.xml index 18636dda..14f59866 100644 --- a/subsonic-android/res/raw/changelog.xml +++ b/subsonic-android/res/raw/changelog.xml @@ -1,7 +1,36 @@ <?xml version="1.0" encoding="utf-8"?> <changelog> + <release version="4.0.3" versioncode="52" releasedate="5/31/2013"> + <change>Sort by disc number if specified in tags</change> + <change>Show starred artists in starred list</change> + <change>Change folder.jpg to albumart.jpg which galleries shouldn't display</change> + <change>Fix Show Album</change> + <change>Added support for server Ignored Articles (future server version) + defaults to server's defaults</change> + <change>On network error return to front of the app instead of exiting all the way</change> + <change>Fix occasional crash when going back into app after running for a while</change> + <change>Various minor bugfixes</change> + </release> + <release version="4.0.2" versioncode="51" releasedate="5/24/2013"> + <change>Fix if you set chat refresh rate to 0, will just not refresh</change> + <change>Revert dark theme modification</change> + <change>New Theme called black which is the pure black background</change> + <change>Option to disable chat menu, need to exit app and reenter for now</change> + </release> + + <release version="4.0.1" versioncode="50" releasedate="5/23/2013"> + <change>New: Chat Tab (Set chat auto refresh rate from settings)</change> + <change>New: Dynamic servers, add as many, or remove all but the ones you are using</change> + <change>New: Added separate setting for songs to preload for Wifi/Mobile</change> + <change>Improvement: The infinite playlist while shuffling is now persistent between startups</change> + <change>Theme: White is now more white, got rid of blue text for white theme only</change> + <change>Theme: Black is now a flat black due to popular request</change> + <change>Theme: Apply the current theme to settings screen</change> + <change>Fix: Don't stretch album art on bottom of main tabs</change> + <change>Fix: Possible fix for some who were having crash on starting EQ</change> + </release> + <release version="4.0.0" versioncode="48" releasedate="5/16/2013"> - <change>Converted everything to fragments!</change> + <change>Converted everything to fragments!</change> <change>Swipe to switch tabs</change> <change>Breadcrumb trail when going down several levels</change> <change>Require double tapping back to exit app</change> @@ -13,5 +42,5 @@ <change>Added total time to playlist/album headers</change> <change>Fixed a lot of the menu items not working when using search</change> <change>Update to Light/Dark themes</change> - </release> + </release> </changelog>
\ No newline at end of file diff --git a/subsonic-android/res/values-ru/strings.xml b/subsonic-android/res/values-ru/strings.xml index a74d7f61..73569d9d 100644 --- a/subsonic-android/res/values-ru/strings.xml +++ b/subsonic-android/res/values-ru/strings.xml @@ -307,6 +307,23 @@ <string name="util.bytes_format.kilobyte">0 КБ</string>
<string name="util.bytes_format.byte">0 Б</string>
+ <string name="button_bar.chat">Чат</string>
+ <string name="main.back_confirm">Нажмите "назад" еще раз для выхода</string>
+ <string name="download.playing_out_of">Воспроизведение: %1$d/%2$d</string>
+ <string name="settings.persistent_title">Постоянное уведомление</string>
+ <string name="settings.persistent_summary">Показывать уведомление даже во время паузы. Остановка воспроизведения уберет это уведомление.</string>
+ <string name="settings.gapless_playback">Непрерывное воспроизведение</string>
+ <string name="settings.gapless_playback_summary">Galaxy S3 может зависать или испытывать прочие трудности с момента начала непрерывного воспроизведения. Выключите эту функцию для исправления данной проблемы.</string>
+ <string name="settings.chat_refresh">Частота обновления чата (сек)</string>
+ <string name="settings.chat_enabled">Чат активен</string>
+ <string name="settings.chat_enabled_summary">Показывать или нет вкладку чата</string>
+ <string name="changelog_full_title">Журнал изменений</string>
+ <string name="changelog_title">Что нового</string>
+ <string name="changelog_ok_button">OK</string>
+ <string name="changelog_show_full">Еще…</string>
+ <string name="chat.send_a_message">Отправить сообщение</string>
+
+
<plurals name="select_album_n_songs">
<item quantity="zero">Нет композиций</item>
<item quantity="one">1 композиция</item>
diff --git a/subsonic-android/res/values/arrays.xml b/subsonic-android/res/values/arrays.xml index 75ad2bea..80cc4fad 100644 --- a/subsonic-android/res/values/arrays.xml +++ b/subsonic-android/res/values/arrays.xml @@ -4,18 +4,22 @@ <string-array name="themeValues"> <item>light</item> <item>dark</item> + <item>black</item> <item>holo</item> <item>light_fullscreen</item> <item>dark_fullscreen</item> + <item>black_fullscreen</item> <item>holo_fullscreen</item> </string-array> <string-array name="themeNames"> <item>@string/settings.theme_light</item> <item>@string/settings.theme_dark</item> + <item>@string/settings.theme_black</item> <item>@string/settings.theme_holo</item> <item>@string/settings.theme_light_fullscreen</item> <item>@string/settings.theme_dark_fullscreen</item> + <item>@string/settings.theme_black_fullscreen</item> <item>@string/settings.theme_holo_fullscreen</item> </string-array> diff --git a/subsonic-android/res/values/attrs.xml b/subsonic-android/res/values/attrs.xml index e5d8984f..8f669cd2 100644 --- a/subsonic-android/res/values/attrs.xml +++ b/subsonic-android/res/values/attrs.xml @@ -7,4 +7,6 @@ <attr name="media_button_repeat_off" format="reference"/> <attr name="media_button_start" format="reference"/> <attr name="media_button_stop" format="reference"/> + <attr name="chat" format="reference"/> + <attr name="chat_send" format="reference" /> </resources> diff --git a/subsonic-android/res/values/strings.xml b/subsonic-android/res/values/strings.xml index 7f03003e..b5e8f3e1 100644 --- a/subsonic-android/res/values/strings.xml +++ b/subsonic-android/res/values/strings.xml @@ -28,6 +28,7 @@ <string name="button_bar.search">Search</string>
<string name="button_bar.playlists">Playlists</string>
<string name="button_bar.now_playing">Playing</string>
+ <string name="button_bar.chat">Chat</string>
<string name="main.welcome_title">Welcome!</string>
<string name="main.welcome_text">Welcome to DSub! The app is currently configured to use the Subsonic demo server. After you\'ve
@@ -50,6 +51,7 @@ <string name="main.albums_highest">Top rated</string>
<string name="main.albums_starred">Starred</string>
<string name="main.albums_random">Random</string>
+ <string name="main.albums_genres">Genres</string>
<string name="main.back_confirm">Press back again to exit</string>
<string name="menu.search">Search</string>
@@ -113,6 +115,8 @@ <string name="select_album.donate_dialog_later">Later</string>
<string name="select_album.donate_dialog_0_trial_days_left">Trial period is over</string>
+ <string name="select_genre.empty">No genres found</string>
+
<string name="select_playlist.empty">No saved playlists on server</string>
<string name="download.empty">Playlist is empty</string>
@@ -175,15 +179,18 @@ <string name="settings.title">DSub settings</string>
<string name="settings.test_connection_title">Test connection</string>
+ <string name="settings.servers_add">Add Server</string>
+ <string name="settings.servers_remove">Remove Server</string>
<string name="settings.servers_title">Servers</string>
- <string name="settings.server_unused1">Unused 1</string>
- <string name="settings.server_unused2">Unused 2</string>
+ <string name="settings.server_unused">Unused</string>
<string name="settings.server_name">Name</string>
<string name="settings.server_address">Server address</string>
<string name="settings.server_username">Username</string>
<string name="settings.server_password">Password</string>
+ <string name="settings.server_open_browser">Open in browser</string>
<string name="settings.cache_title">Music cache</string>
- <string name="settings.preload">Songs to preload</string>
+ <string name="settings.preload_wifi">Songs to preload (Wifi)</string>
+ <string name="settings.preload_mobile">Songs to preload (Mobile)</string>
<string name="settings.cache_size">Cache size (MB)</string>
<string name="settings.cache_location">Cache location</string>
<string name="settings.cache_location_error">Invalid cache location. Using default.</string>
@@ -197,9 +204,11 @@ <string name="settings.theme_title">Theme</string>
<string name="settings.theme_light">Light</string>
<string name="settings.theme_dark">Dark</string>
+ <string name="settings.theme_black">Black</string>
<string name="settings.theme_holo">Holo</string>
<string name="settings.theme_light_fullscreen">Light Fullscreen</string>
<string name="settings.theme_dark_fullscreen">Dark Fullscreen</string>
+ <string name="settings.theme_black_fullscreen">Black Fullscreen</string>
<string name="settings.theme_holo_fullscreen">Holo Fullscreen</string>
<string name="settings.network_title">Network</string>
<string name="settings.max_bitrate_wifi">Max Audio bitrate - Wi-Fi</string>
@@ -270,10 +279,14 @@ <string name="settings.persistent_summary">Show the notification even after pausing. Press the stop button to clear it away.</string>
<string name="settings.gapless_playback">Gapless Playback</string>
<string name="settings.gapless_playback_summary">The Galaxy S3 seems to be experiencing freezes/other weird issue since the introduction of gapless playback. Turn this off to fix the issue.</string>
-
+ <string name="settings.chat_refresh">Chat Refresh Rate (Secs)</string>
+ <string name="settings.chat_enabled">Chat Enabled</string>
+ <string name="settings.chat_enabled_summary">Whether or not to display the chat tab. Restart app after changing.</string>
+
<string name="shuffle.startYear">Start Year:</string>
<string name="shuffle.endYear">End Year:</string>
<string name="shuffle.genre">Genre:</string>
+ <string name="shuffle.pick_genre">Pick a genre</string>
<string name="music_service.retry">A network error occurred. Retrying %1$d of %2$d.</string>
@@ -320,6 +333,8 @@ <string name="changelog_ok_button">OK</string>
<string name="changelog_show_full">More…</string>
+ <string name="chat.send_a_message">Send a message</string>
+
<string name="changelog_version_format" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">Version <xliff:g id="version_name">%s</xliff:g></string>
<plurals name="select_album_n_songs">
diff --git a/subsonic-android/res/values/themes.xml b/subsonic-android/res/values/themes.xml index 2389365f..33dd2de7 100644 --- a/subsonic-android/res/values/themes.xml +++ b/subsonic-android/res/values/themes.xml @@ -3,8 +3,6 @@ <style name="Theme.DSub.Light" parent="Theme.Sherlock.Light"> <item name="actionBarStyle">@style/Widget.DSub.ActionBarStyle.Light</item> <item name="android:actionBarStyle">@style/Widget.DSub.ActionBarStyle.Light</item> - <item name="android:textColorSecondary">@color/cyan</item> - <item name="android:windowBackground">@color/lightBackground</item> <item name="offline_icon">@drawable/main_offline_light</item> <item name="media_button_backward">@drawable/media_backward_light</item> <item name="media_button_forward">@drawable/media_forward_light</item> @@ -12,6 +10,8 @@ <item name="media_button_repeat_off">@drawable/media_repeat_off_light</item> <item name="media_button_start">@drawable/media_start_light</item> <item name="media_button_stop">@drawable/media_stop_light</item> + <item name="chat">@drawable/ic_menu_chat_light</item> + <item name="chat_send">@drawable/ic_menu_chat_send_light</item> </style> <style name="Theme.DSub.Dark" parent="Theme.Sherlock"> <item name="actionBarStyle">@style/Widget.DSub.ActionBarStyle.Dark</item> @@ -24,6 +24,11 @@ <item name="media_button_repeat_off">@drawable/media_repeat_off</item> <item name="media_button_start">@drawable/media_start</item> <item name="media_button_stop">@drawable/media_stop</item> + <item name="chat">@drawable/ic_menu_chat_dark</item> + <item name="chat_send">@drawable/ic_menu_chat_send_dark</item> + </style> + <style name="Theme.DSub.Black" parent="Theme.DSub.Dark"> + <item name="android:windowBackground">@android:color/black</item> </style> <style name="Theme.DSub.Holo" parent="Theme.Sherlock"> <item name="actionBarStyle">@style/Widget.DSub.ActionBarStyle.Holo</item> @@ -37,6 +42,8 @@ <item name="media_button_repeat_off">@drawable/media_repeat_off</item> <item name="media_button_start">@drawable/media_start</item> <item name="media_button_stop">@drawable/media_stop</item> + <item name="chat">@drawable/ic_menu_chat_dark</item> + <item name="chat_send">@drawable/ic_menu_chat_send_dark</item> </style> <style name="Theme.DSub.Light.Fullscreen" parent="Theme.DSub.Light"> @@ -45,6 +52,9 @@ <style name="Theme.DSub.Dark.Fullscreen" parent="Theme.DSub.Dark"> <item name="android:windowFullscreen">true</item> </style> + <style name="Theme.DSub.Black.Fullscreen" parent="Theme.DSub.Black"> + <item name="android:windowFullscreen">true</item> + </style> <style name="Theme.DSub.Holo.Fullscreen" parent="Theme.DSub.Holo"> <item name="android:windowFullscreen">true</item> </style> diff --git a/subsonic-android/res/xml/settings.xml b/subsonic-android/res/xml/settings.xml index d21f928f..17a51621 100644 --- a/subsonic-android/res/xml/settings.xml +++ b/subsonic-android/res/xml/settings.xml @@ -4,102 +4,12 @@ android:title="@string/settings.title"> <PreferenceCategory + android:key="server" android:title="@string/settings.servers_title"> - <PreferenceScreen - android:key="server1"> - - <EditTextPreference - android:key="serverName1" - android:title="@string/settings.server_name" - android:defaultValue="Subsonic demo"/> - - <EditTextPreference - android:key="serverUrl1" - android:title="@string/settings.server_address" - android:defaultValue="http://demo.subsonic.org" - android:inputType="textUri"/> - - <EditTextPreference - android:key="username1" - android:title="@string/settings.server_username" - android:defaultValue="android-guest"/> - - <EditTextPreference - android:key="password1" - android:title="@string/settings.server_password" - android:password="true" - android:defaultValue="guest" - android:summary="****"/> - - <Preference - android:key="testConnection1" - android:title="@string/settings.test_connection_title" - android:persistent="false"/> - - </PreferenceScreen> - - <PreferenceScreen - android:key="server2"> - - <EditTextPreference - android:key="serverName2" - android:title="@string/settings.server_name" - android:defaultValue="@string/settings.server_unused1"/> - - <EditTextPreference - android:key="serverUrl2" - android:title="@string/settings.server_address" - android:defaultValue="http://yourhost" - android:inputType="textUri"/> - - <EditTextPreference - android:key="username2" - android:title="@string/settings.server_username"/> - - <EditTextPreference - android:key="password2" - android:title="@string/settings.server_password" - android:password="true" - android:summary="****"/> - - <Preference - android:key="testConnection2" - android:title="@string/settings.test_connection_title" - android:persistent="false"/> - - </PreferenceScreen> - - <PreferenceScreen - android:key="server3"> - - <EditTextPreference - android:key="serverName3" - android:title="@string/settings.server_name" - android:defaultValue="@string/settings.server_unused2"/> - - <EditTextPreference - android:key="serverUrl3" - android:title="@string/settings.server_address" - android:defaultValue="http://yourhost" - android:inputType="textUri"/> - - <EditTextPreference - android:key="username3" - android:title="@string/settings.server_username"/> - - <EditTextPreference - android:key="password3" - android:title="@string/settings.server_password" - android:password="true" - android:summary="****"/> - - <Preference - android:key="testConnection3" - android:title="@string/settings.test_connection_title" - android:persistent="false"/> - - </PreferenceScreen> + <Preference + android:key="serverAdd" + android:title="@string/settings.servers_add"/> </PreferenceCategory> @@ -204,14 +114,37 @@ android:key="cacheLocation"/> <ListPreference - android:title="@string/settings.preload" - android:key="preloadCount" + android:title="@string/settings.preload_wifi" + android:key="preloadCountWifi" + android:defaultValue="3" + android:entryValues="@array/preloadCountValues" + android:entries="@array/preloadCountNames"/> + + <ListPreference + android:title="@string/settings.preload_mobile" + android:key="preloadCountMobile" android:defaultValue="3" android:entryValues="@array/preloadCountValues" android:entries="@array/preloadCountNames"/> </PreferenceCategory> + <PreferenceCategory + android:title="@string/button_bar.chat"> + + <CheckBoxPreference + android:title="@string/settings.chat_enabled" + android:summary="@string/settings.chat_enabled_summary" + android:key="chatEnabled" + android:defaultValue="true"/> + + <EditTextPreference + android:title="@string/settings.chat_refresh" + android:key="chatRefreshRate" + android:defaultValue="30" + android:digits="0123456789"/> + </PreferenceCategory> + <PreferenceCategory android:title="@string/settings.other_title"> diff --git a/subsonic-android/src/github/daneren2005/dsub/activity/EqualizerActivity.java b/subsonic-android/src/github/daneren2005/dsub/activity/EqualizerActivity.java index e5de3858..d9605fc5 100644 --- a/subsonic-android/src/github/daneren2005/dsub/activity/EqualizerActivity.java +++ b/subsonic-android/src/github/daneren2005/dsub/activity/EqualizerActivity.java @@ -142,18 +142,26 @@ public class EqualizerActivity extends Activity { private void updateBars(boolean changedEnabled) { boolean isEnabled = equalizer.getEnabled(); short minEQLevel = equalizer.getBandLevelRange()[0]; + short maxEQLevel = equalizer.getBandLevelRange()[1]; for (Map.Entry<Short, SeekBar> entry : bars.entrySet()) { short band = entry.getKey(); SeekBar bar = entry.getValue(); bar.setEnabled(isEnabled); if(band >= (short)0) { + short setLevel; if(changedEnabled) { - equalizer.setBandLevel(band, (short)(equalizer.getBandLevel(band) - masterLevel)); + setLevel = (short)(equalizer.getBandLevel(band) - masterLevel); bar.setProgress(equalizer.getBandLevel(band) - minEQLevel); } else { bar.setProgress(equalizer.getBandLevel(band) - minEQLevel); - equalizer.setBandLevel(band, (short)(equalizer.getBandLevel(band) + masterLevel)); + setLevel = (short)(equalizer.getBandLevel(band) + masterLevel); } + if(setLevel < minEQLevel) { + setLevel = minEQLevel; + } else if(setLevel > maxEQLevel) { + setLevel = maxEQLevel; + } + equalizer.setBandLevel(band, setLevel); } else if(!isEnabled) { bar.setProgress(-minEQLevel); } diff --git a/subsonic-android/src/github/daneren2005/dsub/activity/MainActivity.java b/subsonic-android/src/github/daneren2005/dsub/activity/MainActivity.java index d0eb86ef..2709db46 100644 --- a/subsonic-android/src/github/daneren2005/dsub/activity/MainActivity.java +++ b/subsonic-android/src/github/daneren2005/dsub/activity/MainActivity.java @@ -1,9 +1,6 @@ package github.daneren2005.dsub.activity; import android.app.AlertDialog; -import android.app.PendingIntent; -import android.content.ComponentName; -import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.TypedArray; @@ -15,16 +12,18 @@ import com.actionbarsherlock.app.ActionBar; import com.actionbarsherlock.view.Menu; import android.support.v4.view.ViewPager; import android.util.Log; -import android.view.KeyEvent; import android.view.View; import android.widget.ImageButton; import android.widget.TextView; import github.daneren2005.dsub.R; import github.daneren2005.dsub.domain.MusicDirectory; import github.daneren2005.dsub.domain.PlayerState; +import github.daneren2005.dsub.fragments.ChatFragment; import github.daneren2005.dsub.fragments.MainFragment; import github.daneren2005.dsub.fragments.SelectArtistFragment; +import github.daneren2005.dsub.fragments.SelectDirectoryFragment; import github.daneren2005.dsub.fragments.SelectPlaylistFragment; +import github.daneren2005.dsub.fragments.SubsonicFragment; import github.daneren2005.dsub.service.DownloadFile; import github.daneren2005.dsub.service.DownloadServiceImpl; import github.daneren2005.dsub.updates.Updater; @@ -152,9 +151,13 @@ public class MainActivity extends SubsonicActivity { viewPager.setAdapter(pagerAdapter); viewPager.setOnPageChangeListener(pagerAdapter); - addTab("Home", MainFragment.class, null); - addTab("Library", SelectArtistFragment.class, null); - addTab("Playlists", SelectPlaylistFragment.class, null); + addTab(R.string.button_bar_home, MainFragment.class, null); + addTab(R.string.button_bar_browse, SelectArtistFragment.class, null); + addTab(R.string.button_bar_playlists, SelectPlaylistFragment.class, null); + SharedPreferences prefs = Util.getPreferences(this); + if(prefs.getBoolean(Constants.PREFERENCES_KEY_CHAT_ENABLED, true)) { + addTab(R.string.button_bar_chat, ChatFragment.class, null); + } getSupportActionBar().setDisplayHomeAsUpEnabled(false); getSupportActionBar().setHomeButtonEnabled(false); @@ -190,6 +193,31 @@ public class MainActivity extends SubsonicActivity { }); } }; + + if(getIntent().hasExtra(Constants.INTENT_EXTRA_VIEW_ALBUM)) { + viewPager.setCurrentItem(1); + + int fragmentID = R.id.select_artist_layout; + if(getIntent().hasExtra(Constants.INTENT_EXTRA_NAME_PARENT_ID)) { + SubsonicFragment fragment = new SelectDirectoryFragment(); + Bundle args = new Bundle(); + args.putString(Constants.INTENT_EXTRA_NAME_ID, getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_PARENT_ID)); + args.putString(Constants.INTENT_EXTRA_NAME_NAME, getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_PARENT_NAME)); + fragment.setArguments(args); + + pagerAdapter.queueFragment(fragment, R.id.select_artist_layout); + fragmentID = R.id.select_album_layout; + } + + SubsonicFragment fragment = new SelectDirectoryFragment(); + Bundle args = new Bundle(); + args.putString(Constants.INTENT_EXTRA_NAME_ID, getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_ID)); + args.putString(Constants.INTENT_EXTRA_NAME_NAME, getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_NAME)); + fragment.setArguments(args); + + pagerAdapter.queueFragment(fragment, fragmentID); + getIntent().removeExtra(Constants.INTENT_EXTRA_VIEW_ALBUM); + } executorService = Executors.newSingleThreadScheduledExecutor(); executorService.scheduleWithFixedDelay(runnable, 0L, 1000L, TimeUnit.MILLISECONDS); @@ -261,9 +289,19 @@ public class MainActivity extends SubsonicActivity { if (!prefs.contains(Constants.PREFERENCES_KEY_OFFLINE)) { SharedPreferences.Editor editor = prefs.edit(); editor.putBoolean(Constants.PREFERENCES_KEY_OFFLINE, false); + + editor.putString(Constants.PREFERENCES_KEY_SERVER_NAME + 1, "Demo Server"); + editor.putString(Constants.PREFERENCES_KEY_SERVER_URL + 1, "http://demo.subsonic.org"); + editor.putString(Constants.PREFERENCES_KEY_USERNAME + 1, "android-guest"); + editor.putString(Constants.PREFERENCES_KEY_PASSWORD + 1, "guest"); editor.putInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1); editor.commit(); - } + } + if(!prefs.contains(Constants.PREFERENCES_KEY_SERVER_COUNT)) { + SharedPreferences.Editor editor = prefs.edit(); + editor.putInt(Constants.PREFERENCES_KEY_SERVER_COUNT, 3); + editor.commit(); + } } private void showInfoDialog() { diff --git a/subsonic-android/src/github/daneren2005/dsub/activity/SettingsActivity.java b/subsonic-android/src/github/daneren2005/dsub/activity/SettingsActivity.java index 18d7e6b4..88e487f1 100644 --- a/subsonic-android/src/github/daneren2005/dsub/activity/SettingsActivity.java +++ b/subsonic-android/src/github/daneren2005/dsub/activity/SettingsActivity.java @@ -18,14 +18,19 @@ */ package github.daneren2005.dsub.activity; +import android.content.Intent; import android.content.SharedPreferences; +import android.net.Uri; import android.os.Bundle; +import android.preference.CheckBoxPreference; import android.preference.EditTextPreference; import android.preference.ListPreference; import android.preference.Preference; import android.preference.PreferenceActivity; +import android.preference.PreferenceCategory; import android.preference.PreferenceScreen; import android.provider.SearchRecentSuggestions; +import android.text.InputType; import android.util.Log; import github.daneren2005.dsub.R; import github.daneren2005.dsub.provider.DSubSearchProvider; @@ -45,7 +50,6 @@ import java.util.LinkedHashMap; import java.util.Map; public class SettingsActivity extends PreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener { - private static final String TAG = SettingsActivity.class.getSimpleName(); private final Map<String, ServerSettings> serverSettings = new LinkedHashMap<String, ServerSettings>(); private boolean testingConnection; @@ -57,13 +61,21 @@ public class SettingsActivity extends PreferenceActivity implements SharedPrefer private ListPreference networkTimeout; private EditTextPreference cacheSize; private EditTextPreference cacheLocation; - private ListPreference preloadCount; + private ListPreference preloadCountWifi; + private ListPreference preloadCountMobile; private EditTextPreference randomSize; private ListPreference tempLoss; private EditTextPreference bufferLength; + private Preference addServerPreference; + private PreferenceCategory serversCategory; + private EditTextPreference chatRefreshRate; + + private int serverCount = 3; + private SharedPreferences settings; @Override public void onCreate(Bundle savedInstanceState) { + applyTheme(); super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.settings); @@ -75,34 +87,17 @@ public class SettingsActivity extends PreferenceActivity implements SharedPrefer networkTimeout = (ListPreference) findPreference(Constants.PREFERENCES_KEY_NETWORK_TIMEOUT); cacheSize = (EditTextPreference) findPreference(Constants.PREFERENCES_KEY_CACHE_SIZE); cacheLocation = (EditTextPreference) findPreference(Constants.PREFERENCES_KEY_CACHE_LOCATION); - preloadCount = (ListPreference) findPreference(Constants.PREFERENCES_KEY_PRELOAD_COUNT); + preloadCountWifi = (ListPreference) findPreference(Constants.PREFERENCES_KEY_PRELOAD_COUNT_WIFI); + preloadCountMobile = (ListPreference) findPreference(Constants.PREFERENCES_KEY_PRELOAD_COUNT_MOBILE); randomSize = (EditTextPreference) findPreference(Constants.PREFERENCES_KEY_RANDOM_SIZE); tempLoss = (ListPreference) findPreference(Constants.PREFERENCES_KEY_TEMP_LOSS); bufferLength = (EditTextPreference) findPreference(Constants.PREFERENCES_KEY_BUFFER_LENGTH); - - findPreference("testConnection1").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - testConnection(1); - return false; - } - }); - - findPreference("testConnection2").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - testConnection(2); - return false; - } - }); - - findPreference("testConnection3").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - testConnection(3); - return false; - } - }); + addServerPreference = (Preference) findPreference(Constants.PREFERENCES_KEY_SERVER_ADD); + serversCategory = (PreferenceCategory) findPreference(Constants.PREFERENCES_KEY_SERVER_KEY); + chatRefreshRate = (EditTextPreference) findPreference(Constants.PREFERENCES_KEY_CHAT_REFRESH); + + settings = Util.getPreferences(this); + serverCount = settings.getInt(Constants.PREFERENCES_KEY_SERVER_COUNT, 3); findPreference("clearSearchHistory").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { @Override @@ -113,19 +108,43 @@ public class SettingsActivity extends PreferenceActivity implements SharedPrefer return false; } }); + + addServerPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + serverCount++; + String instance = String.valueOf(serverCount); + + Preference addServerPreference = findPreference(Constants.PREFERENCES_KEY_SERVER_ADD); + serversCategory.removePreference(addServerPreference); + serversCategory.addPreference(addServer(serverCount)); + serversCategory.addPreference(addServerPreference); + + SharedPreferences.Editor editor = settings.edit(); + editor.putInt(Constants.PREFERENCES_KEY_SERVER_COUNT, serverCount); + editor.commit(); + + serverSettings.put(instance, new ServerSettings(instance)); + + return true; + } + }); - for (int i = 1; i <= 3; i++) { + serversCategory.removePreference(addServerPreference); + for (int i = 1; i <= serverCount; i++) { String instance = String.valueOf(i); + serversCategory.addPreference(addServer(i)); serverSettings.put(instance, new ServerSettings(instance)); } + serversCategory.addPreference(addServerPreference); SharedPreferences prefs = Util.getPreferences(this); prefs.registerOnSharedPreferenceChangeListener(this); update(); } - - @Override + + @Override protected void onDestroy() { super.onDestroy(); @@ -166,14 +185,145 @@ public class SettingsActivity extends PreferenceActivity implements SharedPrefer networkTimeout.setSummary(networkTimeout.getEntry()); cacheSize.setSummary(cacheSize.getText()); cacheLocation.setSummary(cacheLocation.getText()); - preloadCount.setSummary(preloadCount.getEntry()); + preloadCountWifi.setSummary(preloadCountWifi.getEntry()); + preloadCountMobile.setSummary(preloadCountMobile.getEntry()); randomSize.setSummary(randomSize.getText()); tempLoss.setSummary(tempLoss.getEntry()); bufferLength.setSummary(bufferLength.getText() + " seconds"); + chatRefreshRate.setSummary(chatRefreshRate.getText()); for (ServerSettings ss : serverSettings.values()) { ss.update(); } } + + private PreferenceScreen addServer(final int instance) { + final PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(this); + screen.setTitle(R.string.settings_server_unused); + screen.setKey(Constants.PREFERENCES_KEY_SERVER_KEY + instance); + + final EditTextPreference serverNamePreference = new EditTextPreference(this); + serverNamePreference.setKey(Constants.PREFERENCES_KEY_SERVER_NAME + instance); + serverNamePreference.setDefaultValue(getResources().getString(R.string.settings_server_unused)); + serverNamePreference.setTitle(R.string.settings_server_name); + + if (serverNamePreference.getText() == null) { + serverNamePreference.setText(getResources().getString(R.string.settings_server_unused)); + } + + serverNamePreference.setSummary(serverNamePreference.getText()); + + final EditTextPreference serverUrlPreference = new EditTextPreference(this); + serverUrlPreference.setKey(Constants.PREFERENCES_KEY_SERVER_URL + instance); + serverUrlPreference.getEditText().setInputType(InputType.TYPE_TEXT_VARIATION_URI); + serverUrlPreference.setDefaultValue("http://yourhost"); + serverUrlPreference.setTitle(R.string.settings_server_address); + + if (serverUrlPreference.getText() == null) { + serverUrlPreference.setText("http://yourhost"); + } + + serverUrlPreference.setSummary(serverUrlPreference.getText()); + + screen.setSummary(serverUrlPreference.getText()); + + final EditTextPreference serverUsernamePreference = new EditTextPreference(this); + serverUsernamePreference.setKey(Constants.PREFERENCES_KEY_USERNAME + instance); + serverUsernamePreference.setTitle(R.string.settings_server_username); + + final EditTextPreference serverPasswordPreference = new EditTextPreference(this); + serverPasswordPreference.setKey(Constants.PREFERENCES_KEY_PASSWORD + instance); + serverPasswordPreference.getEditText().setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + serverPasswordPreference.setSummary("***"); + serverPasswordPreference.setTitle(R.string.settings_server_password); + + final Preference serverOpenBrowser = new Preference(this); + serverOpenBrowser.setKey(Constants.PREFERENCES_KEY_OPEN_BROWSER); + serverOpenBrowser.setPersistent(false); + serverOpenBrowser.setTitle(R.string.settings_server_open_browser); + serverOpenBrowser.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + openInBrowser(instance); + return true; + } + }); + + Preference serverRemoveServerPreference = new Preference(this); + serverRemoveServerPreference.setKey(Constants.PREFERENCES_KEY_SERVER_REMOVE + instance); + serverRemoveServerPreference.setPersistent(false); + serverRemoveServerPreference.setTitle(R.string.settings_servers_remove); + + serverRemoveServerPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + // Reset values to null so when we ask for them again they are new + serverNamePreference.setText(null); + serverUrlPreference.setText(null); + serverUsernamePreference.setText(null); + serverPasswordPreference.setText(null); + + int activeServer = Util.getActiveServer(SettingsActivity.this); + for (int i = instance; i <= serverCount; i++) { + Util.removeInstanceName(SettingsActivity.this, i, activeServer); + } + + serverCount--; + SharedPreferences.Editor editor = settings.edit(); + editor.putInt(Constants.PREFERENCES_KEY_SERVER_COUNT, serverCount); + editor.commit(); + + serversCategory.removePreference(screen); + screen.getDialog().dismiss(); + + return true; + } + }); + + Preference serverTestConnectionPreference = new Preference(this); + serverTestConnectionPreference.setKey(Constants.PREFERENCES_KEY_TEST_CONNECTION + instance); + serverTestConnectionPreference.setPersistent(false); + serverTestConnectionPreference.setTitle(R.string.settings_test_connection_title); + serverTestConnectionPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + testConnection(instance); + return false; + } + }); + + screen.addPreference(serverNamePreference); + screen.addPreference(serverUrlPreference); + screen.addPreference(serverUsernamePreference); + screen.addPreference(serverPasswordPreference); + screen.addPreference(serverRemoveServerPreference); + screen.addPreference(serverTestConnectionPreference); + screen.addPreference(serverOpenBrowser); + + return screen; + } + + private void applyTheme() { + String activeTheme = Util.getTheme(this); + if ("dark".equals(activeTheme)) { + setTheme(R.style.Theme_DSub_Dark); + } else if ("black".equals(activeTheme)) { + setTheme(R.style.Theme_DSub_Black); + } else if ("light".equals(activeTheme)) { + setTheme(R.style.Theme_DSub_Light); + } else if ("dark_fullscreen".equals(activeTheme)) { + setTheme(R.style.Theme_DSub_Dark_Fullscreen); + } else if ("black_fullscreen".equals(activeTheme)) { + setTheme(R.style.Theme_DSub_Black_Fullscreen); + } else if ("light_fullscreen".equals(activeTheme)) { + setTheme(R.style.Theme_DSub_Light_Fullscreen); + } else if("holo".equals(activeTheme)) { + setTheme(R.style.Theme_DSub_Holo); + } else if("holo_fullscreen".equals(activeTheme)) { + setTheme(R.style.Theme_DSub_Holo_Fullscreen); + }else { + setTheme(R.style.Theme_DSub_Holo); + } + } private void setHideMedia(boolean hide) { File nomediaDir = new File(FileUtil.getSubsonicDirectory(), ".nomedia"); @@ -264,6 +414,15 @@ public class SettingsActivity extends PreferenceActivity implements SharedPrefer }; task.execute(); } + + private void openInBrowser(final int instance) { + SharedPreferences prefs = Util.getPreferences(this); + String url = prefs.getString(Constants.PREFERENCES_KEY_SERVER_URL + instance, null); + Uri uriServer = Uri.parse(url); + + Intent browserIntent = new Intent(Intent.ACTION_VIEW, uriServer); + startActivity(browserIntent); + } private class ServerSettings { private EditTextPreference serverName; diff --git a/subsonic-android/src/github/daneren2005/dsub/activity/SubsonicActivity.java b/subsonic-android/src/github/daneren2005/dsub/activity/SubsonicActivity.java index 15b8421f..d8158f7d 100644 --- a/subsonic-android/src/github/daneren2005/dsub/activity/SubsonicActivity.java +++ b/subsonic-android/src/github/daneren2005/dsub/activity/SubsonicActivity.java @@ -277,10 +277,14 @@ public class SubsonicActivity extends SherlockFragmentActivity implements OnItem theme = Util.getTheme(this);
if ("dark".equals(theme)) {
setTheme(R.style.Theme_DSub_Dark);
+ } else if ("black".equals(theme)) {
+ setTheme(R.style.Theme_DSub_Black);
} else if ("light".equals(theme)) {
setTheme(R.style.Theme_DSub_Light);
} else if ("dark_fullscreen".equals(theme)) {
setTheme(R.style.Theme_DSub_Dark_Fullscreen);
+ } else if ("black_fullscreen".equals(theme)) {
+ setTheme(R.style.Theme_DSub_Black_Fullscreen);
} else if ("light_fullscreen".equals(theme)) {
setTheme(R.style.Theme_DSub_Light_Fullscreen);
} else if("holo".equals(theme)) {
@@ -388,6 +392,7 @@ public class SubsonicActivity extends SherlockFragmentActivity implements OnItem private SubsonicFragment currentFragment;
private List<TabInfo> tabs = new ArrayList<TabInfo>();
private List<List<SubsonicFragment>> frags = new ArrayList<List<SubsonicFragment>>();
+ private List<QueuedFragment> queue = new ArrayList<QueuedFragment>();
private int currentPosition;
public TabPagerAdapter(SherlockFragmentActivity activity, ViewPager pager) {
@@ -404,8 +409,15 @@ public class SubsonicActivity extends SherlockFragmentActivity implements OnItem SubsonicFragment frag = (SubsonicFragment) Fragment.instantiate(activity, tabInfo.fragmentClass.getName(), tabInfo.args);
List<SubsonicFragment> fragStack = new ArrayList<SubsonicFragment>();
fragStack.add(frag);
- frags.add(i, fragStack);
- if(currentFragment == null) {
+ while(i > frags.size()) {
+ frags.add(null);
+ }
+ if(i == frags.size()) {
+ frags.add(i, fragStack);
+ } else {
+ frags.set(i, fragStack);
+ }
+ if(currentFragment == null || currentPosition == i) {
currentFragment = frag;
currentFragment.setPrimaryFragment(true);
}
@@ -420,6 +432,13 @@ public class SubsonicActivity extends SherlockFragmentActivity implements OnItem public void onCreateOptionsMenu(Menu menu, com.actionbarsherlock.view.MenuInflater menuInflater) {
if(currentFragment != null) {
currentFragment.onCreateOptionsMenu(menu, menuInflater);
+
+ for(QueuedFragment addFragment: queue) {
+ replaceFragment(addFragment.fragment, addFragment.id, currentFragment.getSupportTag());
+ currentFragment = addFragment.fragment;
+ }
+ currentFragment.setPrimaryFragment(true);
+ queue.clear();
}
}
public boolean onOptionsItemSelected(com.actionbarsherlock.view.MenuItem item) {
@@ -454,13 +473,15 @@ public class SubsonicActivity extends SherlockFragmentActivity implements OnItem if(currentFragment != null) {
currentFragment.setPrimaryFragment(false);
}
- List<SubsonicFragment> fragStack = frags.get(position);
- currentFragment = fragStack.get(fragStack.size() - 1);
- if(currentFragment != null) {
- currentFragment.setPrimaryFragment(true);
+ if(position <= frags.size()) {
+ List<SubsonicFragment> fragStack = frags.get(position);
+ currentFragment = fragStack.get(fragStack.size() - 1);
+ if(currentFragment != null) {
+ currentFragment.setPrimaryFragment(true);
+ }
+ activity.invalidateOptionsMenu();
+ recreateSpinner();
}
- activity.invalidateOptionsMenu();
- recreateSpinner();
}
public void addTab(CharSequence title, Class fragmentClass, Bundle args) {
@@ -476,7 +497,12 @@ public class SubsonicActivity extends SherlockFragmentActivity implements OnItem actionBar.addTab(tab);
notifyDataSetChanged();
}
-
+ public void queueFragment(SubsonicFragment fragment, int id) {
+ QueuedFragment frag = new QueuedFragment();
+ frag.fragment = fragment;
+ frag.id = id;
+ queue.add(frag);
+ }
public void replaceCurrent(SubsonicFragment fragment, int id, int tag) {
if(currentFragment != null) {
currentFragment.setPrimaryFragment(false);
@@ -547,11 +573,19 @@ public class SubsonicActivity extends SherlockFragmentActivity implements OnItem }
public void invalidate() {
+ FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
for (int i = 0; i < frags.size(); i++) {
- List fragStack = (List)frags.get(i);
- SubsonicFragment frag = (SubsonicFragment)fragStack.get(fragStack.size() - 1);
+ List<SubsonicFragment> fragStack = frags.get(i);
+
+ for(int j = fragStack.size() - 1; j > 0; j--) {
+ SubsonicFragment oldFrag = fragStack.remove(j);
+ trans.remove((Fragment)oldFrag);
+ }
+
+ SubsonicFragment frag = (SubsonicFragment)fragStack.get(0);
frag.invalidate();
}
+ trans.commit();
}
public void onSaveInstanceState(Bundle savedInstanceState) {
@@ -599,5 +633,9 @@ public class SubsonicActivity extends SherlockFragmentActivity implements OnItem this.args = args;
}
}
+ private class QueuedFragment {
+ public SubsonicFragment fragment;
+ public int id;
+ }
}
}
diff --git a/subsonic-android/src/github/daneren2005/dsub/domain/ChatMessage.java b/subsonic-android/src/github/daneren2005/dsub/domain/ChatMessage.java new file mode 100644 index 00000000..471594e9 --- /dev/null +++ b/subsonic-android/src/github/daneren2005/dsub/domain/ChatMessage.java @@ -0,0 +1,51 @@ +/*
+ This file is part of Subsonic.
+
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2009 (C) Sindre Mehus
+ */
+package github.daneren2005.dsub.domain;
+
+import java.io.Serializable;
+
+public class ChatMessage implements Serializable {
+ private String username;
+ private Long time;
+ private String message;
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public Long getTime() {
+ return time;
+ }
+
+ public void setTime(Long time) {
+ this.time = time;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+}
diff --git a/subsonic-android/src/github/daneren2005/dsub/domain/Genre.java b/subsonic-android/src/github/daneren2005/dsub/domain/Genre.java new file mode 100644 index 00000000..8c705e31 --- /dev/null +++ b/subsonic-android/src/github/daneren2005/dsub/domain/Genre.java @@ -0,0 +1,29 @@ +package github.daneren2005.dsub.domain;
+
+import java.io.Serializable;
+
+public class Genre implements Serializable {
+ private String name;
+ private String index;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getIndex() {
+ return index;
+ }
+
+ public void setIndex(String index) {
+ this.index = index;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+}
diff --git a/subsonic-android/src/github/daneren2005/dsub/domain/MusicDirectory.java b/subsonic-android/src/github/daneren2005/dsub/domain/MusicDirectory.java index b89417c0..10e9ef22 100644 --- a/subsonic-android/src/github/daneren2005/dsub/domain/MusicDirectory.java +++ b/subsonic-android/src/github/daneren2005/dsub/domain/MusicDirectory.java @@ -18,17 +18,23 @@ */ package github.daneren2005.dsub.domain; +import android.util.Log; import java.util.ArrayList; import java.util.List; import java.io.Serializable; +import java.util.Collections; +import java.util.Comparator; /** * @author Sindre Mehus */ public class MusicDirectory { + private static final String TAG = MusicDirectory.class.getSimpleName(); private String name; - private final List<Entry> children = new ArrayList<Entry>(); + private String id; + private String parent; + private List<Entry> children = new ArrayList<Entry>(); public String getName() { return name; @@ -37,6 +43,22 @@ public class MusicDirectory { public void setName(String name) { this.name = name; } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getParent() { + return parent; + } + + public void setParent(String parent) { + this.parent = parent; + } public void addChild(Entry child) { children.add(child); @@ -59,10 +81,15 @@ public class MusicDirectory { } return result; } + + public void sortChildren() { + EntryComparator.sort(children); + } public static class Entry implements Serializable { private String id; private String parent; + private String grandParent; private boolean directory; private String title; private String album; @@ -80,6 +107,7 @@ public class MusicDirectory { private Integer bitRate; private String path; private boolean video; + private Integer discNumber; private boolean starred; private int closeness; @@ -98,6 +126,14 @@ public class MusicDirectory { public void setParent(String parent) { this.parent = parent; } + + public String getGrandParent() { + return grandParent; + } + + public void setGrandParent(String grandParent) { + this.grandParent = grandParent; + } public boolean isDirectory() { return directory; @@ -234,6 +270,14 @@ public class MusicDirectory { public void setVideo(boolean video) { this.video = video; } + + public Integer getDiscNumber() { + return discNumber; + } + + public void setDiscNumber(Integer discNumber) { + this.discNumber = discNumber; + } public boolean isStarred() { return starred; @@ -274,4 +318,53 @@ public class MusicDirectory { return title; } } + + public static class EntryComparator implements Comparator<Entry> { + public int compare(Entry lhs, Entry rhs) { + if(lhs.isDirectory() && !rhs.isDirectory()) { + return -1; + } else if(!lhs.isDirectory() && rhs.isDirectory()) { + return 1; + } + + Integer lhsDisc = lhs.getDiscNumber(); + Integer rhsDisc = rhs.getDiscNumber(); + + if(lhsDisc != null && rhsDisc != null) { + if(lhsDisc < rhsDisc) { + return -1; + } else if(lhsDisc > rhsDisc) { + return 1; + } + } else if(lhsDisc != null) { + return -1; + } else if(rhsDisc != null) { + return 1; + } + + Integer lhsTrack = lhs.getTrack(); + Integer rhsTrack = rhs.getTrack(); + if(lhsTrack != null && rhsTrack != null) { + if(lhsTrack < rhsTrack) { + return -1; + } else if(lhsTrack > rhsTrack) { + return 1; + } + } else if(lhsTrack != null) { + return -1; + } else if(rhsTrack != null) { + return 1; + } + + return lhs.getTitle().compareToIgnoreCase(rhs.getTitle()); + } + + public static void sort(List<Entry> entries) { + try { + Collections.sort(entries, new EntryComparator()); + } catch (Exception e) { + Log.w(TAG, "Failed to sort MusicDirectory"); + } + } + } }
\ No newline at end of file diff --git a/subsonic-android/src/github/daneren2005/dsub/domain/Share.java b/subsonic-android/src/github/daneren2005/dsub/domain/Share.java new file mode 100644 index 00000000..d19496f9 --- /dev/null +++ b/subsonic-android/src/github/daneren2005/dsub/domain/Share.java @@ -0,0 +1,140 @@ +/*
+ This file is part of Subsonic.
+
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2009 (C) Sindre Mehus
+ */
+package github.daneren2005.dsub.domain;
+
+import github.daneren2005.dsub.domain.MusicDirectory.Entry;
+import java.io.Serializable;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+public class Share implements Serializable {
+ private String id;
+ private String url;
+ private String description;
+ private String username;
+ private Date created;
+ private Date lastVisited;
+ private Date expires;
+ private Long visitCount;
+ private List<Entry> entries;
+
+ public Share() {
+ entries = new ArrayList<Entry>();
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public Date getCreated() {
+ return created;
+ }
+
+ public void setCreated(String created) {
+ if (created != null) {
+ try {
+ this.created = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH).parse(created);
+ } catch (ParseException e) {
+ this.created = null;
+ }
+ } else {
+ this.created = null;
+ }
+ }
+
+ public Date getLastVisited() {
+ return lastVisited;
+ }
+
+ public void setLastVisited(String lastVisited) {
+ if (lastVisited != null) {
+ try {
+ this.lastVisited = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH).parse(lastVisited);
+ } catch (ParseException e) {
+ this.lastVisited = null;
+ }
+ } else {
+ this.lastVisited = null;
+ }
+ }
+
+ public Date getExpires() {
+ return expires;
+ }
+
+ public void setExpires(String expires) {
+ if (expires != null) {
+ try {
+ this.expires = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH).parse(expires);
+ } catch (ParseException e) {
+ this.expires = null;
+ }
+ } else {
+ this.expires = null;
+ }
+ }
+
+ public Long getVisitCount() {
+ return visitCount;
+ }
+
+ public void setVisitCount(Long visitCount) {
+ this.visitCount = visitCount;
+ }
+
+ public List<Entry> getEntries() {
+ return this.entries;
+ }
+
+ public void addEntry(Entry entry) {
+ entries.add(entry);
+ }
+ }
diff --git a/subsonic-android/src/github/daneren2005/dsub/fragments/ChatFragment.java b/subsonic-android/src/github/daneren2005/dsub/fragments/ChatFragment.java new file mode 100644 index 00000000..4f373442 --- /dev/null +++ b/subsonic-android/src/github/daneren2005/dsub/fragments/ChatFragment.java @@ -0,0 +1,223 @@ +package github.daneren2005.dsub.fragments;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import android.os.Bundle;
+import android.os.Handler;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.ListView;
+import android.widget.TextView;
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.domain.ChatMessage;
+import github.daneren2005.dsub.service.MusicService;
+import github.daneren2005.dsub.service.MusicServiceFactory;
+import github.daneren2005.dsub.util.BackgroundTask;
+import github.daneren2005.dsub.util.TabBackgroundTask;
+import github.daneren2005.dsub.util.Util;
+import github.daneren2005.dsub.view.ChatAdapter;
+import com.actionbarsherlock.view.Menu;
+import github.daneren2005.dsub.util.Constants;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author Joshua Bahnsen
+ */
+public class ChatFragment extends SubsonicFragment {
+ private static final String TAG = ChatFragment.class.getSimpleName();
+ private ListView chatListView;
+ private EditText messageEditText;
+ private ImageButton sendButton;
+ private Long lastChatMessageTime = (long) 0;
+ private ArrayList<ChatMessage> messageList = new ArrayList<ChatMessage>();
+ private ScheduledExecutorService executorService;
+
+ @Override
+ public void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
+ rootView = inflater.inflate(R.layout.chat, container, false);
+
+ messageEditText = (EditText) rootView.findViewById(R.id.chat_edittext);
+ sendButton = (ImageButton) rootView.findViewById(R.id.chat_send);
+
+ sendButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ sendMessage();
+ }
+ });
+
+ chatListView = (ListView) rootView.findViewById(R.id.chat_entries);
+
+ messageEditText.setImeActionLabel("Send", KeyEvent.KEYCODE_ENTER);
+ messageEditText.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable editable) {
+ sendButton.setEnabled(!Util.isNullOrWhiteSpace(editable.toString()));
+ }
+ });
+
+ messageEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (actionId == EditorInfo.IME_ACTION_DONE || (actionId == EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_DOWN)) {
+ sendMessage();
+ return true;
+ }
+
+ return false;
+ }
+ });
+
+ invalidated = true;
+ return rootView;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ final Handler handler = new Handler();
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ if(primaryFragment) {
+ load(false);
+ } else {
+ invalidated = true;
+ }
+ }
+ });
+ }
+ };
+
+ SharedPreferences prefs = Util.getPreferences(context);
+ long refreshRate = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_CHAT_REFRESH, "30"));
+ if(refreshRate > 0) {
+ executorService = Executors.newSingleThreadScheduledExecutor();
+ executorService.scheduleWithFixedDelay(runnable, refreshRate * 1000L, refreshRate * 1000L, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if(executorService != null) {
+ executorService.shutdown();
+ executorService = null;
+ }
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, com.actionbarsherlock.view.MenuInflater menuInflater) {
+ menuInflater.inflate(R.menu.chat, menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(com.actionbarsherlock.view.MenuItem item) {
+ if(super.onOptionsItemSelected(item)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ protected void refresh(boolean refresh) {
+ load(refresh);
+ }
+
+ private synchronized void load(final boolean refresh) {
+ Log.i(TAG, "Loading: " + refresh);
+ setTitle(R.string.button_bar_chat);
+ BackgroundTask<List<ChatMessage>> task = new TabBackgroundTask<List<ChatMessage>>(this) {
+ @Override
+ protected List<ChatMessage> doInBackground() throws Throwable {
+ MusicService musicService = MusicServiceFactory.getMusicService(context);
+ return musicService.getChatMessages(refresh ? 0L : lastChatMessageTime, context, this);
+ }
+
+ @Override
+ protected void done(List<ChatMessage> result) {
+ if (result != null && !result.isEmpty()) {
+ if(refresh) {
+ messageList.clear();
+ }
+
+ // Reset lastChatMessageTime if we have a newer message
+ for (ChatMessage message : result) {
+ if (message.getTime() > lastChatMessageTime) {
+ lastChatMessageTime = message.getTime();
+ }
+ }
+
+ // Reverse results to show them on the bottom
+ Collections.reverse(result);
+ messageList.addAll(result);
+
+ ChatAdapter chatAdapter = new ChatAdapter(context, messageList);
+ chatListView.setAdapter(chatAdapter);
+ }
+ }
+ };
+
+ task.execute();
+ }
+
+ private void sendMessage() {
+ final String message = messageEditText.getText().toString();
+
+ if (!Util.isNullOrWhiteSpace(message)) {
+ messageEditText.setText("");
+ InputMethodManager mgr = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
+ mgr.hideSoftInputFromWindow(messageEditText.getWindowToken(), 0);
+
+ BackgroundTask<Void> task = new TabBackgroundTask<Void>(this) {
+ @Override
+ protected Void doInBackground() throws Throwable {
+ MusicService musicService = MusicServiceFactory.getMusicService(context);
+ musicService.addChatMessage(message, context, this);
+ return null;
+ }
+
+ @Override
+ protected void done(Void result) {
+ load(false);
+ }
+ };
+
+ task.execute();
+ }
+ }
+}
\ No newline at end of file diff --git a/subsonic-android/src/github/daneren2005/dsub/fragments/DownloadFragment.java b/subsonic-android/src/github/daneren2005/dsub/fragments/DownloadFragment.java index c745e6d2..9113cd74 100644 --- a/subsonic-android/src/github/daneren2005/dsub/fragments/DownloadFragment.java +++ b/subsonic-android/src/github/daneren2005/dsub/fragments/DownloadFragment.java @@ -58,6 +58,7 @@ import java.util.ArrayList; import java.util.concurrent.ScheduledFuture;
import com.mobeta.android.dslv.*;
import github.daneren2005.dsub.activity.EqualizerActivity;
+import github.daneren2005.dsub.activity.MainActivity;
import github.daneren2005.dsub.activity.SubsonicActivity;
public class DownloadFragment extends SubsonicFragment implements OnGestureListener {
@@ -518,10 +519,19 @@ public class DownloadFragment extends SubsonicFragment implements OnGestureListe private boolean menuItemSelected(int menuItemId, final DownloadFile song) {
switch (menuItemId) {
case R.id.menu_show_album:
- /*Intent intent = new Intent(context, SelectAlbumActivity.class);
- intent.putExtra(Constants.INTENT_EXTRA_NAME_ID, song.getSong().getParent());
- intent.putExtra(Constants.INTENT_EXTRA_NAME_NAME, song.getSong().getAlbum());
- Util.startActivityWithoutTransition(context, intent);*/
+ MusicDirectory.Entry entry = song.getSong();
+
+ Intent intent = new Intent(context, MainActivity.class);
+ intent.putExtra(Constants.INTENT_EXTRA_VIEW_ALBUM, true);
+ intent.putExtra(Constants.INTENT_EXTRA_NAME_ID, entry.getParent());
+ intent.putExtra(Constants.INTENT_EXTRA_NAME_NAME, entry.getAlbum());
+
+ if(entry.getGrandParent() != null) {
+ intent.putExtra(Constants.INTENT_EXTRA_NAME_PARENT_ID, entry.getGrandParent());
+ intent.putExtra(Constants.INTENT_EXTRA_NAME_PARENT_NAME, entry.getArtist());
+ }
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ Util.startActivityWithoutTransition(context, intent);
return true;
case R.id.menu_lyrics:
SubsonicFragment fragment = new LyricsFragment();
@@ -653,9 +663,6 @@ public class DownloadFragment extends SubsonicFragment implements OnGestureListe playlistFlipper.setDisplayedChild(1);
}
- onDownloadListChanged();
- onCurrentChanged();
- onProgressChanged();
scrollToCurrent();
if (downloadService != null && downloadService.getKeepScreenOn()) {
context.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
diff --git a/subsonic-android/src/github/daneren2005/dsub/fragments/MainFragment.java b/subsonic-android/src/github/daneren2005/dsub/fragments/MainFragment.java index 3ccd8a0b..bbe3c507 100644 --- a/subsonic-android/src/github/daneren2005/dsub/fragments/MainFragment.java +++ b/subsonic-android/src/github/daneren2005/dsub/fragments/MainFragment.java @@ -32,9 +32,7 @@ public class MainFragment extends SubsonicFragment { private LayoutInflater inflater;
private static final int MENU_GROUP_SERVER = 10;
- private static final int MENU_ITEM_SERVER_1 = 101;
- private static final int MENU_ITEM_SERVER_2 = 102;
- private static final int MENU_ITEM_SERVER_3 = 103;
+ private static final int MENU_ITEM_SERVER_BASE = 100;
@Override
public void onCreate(Bundle bundle) {
@@ -90,24 +88,17 @@ public class MainFragment extends SubsonicFragment { @Override
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, view, menuInfo);
-
- android.view.MenuItem menuItem1 = menu.add(MENU_GROUP_SERVER, MENU_ITEM_SERVER_1, MENU_ITEM_SERVER_1, Util.getServerName(context, 1));
- android.view.MenuItem menuItem2 = menu.add(MENU_GROUP_SERVER, MENU_ITEM_SERVER_2, MENU_ITEM_SERVER_2, Util.getServerName(context, 2));
- android.view.MenuItem menuItem3 = menu.add(MENU_GROUP_SERVER, MENU_ITEM_SERVER_3, MENU_ITEM_SERVER_3, Util.getServerName(context, 3));
+
+ int serverCount = Util.getServerCount(context);
+ int activeServer = Util.getActiveServer(context);
+ for(int i = 1; i <= serverCount; i++) {
+ android.view.MenuItem menuItem = menu.add(MENU_GROUP_SERVER, MENU_ITEM_SERVER_BASE + i, MENU_ITEM_SERVER_BASE + i, Util.getServerName(context, i));
+ if(i == activeServer) {
+ menuItem.setChecked(true);
+ }
+ }
menu.setGroupCheckable(MENU_GROUP_SERVER, true, true);
menu.setHeaderTitle(R.string.main_select_server);
-
- switch (Util.getActiveServer(context)) {
- case 1:
- menuItem1.setChecked(true);
- break;
- case 2:
- menuItem2.setChecked(true);
- break;
- case 3:
- menuItem3.setChecked(true);
- break;
- }
}
@Override
@@ -116,20 +107,8 @@ public class MainFragment extends SubsonicFragment { return false;
}
- switch (menuItem.getItemId()) {
- case MENU_ITEM_SERVER_1:
- setActiveServer(1);
- break;
- case MENU_ITEM_SERVER_2:
- setActiveServer(2);
- break;
- case MENU_ITEM_SERVER_3:
- setActiveServer(3);
- break;
- default:
- return super.onContextItemSelected(menuItem);
- }
-
+ int activeServer = menuItem.getItemId() - MENU_ITEM_SERVER_BASE;
+ setActiveServer(activeServer);
context.getPagerAdapter().invalidate();
return true;
}
@@ -154,6 +133,7 @@ public class MainFragment extends SubsonicFragment { final View albumsRecentButton = buttons.findViewById(R.id.main_albums_recent);
final View albumsFrequentButton = buttons.findViewById(R.id.main_albums_frequent);
final View albumsStarredButton = buttons.findViewById(R.id.main_albums_starred);
+ final View albumsGenresButton = buttons.findViewById(R.id.main_albums_genres);
final View dummyView = rootView.findViewById(R.id.main_dummy);
@@ -170,7 +150,7 @@ public class MainFragment extends SubsonicFragment { adapter.addView(offlineButton, true);
if (!Util.isOffline(context)) {
adapter.addView(albumsTitle, false);
- adapter.addViews(Arrays.asList(albumsNewestButton, albumsRandomButton, albumsHighestButton, albumsStarredButton, albumsRecentButton, albumsFrequentButton), true);
+ adapter.addViews(Arrays.asList(albumsNewestButton, albumsRandomButton, albumsHighestButton, albumsStarredButton, albumsGenresButton, albumsRecentButton, albumsFrequentButton), true);
}
list.setAdapter(adapter);
registerForContextMenu(dummyView);
@@ -194,6 +174,8 @@ public class MainFragment extends SubsonicFragment { showAlbumList("frequent");
} else if (view == albumsStarredButton) {
showAlbumList("starred");
+ } else if(view == albumsGenresButton) {
+ showAlbumList("genres");
}
}
});
@@ -215,14 +197,19 @@ public class MainFragment extends SubsonicFragment { }
private void showAlbumList(String type) {
- SubsonicFragment fragment = new SelectDirectoryFragment();
- Bundle args = new Bundle();
- args.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, type);
- args.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 20);
- args.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0);
- fragment.setArguments(args);
-
- replaceFragment(fragment, R.id.home_layout);
+ if("genres".equals(type)) {
+ SubsonicFragment fragment = new SelectGenreFragment();
+ replaceFragment(fragment, R.id.home_layout);
+ } else {
+ SubsonicFragment fragment = new SelectDirectoryFragment();
+ Bundle args = new Bundle();
+ args.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, type);
+ args.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 20);
+ args.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0);
+ fragment.setArguments(args);
+
+ replaceFragment(fragment, R.id.home_layout);
+ }
}
private void showAboutDialog() {
diff --git a/subsonic-android/src/github/daneren2005/dsub/fragments/SelectArtistFragment.java b/subsonic-android/src/github/daneren2005/dsub/fragments/SelectArtistFragment.java index 8485f342..0a35233e 100644 --- a/subsonic-android/src/github/daneren2005/dsub/fragments/SelectArtistFragment.java +++ b/subsonic-android/src/github/daneren2005/dsub/fragments/SelectArtistFragment.java @@ -58,7 +58,11 @@ public class SelectArtistFragment extends SubsonicFragment implements AdapterVie folderButton = folderButtonParent.findViewById(R.id.select_artist_folder);
registerForContextMenu(artistList);
- invalidated = true;
+ if(!primaryFragment) {
+ invalidated = true;
+ } else {
+ refresh(false);
+ }
return rootView;
}
diff --git a/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java b/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java index 084269ee..13f61625 100644 --- a/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java +++ b/subsonic-android/src/github/daneren2005/dsub/fragments/SelectDirectoryFragment.java @@ -3,15 +3,11 @@ package github.daneren2005.dsub.fragments; import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
-import android.support.v4.app.FragmentTransaction;
import android.util.Log;
import android.view.ContextMenu;
import android.view.LayoutInflater;
-import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
@@ -26,19 +22,16 @@ import java.util.List; import com.mobeta.android.dslv.*;
import github.daneren2005.dsub.activity.DownloadActivity;
import github.daneren2005.dsub.activity.SearchActivity;
-import github.daneren2005.dsub.service.DownloadFile;
import github.daneren2005.dsub.service.MusicService;
import github.daneren2005.dsub.service.MusicServiceFactory;
import github.daneren2005.dsub.service.OfflineException;
import github.daneren2005.dsub.service.ServerTooOldException;
import github.daneren2005.dsub.util.Constants;
-import github.daneren2005.dsub.util.FileUtil;
import github.daneren2005.dsub.util.LoadingTask;
import github.daneren2005.dsub.util.Pair;
import github.daneren2005.dsub.util.TabBackgroundTask;
import github.daneren2005.dsub.util.Util;
import github.daneren2005.dsub.view.AlbumListAdapter;
-import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
@@ -62,6 +55,7 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter String playlistId;
String playlistName;
String albumListType;
+ String albumListExtra;
int albumListSize;
@Override
@@ -107,6 +101,7 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter playlistId = args.getString(Constants.INTENT_EXTRA_NAME_PLAYLIST_ID);
playlistName = args.getString(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME);
albumListType = args.getString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE);
+ albumListExtra = args.getString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_EXTRA);
albumListSize = args.getInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 0);
}
if(primaryFragment) {
@@ -320,6 +315,8 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter setTitle(R.string.main_albums_frequent);
} else if ("starred".equals(albumListType)) {
setTitle(R.string.main_albums_starred);
+ } else if("genres".equals(albumListType)) {
+ setTitle(albumListExtra);
}
new LoadTask() {
@@ -328,6 +325,8 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter MusicDirectory result;
if ("starred".equals(albumListType)) {
result = service.getStarredList(context, this);
+ } else if("genres".equals(albumListType)) {
+ result = service.getSongsByGenre(albumListExtra, size, 0, context, this);
} else {
result = service.getAlbumList(albumListType, size, 0, context, this);
}
@@ -380,7 +379,7 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter if(albumListType == null || "starred".equals(albumListType)) {
entryList.setAdapter(entryAdapter);
} else {
- entryList.setAdapter(new AlbumListAdapter(context, entryAdapter, albumListType, albumListSize));
+ entryList.setAdapter(new AlbumListAdapter(context, entryAdapter, albumListType, albumListExtra, albumListSize));
}
entryList.setVisibility(View.VISIBLE);
licenseValid = result.getSecond();
@@ -646,7 +645,7 @@ public class SelectDirectoryFragment extends SubsonicFragment implements Adapter }
View coverArtView = header.findViewById(R.id.select_album_art);
- getImageLoader().loadImage(coverArtView, entries.get(random.nextInt(entries.size())), true, true);
+ getImageLoader().loadImage(coverArtView, entries.get(random.nextInt(entries.size())), false, true);
TextView titleView = (TextView) header.findViewById(R.id.select_album_title);
if(playlistName != null) {
diff --git a/subsonic-android/src/github/daneren2005/dsub/fragments/SelectGenreFragment.java b/subsonic-android/src/github/daneren2005/dsub/fragments/SelectGenreFragment.java new file mode 100644 index 00000000..f4b0f213 --- /dev/null +++ b/subsonic-android/src/github/daneren2005/dsub/fragments/SelectGenreFragment.java @@ -0,0 +1,141 @@ +/*
+ This file is part of Subsonic.
+
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2010 (C) Sindre Mehus
+ */
+package github.daneren2005.dsub.fragments;
+
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ListView;
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.domain.Genre;
+import github.daneren2005.dsub.service.MusicService;
+import github.daneren2005.dsub.service.MusicServiceFactory;
+import github.daneren2005.dsub.util.BackgroundTask;
+import github.daneren2005.dsub.util.Constants;
+import github.daneren2005.dsub.util.TabBackgroundTask;
+import github.daneren2005.dsub.view.GenreAdapter;
+import com.actionbarsherlock.view.Menu;
+import com.actionbarsherlock.view.MenuItem;
+import com.actionbarsherlock.view.MenuInflater;
+import java.util.ArrayList;
+import java.util.List;
+
+public class SelectGenreFragment extends SubsonicFragment implements AdapterView.OnItemClickListener {
+ private static final String TAG = SelectGenreFragment.class.getSimpleName();
+ private ListView genreListView;
+ private View emptyView;
+
+ @Override
+ public void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
+ rootView = inflater.inflate(R.layout.select_genres, container, false);
+
+ genreListView = (ListView)rootView.findViewById(R.id.select_genre_list);
+ genreListView.setOnItemClickListener(this);
+ emptyView = rootView.findViewById(R.id.select_genre_empty);
+ refresh();
+
+ return rootView;
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) {
+ menuInflater.inflate(R.menu.select_genres, menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if(super.onOptionsItemSelected(item)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public void setPrimaryFragment(boolean primary) {
+ super.setPrimaryFragment(primary);
+ if(rootView != null) {
+ if(primary) {
+ ((ViewGroup)rootView).getChildAt(0).setVisibility(View.VISIBLE);
+ } else {
+ ((ViewGroup)rootView).getChildAt(0).setVisibility(View.GONE);
+ }
+ }
+ }
+
+ @Override
+ protected void refresh(boolean refresh) {
+ load();
+ }
+
+ private void load() {
+ setTitle(R.string.main_albums_genres);
+
+ BackgroundTask<List<Genre>> task = new TabBackgroundTask<List<Genre>>(this) {
+ @Override
+ protected List<Genre> doInBackground() throws Throwable {
+ MusicService musicService = MusicServiceFactory.getMusicService(context);
+
+ List<Genre> genres = new ArrayList<Genre>();
+
+ try {
+ genres = musicService.getGenres(context, this);
+ } catch (Exception x) {
+ Log.e(TAG, "Failed to load genres", x);
+ }
+
+ return genres;
+ }
+
+ @Override
+ protected void done(List<Genre> result) {
+ emptyView.setVisibility(result == null || result.isEmpty() ? View.VISIBLE : View.GONE);
+
+ if (result != null) {
+ genreListView.setAdapter(new GenreAdapter(context, result));
+ }
+
+ }
+ };
+ task.execute();
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ Genre genre = (Genre) parent.getItemAtPosition(position);
+
+ SubsonicFragment fragment = new SelectDirectoryFragment();
+ Bundle args = new Bundle();
+ args.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, "genres");
+ args.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, 20);
+ args.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0);
+ args.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_EXTRA, genre.getName());
+ fragment.setArguments(args);
+
+ replaceFragment(fragment, R.id.select_genre_layout);
+ }
+}
diff --git a/subsonic-android/src/github/daneren2005/dsub/fragments/SelectPlaylistFragment.java b/subsonic-android/src/github/daneren2005/dsub/fragments/SelectPlaylistFragment.java index 1369eef0..aa0ffcf8 100644 --- a/subsonic-android/src/github/daneren2005/dsub/fragments/SelectPlaylistFragment.java +++ b/subsonic-android/src/github/daneren2005/dsub/fragments/SelectPlaylistFragment.java @@ -51,7 +51,11 @@ public class SelectPlaylistFragment extends SubsonicFragment implements AdapterV emptyTextView = rootView.findViewById(R.id.select_playlist_empty);
list.setOnItemClickListener(this);
registerForContextMenu(list);
- invalidated = true;
+ if(!primaryFragment) {
+ invalidated = true;
+ } else {
+ refresh(false);
+ }
return rootView;
}
diff --git a/subsonic-android/src/github/daneren2005/dsub/fragments/SubsonicFragment.java b/subsonic-android/src/github/daneren2005/dsub/fragments/SubsonicFragment.java index 35a272eb..05b78eab 100644 --- a/subsonic-android/src/github/daneren2005/dsub/fragments/SubsonicFragment.java +++ b/subsonic-android/src/github/daneren2005/dsub/fragments/SubsonicFragment.java @@ -35,6 +35,7 @@ import android.view.MenuInflater; import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
+import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.actionbarsherlock.app.SherlockFragment;
@@ -46,8 +47,10 @@ import github.daneren2005.dsub.activity.SearchActivity; import github.daneren2005.dsub.activity.SettingsActivity;
import github.daneren2005.dsub.activity.SubsonicActivity;
import github.daneren2005.dsub.domain.Artist;
+import github.daneren2005.dsub.domain.Genre;
import github.daneren2005.dsub.domain.MusicDirectory;
import github.daneren2005.dsub.domain.Playlist;
+import github.daneren2005.dsub.domain.Version;
import github.daneren2005.dsub.service.DownloadFile;
import github.daneren2005.dsub.service.DownloadService;
import github.daneren2005.dsub.service.DownloadServiceImpl;
@@ -379,15 +382,70 @@ public class SubsonicFragment extends SherlockFragment { final EditText startYearBox = (EditText)dialogView.findViewById(R.id.start_year);
final EditText endYearBox = (EditText)dialogView.findViewById(R.id.end_year);
final EditText genreBox = (EditText)dialogView.findViewById(R.id.genre);
+ final Button genreCombo = (Button)dialogView.findViewById(R.id.genre_combo);
final SharedPreferences prefs = Util.getPreferences(context);
final String oldStartYear = prefs.getString(Constants.PREFERENCES_KEY_SHUFFLE_START_YEAR, "");
final String oldEndYear = prefs.getString(Constants.PREFERENCES_KEY_SHUFFLE_END_YEAR, "");
final String oldGenre = prefs.getString(Constants.PREFERENCES_KEY_SHUFFLE_GENRE, "");
+
+ Version version = Util.getServerRestVersion(context);
+ Version genreVersion = new Version("1.9.0");
+ boolean _useCombo = false;
+ if(version.compareTo(genreVersion) >= 0) {
+ genreBox.setVisibility(View.GONE);
+ genreCombo.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ new LoadingTask<List<Genre>>(context, true) {
+ @Override
+ protected List<Genre> doInBackground() throws Throwable {
+ MusicService musicService = MusicServiceFactory.getMusicService(context);
+ return musicService.getGenres(context, this);
+ }
+
+ @Override
+ protected void done(final List<Genre> genres) {
+ List<String> names = new ArrayList<String>();
+ for(Genre genre: genres) {
+ names.add(genre.getName());
+ }
+ final List<String> finalNames = names;
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.shuffle_pick_genre)
+ .setItems(names.toArray(new CharSequence[names.size()]), new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ genreCombo.setText(finalNames.get(which));
+ }
+ });
+ AlertDialog dialog = builder.create();
+ dialog.show();
+ }
+
+ @Override
+ protected void error(Throwable error) {
+ String msg;
+ if (error instanceof OfflineException || error instanceof ServerTooOldException) {
+ msg = getErrorMessage(error);
+ } else {
+ msg = context.getResources().getString(R.string.playlist_error) + " " + getErrorMessage(error);
+ }
+
+ Util.toast(context, msg, false);
+ }
+ }.execute();
+ }
+ });
+ _useCombo = true;
+ } else {
+ genreCombo.setVisibility(View.GONE);
+ }
+ final boolean useCombo = _useCombo;
startYearBox.setText(oldStartYear);
endYearBox.setText(oldEndYear);
genreBox.setText(oldGenre);
+ genreCombo.setText(oldGenre);
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("Shuffle By")
@@ -397,7 +455,12 @@ public class SubsonicFragment extends SherlockFragment { public void onClick(DialogInterface dialog, int id) {
Intent intent = new Intent(context, DownloadActivity.class);
intent.putExtra(Constants.INTENT_EXTRA_NAME_SHUFFLE, true);
- String genre = genreBox.getText().toString();
+ String genre;
+ if(useCombo) {
+ genre = genreCombo.getText().toString();
+ } else {
+ genre = genreBox.getText().toString();
+ }
String startYear = startYearBox.getText().toString();
String endYear = endYearBox.getText().toString();
diff --git a/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java index 32807289..71bbb867 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/CachedMusicService.java @@ -25,15 +25,17 @@ import org.apache.http.HttpResponse; import android.content.Context; import android.graphics.Bitmap; +import github.daneren2005.dsub.domain.ChatMessage; +import github.daneren2005.dsub.domain.Genre; import github.daneren2005.dsub.domain.Indexes; import github.daneren2005.dsub.domain.JukeboxStatus; import github.daneren2005.dsub.domain.Lyrics; import github.daneren2005.dsub.domain.MusicDirectory; -import github.daneren2005.dsub.domain.MusicDirectory.Entry; import github.daneren2005.dsub.domain.MusicFolder; import github.daneren2005.dsub.domain.Playlist; import github.daneren2005.dsub.domain.SearchCritera; import github.daneren2005.dsub.domain.SearchResult; +import github.daneren2005.dsub.domain.Share; import github.daneren2005.dsub.domain.Version; import github.daneren2005.dsub.util.CancellableTask; import github.daneren2005.dsub.util.LRUCache; @@ -55,6 +57,7 @@ public class CachedMusicService implements MusicService { private final TimeLimitedCache<Indexes> cachedIndexes = new TimeLimitedCache<Indexes>(60 * 60, TimeUnit.SECONDS); private final TimeLimitedCache<List<Playlist>> cachedPlaylists = new TimeLimitedCache<List<Playlist>>(3600, TimeUnit.SECONDS); private final TimeLimitedCache<List<MusicFolder>> cachedMusicFolders = new TimeLimitedCache<List<MusicFolder>>(10 * 3600, TimeUnit.SECONDS); + private final TimeLimitedCache<List<Genre>> cachedGenres = new TimeLimitedCache<List<Genre>>(10 * 3600, TimeUnit.SECONDS); private String restUrl; public CachedMusicService(MusicService musicService) { @@ -259,6 +262,39 @@ public class CachedMusicService implements MusicService { public void setStarred(String id, boolean starred, Context context, ProgressListener progressListener) throws Exception { musicService.setStarred(id, starred, context, progressListener); } + + @Override + public List<Share> getShares(Context context, ProgressListener progressListener) throws Exception { + return musicService.getShares(context, progressListener); + } + + @Override + public List<ChatMessage> getChatMessages(Long since, Context context, ProgressListener progressListener) throws Exception { + return musicService.getChatMessages(since, context, progressListener); + } + + @Override + public void addChatMessage(String message, Context context, ProgressListener progressListener) throws Exception { + musicService.addChatMessage(message, context, progressListener); + } + + @Override + public List<Genre> getGenres(Context context, ProgressListener progressListener) throws Exception { + checkSettingsChanged(context); + List<Genre> result = cachedGenres.get(); + + if (result == null) { + result = musicService.getGenres(context, progressListener); + cachedGenres.set(result); + } + + return result; + } + + @Override + public MusicDirectory getSongsByGenre(String genre, int count, int offset, Context context, ProgressListener progressListener) throws Exception { + return musicService.getSongsByGenre(genre, count, offset, context, progressListener); + } private void checkSettingsChanged(Context context) { String newUrl = Util.getRestUrl(context, null); diff --git a/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java b/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java index fabeba6f..f659a434 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java @@ -209,6 +209,8 @@ public class DownloadServiceImpl extends Service implements DownloadService { @Override public void onDestroy() { super.onDestroy(); + instance = null; + if(currentPlaying != null) currentPlaying.setPlaying(false); if(sleepTimer != null){ sleepTimer.cancel(); @@ -249,8 +251,6 @@ public class DownloadServiceImpl extends Service implements DownloadService { nextPlayingTask.cancel(); } Util.hidePlayingNotification(this, this, handler); - - instance = null; } public static DownloadService getInstance() { @@ -264,7 +264,7 @@ public class DownloadServiceImpl extends Service implements DownloadService { @Override public synchronized void download(List<MusicDirectory.Entry> songs, boolean save, boolean autoplay, boolean playNext, boolean shuffle) { - shufflePlay = false; + setShufflePlayEnabled(false); int offset = 1; if (songs.isEmpty()) { @@ -320,8 +320,20 @@ public class DownloadServiceImpl extends Service implements DownloadService { } public void restore(List<MusicDirectory.Entry> songs, int currentPlayingIndex, int currentPlayingPosition) { + SharedPreferences prefs = Util.getPreferences(this); + boolean startShufflePlay = prefs.getBoolean(Constants.PREFERENCES_KEY_SHUFFLE_MODE, false); download(songs, false, false, false, false); + if(startShufflePlay) { + shufflePlay = true; + SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean(Constants.PREFERENCES_KEY_SHUFFLE_MODE, true); + editor.commit(); + } if (currentPlayingIndex != -1) { + while(mediaPlayer == null) { + Util.sleepQuietly(50L); + } + play(currentPlayingIndex, autoPlayStart); if (currentPlaying != null && currentPlaying.isCompleteFileAvailable()) { doPlay(currentPlaying, currentPlayingPosition, autoPlayStart); @@ -337,6 +349,9 @@ public class DownloadServiceImpl extends Service implements DownloadService { clear(); checkDownloads(); } + SharedPreferences.Editor editor = Util.getPreferences(this).edit(); + editor.putBoolean(Constants.PREFERENCES_KEY_SHUFFLE_MODE, enabled); + editor.commit(); } @Override diff --git a/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java index de3046d7..31e9c23c 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/MusicService.java @@ -24,6 +24,8 @@ import org.apache.http.HttpResponse; import android.content.Context; import android.graphics.Bitmap; +import github.daneren2005.dsub.domain.ChatMessage; +import github.daneren2005.dsub.domain.Genre; import github.daneren2005.dsub.domain.Indexes; import github.daneren2005.dsub.domain.JukeboxStatus; import github.daneren2005.dsub.domain.Lyrics; @@ -32,6 +34,7 @@ import github.daneren2005.dsub.domain.MusicFolder; import github.daneren2005.dsub.domain.Playlist; import github.daneren2005.dsub.domain.SearchCritera; import github.daneren2005.dsub.domain.SearchResult; +import github.daneren2005.dsub.domain.Share; import github.daneren2005.dsub.domain.Version; import github.daneren2005.dsub.util.CancellableTask; import github.daneren2005.dsub.util.ProgressListener; @@ -102,4 +105,14 @@ public interface MusicService { JukeboxStatus setJukeboxGain(float gain, Context context, ProgressListener progressListener) throws Exception; void setStarred(String id, boolean starred, Context context, ProgressListener progressListener) throws Exception; + + List<Share> getShares(Context context, ProgressListener progressListener) throws Exception; + + List<ChatMessage> getChatMessages(Long since, Context context, ProgressListener progressListener) throws Exception; + + void addChatMessage(String message, Context context, ProgressListener progressListener) throws Exception; + + List<Genre> getGenres(Context context, ProgressListener progressListener) throws Exception; + + public MusicDirectory getSongsByGenre(String genre, int count, int offset, Context context, ProgressListener progressListener) throws Exception; }
\ No newline at end of file diff --git a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java index 5f9aaf96..97fed19f 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/OfflineMusicService.java @@ -32,11 +32,13 @@ import java.util.Random; import java.util.Set; import android.content.Context; +import android.content.SharedPreferences; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.media.MediaMetadataRetriever; import android.util.Log; import github.daneren2005.dsub.domain.Artist; +import github.daneren2005.dsub.domain.Genre; import github.daneren2005.dsub.domain.Indexes; import github.daneren2005.dsub.domain.JukeboxStatus; import github.daneren2005.dsub.domain.Lyrics; @@ -78,6 +80,10 @@ public class OfflineMusicService extends RESTMusicService { } } + SharedPreferences prefs = Util.getPreferences(context); + String ignoredArticlesString = prefs.getString(Constants.CACHE_KEY_IGNORE, "The El La Los Las Le Les"); + final String[] ignoredArticles = ignoredArticlesString.split(" "); + Collections.sort(artists, new Comparator<Artist>() { public int compare(Artist lhsArtist, Artist rhsArtist) { String lhs = lhsArtist.getName().toLowerCase(); @@ -92,13 +98,15 @@ public class OfflineMusicService extends RESTMusicService { return -1; } - int index = lhs.indexOf("The "); - if(index == 0) { - lhs = lhs.substring(4, 0); - } - index = rhs.indexOf("The "); - if(index == 0) { - rhs = rhs.substring(4, 0); + for(String article: ignoredArticles) { + int index = lhs.indexOf(article.toLowerCase()); + if(index == 0) { + lhs = lhs.substring(article.length() + 1); + } + index = rhs.indexOf(article.toLowerCase()); + if(index == 0) { + rhs = rhs.substring(article.length() + 1); + } } return lhs.compareTo(rhs); @@ -123,6 +131,7 @@ public class OfflineMusicService extends RESTMusicService { result.addChild(createEntry(context, file, name)); } } + result.sortChildren(); return result; } @@ -162,6 +171,27 @@ public class OfflineMusicService extends RESTMusicService { // Failed parseInt, just means track filled out } } + + try { + MediaMetadataRetriever metadata = new MediaMetadataRetriever(); + metadata.setDataSource(file.getAbsolutePath()); + String discNumber = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER); + if(discNumber == null) { + discNumber = "1/1"; + } + int slashIndex = discNumber.indexOf("/"); + if(slashIndex > 0) { + discNumber = discNumber.substring(0, slashIndex); + } + entry.setDiscNumber(Integer.parseInt(discNumber)); + String bitrate = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE); + entry.setBitRate(Integer.parseInt((bitrate != null) ? bitrate : "0") / 1000); + String length = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); + entry.setDuration(Integer.parseInt(length) / 1000); + metadata.release(); + } catch(Exception e) { + Log.i(TAG, "Device doesn't properly support MediaMetadataRetreiver"); + } } entry.setTitle(title); @@ -451,6 +481,16 @@ public class OfflineMusicService extends RESTMusicService { public void setStarred(String id, boolean starred, Context context, ProgressListener progressListener) throws Exception { throw new OfflineException("Starring not available in offline mode"); } + + @Override + public List<Genre> getGenres(Context context, ProgressListener progressListener) throws Exception { + throw new OfflineException("Getting Genres not available in offline mode"); + } + + @Override + public MusicDirectory getSongsByGenre(String genre, int count, int offset, Context context, ProgressListener progressListener) throws Exception { + throw new OfflineException("Getting Songs By Genre not available in offline mode"); + } @Override public MusicDirectory getRandomSongs(int size, String folder, String genre, String startYear, String endYear, Context context, ProgressListener progressListener) throws Exception { diff --git a/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java b/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java index 24dcbabd..5fc66190 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/RESTMusicService.java @@ -73,7 +73,9 @@ import github.daneren2005.dsub.R; import github.daneren2005.dsub.domain.*; import github.daneren2005.dsub.domain.MusicDirectory.Entry; import github.daneren2005.dsub.service.parser.AlbumListParser; +import github.daneren2005.dsub.service.parser.ChatMessageParser; import github.daneren2005.dsub.service.parser.ErrorParser; +import github.daneren2005.dsub.service.parser.GenreParser; import github.daneren2005.dsub.service.parser.IndexesParser; import github.daneren2005.dsub.service.parser.JukeboxStatusParser; import github.daneren2005.dsub.service.parser.LicenseParser; @@ -85,6 +87,7 @@ import github.daneren2005.dsub.service.parser.PlaylistsParser; import github.daneren2005.dsub.service.parser.RandomSongsParser; import github.daneren2005.dsub.service.parser.SearchResult2Parser; import github.daneren2005.dsub.service.parser.SearchResultParser; +import github.daneren2005.dsub.service.parser.ShareParser; import github.daneren2005.dsub.service.parser.StarredListParser; import github.daneren2005.dsub.service.parser.VersionParser; import github.daneren2005.dsub.service.ssl.SSLSocketFactory; @@ -729,6 +732,100 @@ public class RESTMusicService implements MusicService { Util.close(reader); } } + + @Override + public List<Share> getShares(Context context, ProgressListener progressListener) throws Exception { + checkServerVersion(context, "1.6", "Shares not supported."); + + Reader reader = getReader(context, progressListener, "getShares", null); + try { + return new ShareParser(context).parse(reader, progressListener); + } finally { + Util.close(reader); + } + } + + @Override + public List<ChatMessage> getChatMessages(Long since, Context context, ProgressListener progressListener) throws Exception { + checkServerVersion(context, "1.2", "Chat not supported."); + + HttpParams params = new BasicHttpParams(); + HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_GET_RANDOM_SONGS); + + List<String> parameterNames = new ArrayList<String>(); + List<Object> parameterValues = new ArrayList<Object>(); + + parameterNames.add("since"); + parameterValues.add(since); + + Reader reader = getReader(context, progressListener, "getChatMessages", params, parameterNames, parameterValues); + + try { + return new ChatMessageParser(context).parse(reader, progressListener); + } finally { + Util.close(reader); + } + } + + @Override + public void addChatMessage(String message, Context context, ProgressListener progressListener) throws Exception { + checkServerVersion(context, "1.2", "Chat not supported."); + + HttpParams params = new BasicHttpParams(); + HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_GET_RANDOM_SONGS); + + List<String> parameterNames = new ArrayList<String>(); + List<Object> parameterValues = new ArrayList<Object>(); + + parameterNames.add("message"); + parameterValues.add(message); + + Reader reader = getReader(context, progressListener, "addChatMessage", params, parameterNames, parameterValues); + + try { + new ErrorParser(context).parse(reader); + } finally { + Util.close(reader); + } + } + + @Override + public List<Genre> getGenres(Context context, ProgressListener progressListener) throws Exception { + checkServerVersion(context, "1.9", "Genres not supported."); + + Reader reader = getReader(context, progressListener, "getGenres", null); + try { + return new GenreParser(context).parse(reader, progressListener); + } finally { + Util.close(reader); + } + } + + @Override + public MusicDirectory getSongsByGenre(String genre, int count, int offset, Context context, ProgressListener progressListener) throws Exception { + checkServerVersion(context, "1.9", "Genres not supported."); + + HttpParams params = new BasicHttpParams(); + HttpConnectionParams.setSoTimeout(params, SOCKET_READ_TIMEOUT_GET_RANDOM_SONGS); + + List<String> parameterNames = new ArrayList<String>(); + List<Object> parameterValues = new ArrayList<Object>(); + + parameterNames.add("genre"); + parameterValues.add(genre); + parameterNames.add("count"); + parameterValues.add(count); + parameterNames.add("offset"); + parameterValues.add(offset); + + Reader reader = getReader(context, progressListener, "getSongsByGenre", params, parameterNames, parameterValues); + + try { + return new RandomSongsParser(context).parse(reader, progressListener); + } finally { + Util.close(reader); + } + } private Reader getReader(Context context, ProgressListener progressListener, String method, HttpParams requestParams) throws Exception { return getReader(context, progressListener, method, requestParams, Collections.<String>emptyList(), Collections.emptyList()); diff --git a/subsonic-android/src/github/daneren2005/dsub/service/parser/ChatMessageParser.java b/subsonic-android/src/github/daneren2005/dsub/service/parser/ChatMessageParser.java new file mode 100644 index 00000000..1425a734 --- /dev/null +++ b/subsonic-android/src/github/daneren2005/dsub/service/parser/ChatMessageParser.java @@ -0,0 +1,67 @@ +/*
+ This file is part of Subsonic.
+
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2009 (C) Sindre Mehus
+ */
+package github.daneren2005.dsub.service.parser;
+
+import android.content.Context;
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.domain.ChatMessage;
+import github.daneren2005.dsub.util.ProgressListener;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Joshua Bahnsen
+ */
+public class ChatMessageParser extends AbstractParser {
+
+ public ChatMessageParser(Context context) {
+ super(context);
+ }
+
+ public List<ChatMessage> parse(Reader reader, ProgressListener progressListener) throws Exception {
+ updateProgress(progressListener, R.string.parser_reading);
+ init(reader);
+ List<ChatMessage> result = new ArrayList<ChatMessage>();
+ int eventType;
+
+ do {
+ eventType = nextParseEvent();
+ if (eventType == XmlPullParser.START_TAG) {
+ String name = getElementName();
+ if ("chatMessage".equals(name)) {
+ ChatMessage chatMessage = new ChatMessage();
+ chatMessage.setUsername(get("username"));
+ chatMessage.setTime(getLong("time"));
+ chatMessage.setMessage(get("message"));
+ result.add(chatMessage);
+ } else if ("error".equals(name)) {
+ handleError();
+ }
+ }
+ } while (eventType != XmlPullParser.END_DOCUMENT);
+
+ validate();
+ updateProgress(progressListener, R.string.parser_reading_done);
+
+ return result;
+ }
+}
diff --git a/subsonic-android/src/github/daneren2005/dsub/service/parser/GenreParser.java b/subsonic-android/src/github/daneren2005/dsub/service/parser/GenreParser.java new file mode 100644 index 00000000..1062d3af --- /dev/null +++ b/subsonic-android/src/github/daneren2005/dsub/service/parser/GenreParser.java @@ -0,0 +1,122 @@ +/*
+ This file is part of Subsonic.
+
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2010 (C) Sindre Mehus
+ */
+package github.daneren2005.dsub.service.parser;
+
+import android.content.Context;
+import android.util.Log;
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.domain.Genre;
+import github.daneren2005.dsub.util.ProgressListener;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Joshua Bahnsen
+ */
+public class GenreParser extends AbstractParser {
+ private static final String TAG = GenreParser.class.getSimpleName();
+
+ public GenreParser(Context context) {
+ super(context);
+ }
+
+ public List<Genre> parse(Reader reader, ProgressListener progressListener) throws Exception {
+ updateProgress(progressListener, R.string.parser_reading);
+
+ List<Genre> result = new ArrayList<Genre>();
+ StringReader sr = null;
+
+ try {
+ BufferedReader br = new BufferedReader(reader);
+ String xml = null;
+ String line = null;
+
+ while ((line = br.readLine()) != null) {
+ if (xml == null) {
+ xml = line;
+ } else {
+ xml += line;
+ }
+ }
+ br.close();
+
+ // Replace double escaped ampersand (&apos;)
+ xml = xml.replaceAll("(?:&)(amp;|lt;|gt;|#37;|apos;)", "&$1");
+
+ // Replace unescaped ampersand
+ xml = xml.replaceAll("&(?!amp;|lt;|gt;|#37;|apos;)", "&");
+
+ // Replace unescaped percent symbol
+ // No replacements for <> at this time
+ xml = xml.replaceAll("%", "%");
+
+ xml = xml.replaceAll("'", "'");
+
+ sr = new StringReader(xml);
+ } catch (IOException ioe) {
+ Log.e(TAG, "Error parsing Genre XML", ioe);
+ }
+
+ if (sr == null) {
+ Log.w(TAG, "Unable to parse Genre XML, returning empty list");
+ return result;
+ }
+
+ init(sr);
+
+ Genre genre = null;
+
+ int eventType;
+ do {
+ eventType = nextParseEvent();
+ if (eventType == XmlPullParser.START_TAG) {
+ String name = getElementName();
+ if ("genre".equals(name)) {
+ genre = new Genre();
+ } else if ("error".equals(name)) {
+ handleError();
+ } else {
+ genre = null;
+ }
+ } else if (eventType == XmlPullParser.TEXT) {
+ if (genre != null) {
+ String value = getText();
+ if (genre != null) {
+ genre.setName(value);
+ genre.setIndex(value.substring(0, 1));
+ result.add(genre);
+ genre = null;
+ }
+ }
+ }
+ } while (eventType != XmlPullParser.END_DOCUMENT);
+
+ validate();
+ updateProgress(progressListener, R.string.parser_reading_done);
+
+ return result;
+ }
+}
diff --git a/subsonic-android/src/github/daneren2005/dsub/service/parser/IndexesParser.java b/subsonic-android/src/github/daneren2005/dsub/service/parser/IndexesParser.java index 4a307812..6196411d 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/parser/IndexesParser.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/parser/IndexesParser.java @@ -25,20 +25,26 @@ import java.util.ArrayList; import org.xmlpull.v1.XmlPullParser; import android.content.Context; +import android.content.SharedPreferences; import github.daneren2005.dsub.R; import github.daneren2005.dsub.domain.Artist; import github.daneren2005.dsub.domain.Indexes; import github.daneren2005.dsub.util.ProgressListener; import android.util.Log; +import github.daneren2005.dsub.util.Constants; +import github.daneren2005.dsub.util.Util; /** * @author Sindre Mehus */ public class IndexesParser extends AbstractParser { private static final String TAG = IndexesParser.class.getSimpleName(); + + private Context context; public IndexesParser(Context context) { super(context); + this.context = context; } public Indexes parse(Reader reader, ProgressListener progressListener) throws Exception { @@ -52,6 +58,7 @@ public class IndexesParser extends AbstractParser { Long lastModified = null; int eventType; String index = "#"; + String ignoredArticles = null; boolean changed = false; do { @@ -61,6 +68,7 @@ public class IndexesParser extends AbstractParser { if ("indexes".equals(name)) { changed = true; lastModified = getLong("lastModified"); + ignoredArticles = get("ignoredArticles"); } else if ("index".equals(name)) { index = get("name"); @@ -90,6 +98,12 @@ public class IndexesParser extends AbstractParser { } while (eventType != XmlPullParser.END_DOCUMENT); validate(); + + if(ignoredArticles != null) { + SharedPreferences.Editor prefs = Util.getPreferences(context).edit(); + prefs.putString(Constants.CACHE_KEY_IGNORE, ignoredArticles); + prefs.commit(); + } if (!changed) { return null; diff --git a/subsonic-android/src/github/daneren2005/dsub/service/parser/MusicDirectoryEntryParser.java b/subsonic-android/src/github/daneren2005/dsub/service/parser/MusicDirectoryEntryParser.java index 818c8235..b0434aca 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/parser/MusicDirectoryEntryParser.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/parser/MusicDirectoryEntryParser.java @@ -25,7 +25,6 @@ import github.daneren2005.dsub.domain.MusicDirectory; * @author Sindre Mehus */ public class MusicDirectoryEntryParser extends AbstractParser { - public MusicDirectoryEntryParser(Context context) { super(context); } @@ -54,9 +53,22 @@ public class MusicDirectoryEntryParser extends AbstractParser { entry.setBitRate(getInteger("bitRate")); entry.setPath(get("path")); entry.setVideo(getBoolean("isVideo")); + entry.setDiscNumber(getInteger("discNumber")); } else if(!"".equals(artist)) { entry.setPath(artist + "/" + entry.getTitle()); } return entry; } + + protected MusicDirectory.Entry parseArtist() { + MusicDirectory.Entry entry = new MusicDirectory.Entry(); + + entry.setId(get("id")); + entry.setTitle(get("name")); + entry.setPath(entry.getTitle()); + entry.setStarred(true); + entry.setDirectory(true); + + return entry; + } }
\ No newline at end of file diff --git a/subsonic-android/src/github/daneren2005/dsub/service/parser/MusicDirectoryParser.java b/subsonic-android/src/github/daneren2005/dsub/service/parser/MusicDirectoryParser.java index 370df305..038518c6 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/parser/MusicDirectoryParser.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/parser/MusicDirectoryParser.java @@ -22,7 +22,9 @@ import android.content.Context; import android.util.Log; import github.daneren2005.dsub.R; import github.daneren2005.dsub.domain.MusicDirectory; +import github.daneren2005.dsub.domain.Version; import github.daneren2005.dsub.util.ProgressListener; +import github.daneren2005.dsub.util.Util; import org.xmlpull.v1.XmlPullParser; import java.io.Reader; @@ -33,13 +35,14 @@ import java.io.Reader; public class MusicDirectoryParser extends MusicDirectoryEntryParser { private static final String TAG = MusicDirectoryParser.class.getSimpleName(); + private Context context; public MusicDirectoryParser(Context context) { super(context); + this.context = context; } public MusicDirectory parse(String artist, Reader reader, ProgressListener progressListener) throws Exception { - long t0 = System.currentTimeMillis(); updateProgress(progressListener, R.string.parser_reading); init(reader); @@ -51,9 +54,13 @@ public class MusicDirectoryParser extends MusicDirectoryEntryParser { if (eventType == XmlPullParser.START_TAG) { String name = getElementName(); if ("child".equals(name)) { - dir.addChild(parseEntry(artist)); + MusicDirectory.Entry entry = parseEntry(artist); + entry.setGrandParent(dir.getParent()); + dir.addChild(entry); } else if ("directory".equals(name)) { dir.setName(get("name")); + dir.setId(get("id")); + dir.setParent(get("parent")); } else if ("error".equals(name)) { handleError(); } @@ -62,6 +69,13 @@ public class MusicDirectoryParser extends MusicDirectoryEntryParser { validate(); updateProgress(progressListener, R.string.parser_reading_done); + + // Only apply sorting on server version 4.7 and greater, where disc is supported + Version version = Util.getServerRestVersion(context); + Version discVersion = new Version("1.8.0"); + if(version.compareTo(discVersion) >= 0) { + dir.sortChildren(); + } long t1 = System.currentTimeMillis(); Log.d(TAG, "Got music directory in " + (t1 - t0) + "ms."); diff --git a/subsonic-android/src/github/daneren2005/dsub/service/parser/ShareParser.java b/subsonic-android/src/github/daneren2005/dsub/service/parser/ShareParser.java new file mode 100644 index 00000000..c317e799 --- /dev/null +++ b/subsonic-android/src/github/daneren2005/dsub/service/parser/ShareParser.java @@ -0,0 +1,77 @@ +/*
+ This file is part of Subsonic.
+
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2009 (C) Sindre Mehus
+ */
+package github.daneren2005.dsub.service.parser;
+
+import android.content.Context;
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.domain.Share;
+import github.daneren2005.dsub.util.ProgressListener;
+import org.xmlpull.v1.XmlPullParser;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Joshua Bahnsen
+ */
+public class ShareParser extends MusicDirectoryEntryParser {
+
+ public ShareParser(Context context) {
+ super(context);
+ }
+
+ public List<Share> parse(Reader reader, ProgressListener progressListener) throws Exception {
+
+ updateProgress(progressListener, R.string.parser_reading);
+ init(reader);
+
+ List<Share> dir = new ArrayList<Share>();
+ Share share = null;
+ int eventType;
+
+ do {
+ eventType = nextParseEvent();
+
+ if (eventType == XmlPullParser.START_TAG) {
+ String name = getElementName();
+
+ if ("share".equals(name)) {
+ share = new Share();
+ share.setCreated(get("created"));
+ share.setDescription(get("description"));
+ share.setExpires(get("expires"));
+ share.setId(get("id"));
+ share.setLastVisited(get("lastVisited"));
+ share.setUrl(get("url"));
+ share.setUsername(get("username"));
+ share.setVisitCount(getLong("visitCount"));
+ } else if ("entry".equals(name)) {
+ share.addEntry(parseEntry(null));
+ } else if ("error".equals(name)) {
+ handleError();
+ }
+ }
+ } while (eventType != XmlPullParser.END_DOCUMENT);
+
+ validate();
+ updateProgress(progressListener, R.string.parser_reading_done);
+
+ return dir;
+ }
+}
\ No newline at end of file diff --git a/subsonic-android/src/github/daneren2005/dsub/service/parser/StarredListParser.java b/subsonic-android/src/github/daneren2005/dsub/service/parser/StarredListParser.java index 2cb42f30..fc4cd175 100644 --- a/subsonic-android/src/github/daneren2005/dsub/service/parser/StarredListParser.java +++ b/subsonic-android/src/github/daneren2005/dsub/service/parser/StarredListParser.java @@ -49,7 +49,7 @@ public class StarredListParser extends MusicDirectoryEntryParser { if ("album".equals(name) || "song".equals(name)) { dir.addChild(parseEntry("")); } else if("artist".equals(name)) { - + dir.addChild(parseArtist()); } else if ("error".equals(name)) { handleError(); } diff --git a/subsonic-android/src/github/daneren2005/dsub/updates/Updater.java b/subsonic-android/src/github/daneren2005/dsub/updates/Updater.java index c239fa43..60a17b67 100644 --- a/subsonic-android/src/github/daneren2005/dsub/updates/Updater.java +++ b/subsonic-android/src/github/daneren2005/dsub/updates/Updater.java @@ -43,7 +43,7 @@ public class Updater { public void checkUpdates(Context context) {
this.context = context;
List<Updater> updaters = new ArrayList<Updater>();
- updaters.add(new Updater373());
+ updaters.add(new Updater403());
SharedPreferences prefs = Util.getPreferences(context);
int lastVersion = prefs.getInt(Constants.LAST_VERSION, 0);
diff --git a/subsonic-android/src/github/daneren2005/dsub/updates/Updater373.java b/subsonic-android/src/github/daneren2005/dsub/updates/Updater403.java index b56c2731..17947ce5 100644 --- a/subsonic-android/src/github/daneren2005/dsub/updates/Updater373.java +++ b/subsonic-android/src/github/daneren2005/dsub/updates/Updater403.java @@ -29,16 +29,16 @@ import java.io.File; *
* @author Scott
*/
-public class Updater373 extends Updater {
- public Updater373() {
- super(373);
- TAG = Updater373.class.getSimpleName();
+public class Updater403 extends Updater {
+ public Updater403() {
+ super(403);
+ TAG = Updater403.class.getSimpleName();
}
@Override
public void update(Context context) {
// Rename cover.jpeg to cover.jpg
- Log.i(TAG, "Running Updater373: updating cover.jpeg to cover.jpg");
+ Log.i(TAG, "Running Updater403: updating cover.jpg to albumart.jpg");
File dir = FileUtil.getMusicDirectory(context);
if(dir != null) {
moveArt(dir);
@@ -49,7 +49,7 @@ public class Updater373 extends Updater { for(File file: dir.listFiles()) {
if(file.isDirectory()) {
moveArt(file);
- } else if("cover.jpeg".equals(file.getName())) {
+ } else if("cover.jpg".equals(file.getName()) || "cover.jpeg".equals(file.getName())) {
File renamed = new File(dir, Constants.ALBUM_ART_FILE);
file.renameTo(renamed);
}
diff --git a/subsonic-android/src/github/daneren2005/dsub/util/CacheCleaner.java b/subsonic-android/src/github/daneren2005/dsub/util/CacheCleaner.java index c69a595c..44de369f 100644 --- a/subsonic-android/src/github/daneren2005/dsub/util/CacheCleaner.java +++ b/subsonic-android/src/github/daneren2005/dsub/util/CacheCleaner.java @@ -54,7 +54,7 @@ public class CacheCleaner { // No songs left in the folder if(children.length == 1 && children[0].getPath().equals(FileUtil.getAlbumArtFile(dir).getPath())) { - Util.delete(FileUtil.getAlbumArtFile(dir)); + Util.delete(children[0]); children = dir.listFiles(); } diff --git a/subsonic-android/src/github/daneren2005/dsub/util/Constants.java b/subsonic-android/src/github/daneren2005/dsub/util/Constants.java index 302ececf..5de13e73 100644 --- a/subsonic-android/src/github/daneren2005/dsub/util/Constants.java +++ b/subsonic-android/src/github/daneren2005/dsub/util/Constants.java @@ -36,6 +36,8 @@ public final class Constants { // Names for intent extras. public static final String INTENT_EXTRA_NAME_ID = "subsonic.id"; public static final String INTENT_EXTRA_NAME_NAME = "subsonic.name"; + public static final String INTENT_EXTRA_NAME_PARENT_ID = "subsonic.parent_id"; + public static final String INTENT_EXTRA_NAME_PARENT_NAME = "subsonic.parent_name"; public static final String INTENT_EXTRA_NAME_ARTIST = "subsonic.artist"; public static final String INTENT_EXTRA_NAME_TITLE = "subsonic.title"; public static final String INTENT_EXTRA_NAME_AUTOPLAY = "subsonic.playall"; @@ -44,6 +46,7 @@ public final class Constants { public static final String INTENT_EXTRA_NAME_PLAYLIST_ID = "subsonic.playlist.id"; public static final String INTENT_EXTRA_NAME_PLAYLIST_NAME = "subsonic.playlist.name"; public static final String INTENT_EXTRA_NAME_ALBUM_LIST_TYPE = "subsonic.albumlisttype"; + public static final String INTENT_EXTRA_NAME_ALBUM_LIST_EXTRA = "subsonic.albumlistextra"; public static final String INTENT_EXTRA_NAME_ALBUM_LIST_SIZE = "subsonic.albumlistsize"; public static final String INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET = "subsonic.albumlistoffset"; public static final String INTENT_EXTRA_NAME_SHUFFLE = "subsonic.shuffle"; @@ -51,15 +54,22 @@ public final class Constants { public static final String INTENT_EXTRA_REQUEST_SEARCH = "subsonic.requestsearch"; public static final String INTENT_EXTRA_NAME_EXIT = "subsonic.exit" ; public static final String INTENT_EXTRA_NAME_DOWNLOAD = "subsonic.download"; + public static final String INTENT_EXTRA_VIEW_ALBUM = "subsonic.view_album"; // Notification IDs. public static final int NOTIFICATION_ID_PLAYING = 100; public static final int NOTIFICATION_ID_ERROR = 101; // Preferences keys. + public static final String PREFERENCES_KEY_SERVER_KEY = "server"; + public static final String PREFERENCES_KEY_SERVER_COUNT = "serverCount"; + public static final String PREFERENCES_KEY_SERVER_ADD = "serverAdd"; + public static final String PREFERENCES_KEY_SERVER_REMOVE = "serverRemove"; public static final String PREFERENCES_KEY_SERVER_INSTANCE = "serverInstanceId"; public static final String PREFERENCES_KEY_SERVER_NAME = "serverName"; public static final String PREFERENCES_KEY_SERVER_URL = "serverUrl"; + public static final String PREFERENCES_KEY_TEST_CONNECTION = "serverTestConnection"; + public static final String PREFERENCES_KEY_OPEN_BROWSER = "openBrowser"; public static final String PREFERENCES_KEY_MUSIC_FOLDER_ID = "musicFolderId"; public static final String PREFERENCES_KEY_USERNAME = "username"; public static final String PREFERENCES_KEY_PASSWORD = "password"; @@ -72,7 +82,8 @@ public final class Constants { public static final String PREFERENCES_KEY_NETWORK_TIMEOUT = "networkTimeout"; public static final String PREFERENCES_KEY_CACHE_SIZE = "cacheSize"; public static final String PREFERENCES_KEY_CACHE_LOCATION = "cacheLocation"; - public static final String PREFERENCES_KEY_PRELOAD_COUNT = "preloadCount"; + public static final String PREFERENCES_KEY_PRELOAD_COUNT_WIFI = "preloadCountWifi"; + public static final String PREFERENCES_KEY_PRELOAD_COUNT_MOBILE = "preloadCountMobile"; public static final String PREFERENCES_KEY_HIDE_MEDIA = "hideMedia"; public static final String PREFERENCES_KEY_MEDIA_BUTTONS = "mediaButtons"; public static final String PREFERENCES_KEY_SCREEN_LIT_ON_DOWNLOAD = "screenLitOnDownload"; @@ -93,6 +104,11 @@ public final class Constants { public static final String PREFERENCES_EQUALIZER_SETTINGS = "equalizerSettings"; public static final String PREFERENCES_KEY_PERSISTENT_NOTIFICATION = "persistentNotification"; public static final String PREFERENCES_KEY_GAPLESS_PLAYBACK = "gaplessPlayback"; + public static final String PREFERENCES_KEY_SHUFFLE_MODE = "shuffleMode"; + public static final String PREFERENCES_KEY_CHAT_REFRESH = "chatRefreshRate"; + public static final String PREFERENCES_KEY_CHAT_ENABLED = "chatEnabled"; + + public static final String CACHE_KEY_IGNORE = "ignoreArticles"; public static final String MAIN_BACK_STACK = "backStackIds"; public static final String MAIN_BACK_STACK_SIZE = "backStackIdsSize"; @@ -108,7 +124,7 @@ public final class Constants { // URL for project donations. public static final String DONATION_URL = "http://subsonic.org/pages/android-donation.jsp"; - public static final String ALBUM_ART_FILE = "cover.jpg"; + public static final String ALBUM_ART_FILE = "albumart.jpg"; private Constants() { } diff --git a/subsonic-android/src/github/daneren2005/dsub/util/ErrorDialog.java b/subsonic-android/src/github/daneren2005/dsub/util/ErrorDialog.java index 2928e93f..ab9b4fa3 100644 --- a/subsonic-android/src/github/daneren2005/dsub/util/ErrorDialog.java +++ b/subsonic-android/src/github/daneren2005/dsub/util/ErrorDialog.java @@ -21,6 +21,8 @@ package github.daneren2005.dsub.util; import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; +import android.content.Intent; +import github.daneren2005.dsub.activity.MainActivity; import github.daneren2005.dsub.R; /** @@ -43,7 +45,7 @@ public class ErrorDialog { @Override public void onCancel(DialogInterface dialogInterface) { if (finishActivityOnClose) { - activity.finish(); + restart(activity); } } }); @@ -51,11 +53,17 @@ public class ErrorDialog { @Override public void onClick(DialogInterface dialogInterface, int i) { if (finishActivityOnClose) { - activity.finish(); + restart(activity); } } }); builder.create().show(); } + + private void restart(Activity context) { + Intent intent = new Intent(context, MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + Util.startActivityWithoutTransition(context, intent); + } } diff --git a/subsonic-android/src/github/daneren2005/dsub/util/FileUtil.java b/subsonic-android/src/github/daneren2005/dsub/util/FileUtil.java index 65f5a37f..7ead874c 100644 --- a/subsonic-android/src/github/daneren2005/dsub/util/FileUtil.java +++ b/subsonic-android/src/github/daneren2005/dsub/util/FileUtil.java @@ -129,6 +129,10 @@ public class FileUtil { File dir = new File(getMusicDirectory(context).getPath() + "/" + fileSystemSafe(artist.getName())); return dir; } + public static File getArtistDirectory(Context context, MusicDirectory.Entry artist) { + File dir = new File(getMusicDirectory(context).getPath() + "/" + fileSystemSafe(artist.getTitle())); + return dir; + } public static File getAlbumDirectory(Context context, MusicDirectory.Entry entry) { File dir; diff --git a/subsonic-android/src/github/daneren2005/dsub/util/ImageLoader.java b/subsonic-android/src/github/daneren2005/dsub/util/ImageLoader.java index 61f20c0a..f84116f7 100644 --- a/subsonic-android/src/github/daneren2005/dsub/util/ImageLoader.java +++ b/subsonic-android/src/github/daneren2005/dsub/util/ImageLoader.java @@ -56,6 +56,7 @@ public class ImageLoader implements Runnable { private Handler mHandler = new Handler(); private Context context; private LruCache<String, Bitmap> cache; + private Bitmap nowPlaying; private final BlockingQueue<Task> queue; private final int imageSizeDefault; private final int imageSizeLarge; @@ -73,7 +74,7 @@ public class ImageLoader implements Runnable { @Override protected void entryRemoved(boolean evicted, String key, Bitmap oldBitmap, Bitmap newBitmap) { - if(evicted) { + if(evicted && oldBitmap != nowPlaying) { oldBitmap.recycle(); } } @@ -110,13 +111,16 @@ public class ImageLoader implements Runnable { if (bitmap != null) { final Drawable drawable = Util.createDrawableFromBitmap(this.context, bitmap); setImage(view, drawable, large); + if(large) { + nowPlaying = bitmap; + } return; } if (!large) { setUnknownImage(view, large); } - queue.offer(new Task(view.getContext(), entry, size, imageSizeLarge, new ViewTaskHandler(view, crossfade))); + queue.offer(new Task(view.getContext(), entry, size, imageSizeLarge, large, new ViewTaskHandler(view, crossfade))); } public void loadImage(Context context, RemoteControlClient remoteControl, MusicDirectory.Entry entry) { @@ -133,7 +137,7 @@ public class ImageLoader implements Runnable { } setUnknownImage(remoteControl); - queue.offer(new Task(context, entry, imageSizeLarge, imageSizeLarge, new RemoteControlClientTaskHandler(remoteControl))); + queue.offer(new Task(context, entry, imageSizeLarge, imageSizeLarge, false, new RemoteControlClientTaskHandler(remoteControl))); } private String getKey(String coverArtId, int size) { @@ -228,13 +232,15 @@ public class ImageLoader implements Runnable { private final MusicDirectory.Entry mEntry; private final int mSize; private final int mSaveSize; + private final boolean mIsNowPlaying; private ImageLoaderTaskHandler mTaskHandler; - public Task(Context context, MusicDirectory.Entry entry, int size, int saveSize, ImageLoaderTaskHandler taskHandler) { + public Task(Context context, MusicDirectory.Entry entry, int size, int saveSize, boolean isNowPlaying, ImageLoaderTaskHandler taskHandler) { mContext = context; mEntry = entry; mSize = size; mSaveSize = saveSize; + mIsNowPlaying = isNowPlaying; mTaskHandler = taskHandler; } @@ -255,6 +261,9 @@ public class ImageLoader implements Runnable { cache.put(key, bitmap); // Make sure key is the most recently "used" cache.get(key); + if(mIsNowPlaying) { + nowPlaying = bitmap; + } final Drawable drawable = Util.createDrawableFromBitmap(mContext, bitmap); mTaskHandler.setDrawable(drawable); diff --git a/subsonic-android/src/github/daneren2005/dsub/util/Util.java b/subsonic-android/src/github/daneren2005/dsub/util/Util.java index 48cb7276..a8918923 100644 --- a/subsonic-android/src/github/daneren2005/dsub/util/Util.java +++ b/subsonic-android/src/github/daneren2005/dsub/util/Util.java @@ -165,11 +165,58 @@ public final class Util { SharedPreferences prefs = getPreferences(context); return prefs.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, 1); } + + public static int getServerCount(Context context) { + SharedPreferences prefs = getPreferences(context); + return prefs.getInt(Constants.PREFERENCES_KEY_SERVER_COUNT, 1); + } + + public static void removeInstanceName(Context context, int instance, int activeInstance) { + SharedPreferences prefs = getPreferences(context); + SharedPreferences.Editor editor = prefs.edit(); + + int newInstance = instance + 1; + + String server = prefs.getString(Constants.PREFERENCES_KEY_SERVER_KEY + newInstance, null); + String serverName = prefs.getString(Constants.PREFERENCES_KEY_SERVER_NAME + newInstance, null); + String serverUrl = prefs.getString(Constants.PREFERENCES_KEY_SERVER_URL + newInstance, null); + String userName = prefs.getString(Constants.PREFERENCES_KEY_USERNAME + newInstance, null); + String password = prefs.getString(Constants.PREFERENCES_KEY_PASSWORD + newInstance, null); + + editor.putString(Constants.PREFERENCES_KEY_SERVER_KEY + instance, server); + editor.putString(Constants.PREFERENCES_KEY_SERVER_NAME + instance, serverName); + editor.putString(Constants.PREFERENCES_KEY_SERVER_URL + instance, serverUrl); + editor.putString(Constants.PREFERENCES_KEY_USERNAME + instance, userName); + editor.putString(Constants.PREFERENCES_KEY_PASSWORD + instance, password); + + editor.putString(Constants.PREFERENCES_KEY_SERVER_KEY + newInstance, null); + editor.putString(Constants.PREFERENCES_KEY_SERVER_NAME + newInstance, null); + editor.putString(Constants.PREFERENCES_KEY_SERVER_URL + newInstance, null); + editor.putString(Constants.PREFERENCES_KEY_USERNAME + newInstance, null); + editor.putString(Constants.PREFERENCES_KEY_PASSWORD + newInstance, null); + editor.commit(); + + if (instance == activeInstance) { + Util.setActiveServer(context, 0); + } + + if (newInstance == activeInstance) { + Util.setActiveServer(context, instance); + } + } public static String getServerName(Context context, int instance) { SharedPreferences prefs = getPreferences(context); return prefs.getString(Constants.PREFERENCES_KEY_SERVER_NAME + instance, null); } + + public static String getUserName(Context context, int instance) { + if (instance == 0) { + return context.getResources().getString(R.string.main_offline); + } + SharedPreferences prefs = getPreferences(context); + return prefs.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null); + } public static void setServerRestVersion(Context context, Version version) { SERVER_REST_VERSIONS.put(getActiveServer(context), version); @@ -223,8 +270,15 @@ public final class Util { } public static int getPreloadCount(Context context) { + ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = manager.getActiveNetworkInfo(); + if (networkInfo == null) { + return 3; + } + SharedPreferences prefs = getPreferences(context); - int preloadCount = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_PRELOAD_COUNT, "-1")); + boolean wifi = networkInfo.getType() == ConnectivityManager.TYPE_WIFI; + int preloadCount = Integer.parseInt(prefs.getString(wifi ? Constants.PREFERENCES_KEY_PRELOAD_COUNT_WIFI : Constants.PREFERENCES_KEY_PRELOAD_COUNT_MOBILE, "-1")); return preloadCount == -1 ? Integer.MAX_VALUE : preloadCount; } @@ -588,6 +642,10 @@ public final class Util { throw new RuntimeException(x.getMessage(), x); } } + + public static boolean isNullOrWhiteSpace(String string) { + return string == null || string.isEmpty() || string.trim().isEmpty(); + } public static boolean isNetworkConnected(Context context) { ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); diff --git a/subsonic-android/src/github/daneren2005/dsub/view/AlbumListAdapter.java b/subsonic-android/src/github/daneren2005/dsub/view/AlbumListAdapter.java index 6c8d315a..3ff8350b 100644 --- a/subsonic-android/src/github/daneren2005/dsub/view/AlbumListAdapter.java +++ b/subsonic-android/src/github/daneren2005/dsub/view/AlbumListAdapter.java @@ -34,15 +34,17 @@ public class AlbumListAdapter extends EndlessAdapter { Context context;
ArrayAdapter<MusicDirectory.Entry> adapter;
String type;
+ String extra;
int size;
int offset;
List<MusicDirectory.Entry> entries;
- public AlbumListAdapter(Context context, ArrayAdapter<MusicDirectory.Entry> adapter, String type, int size) {
+ public AlbumListAdapter(Context context, ArrayAdapter<MusicDirectory.Entry> adapter, String type, String extra, int size) {
super(adapter);
this.context = context;
this.adapter = adapter;
this.type = type;
+ this.extra = extra;
this.size = size;
this.offset = size;
}
@@ -50,7 +52,12 @@ public class AlbumListAdapter extends EndlessAdapter { @Override
protected boolean cacheInBackground() throws Exception {
MusicService service = MusicServiceFactory.getMusicService(context);
- MusicDirectory result = service.getAlbumList(type, size, offset, context, null);
+ MusicDirectory result;
+ if("genres".equals(type)) {
+ result = service.getSongsByGenre(extra, size, offset, context, null);
+ } else {
+ result = service.getAlbumList(type, size, offset, context, null);
+ }
entries = result.getChildren();
if(entries.size() > 0) {
return true;
diff --git a/subsonic-android/src/github/daneren2005/dsub/view/ArtistEntryView.java b/subsonic-android/src/github/daneren2005/dsub/view/ArtistEntryView.java new file mode 100644 index 00000000..3b6a50e4 --- /dev/null +++ b/subsonic-android/src/github/daneren2005/dsub/view/ArtistEntryView.java @@ -0,0 +1,83 @@ +/*
+ This file is part of Subsonic.
+
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2009 (C) Sindre Mehus
+ */
+package github.daneren2005.dsub.view;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.domain.MusicDirectory;
+import github.daneren2005.dsub.util.FileUtil;
+import github.daneren2005.dsub.util.ImageLoader;
+import github.daneren2005.dsub.util.Util;
+import java.io.File;
+/**
+ * Used to display albums in a {@code ListView}.
+ *
+ * @author Sindre Mehus
+ */
+public class ArtistEntryView extends UpdateView {
+ private static final String TAG = AlbumView.class.getSimpleName();
+
+ private Context context;
+ private MusicDirectory.Entry artist;
+
+ private TextView titleView;
+ private ImageButton starButton;
+ private ImageView moreButton;
+
+ public ArtistEntryView(Context context) {
+ super(context);
+ this.context = context;
+ LayoutInflater.from(context).inflate(R.layout.artist_list_item, this, true);
+
+ titleView = (TextView) findViewById(R.id.artist_name);
+ starButton = (ImageButton) findViewById(R.id.artist_star);
+ moreButton = (ImageView) findViewById(R.id.artist_more);
+ moreButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ v.showContextMenu();
+ }
+ });
+ }
+
+ public void setArtist(MusicDirectory.Entry artist) {
+ this.artist = artist;
+
+ titleView.setText(artist.getTitle());
+ starButton.setVisibility((Util.isOffline(getContext()) || !artist.isStarred()) ? View.GONE : View.VISIBLE);
+ starButton.setFocusable(false);
+ update();
+ }
+
+ @Override
+ protected void update() {
+ starButton.setVisibility((Util.isOffline(getContext()) || !artist.isStarred()) ? View.GONE : View.VISIBLE);
+ File file = FileUtil.getArtistDirectory(context, artist);
+ if(file.exists()) {
+ moreButton.setImageResource(R.drawable.list_item_more_shaded);
+ } else {
+ moreButton.setImageResource(R.drawable.list_item_more);
+ }
+ }
+}
diff --git a/subsonic-android/src/github/daneren2005/dsub/view/ChatAdapter.java b/subsonic-android/src/github/daneren2005/dsub/view/ChatAdapter.java new file mode 100644 index 00000000..518f81ef --- /dev/null +++ b/subsonic-android/src/github/daneren2005/dsub/view/ChatAdapter.java @@ -0,0 +1,100 @@ +package github.daneren2005.dsub.view;
+
+import android.text.method.LinkMovementMethod;
+import android.text.util.Linkify;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.activity.SubsonicActivity;
+import github.daneren2005.dsub.domain.ChatMessage;
+import github.daneren2005.dsub.util.Util;
+
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.regex.Pattern;
+
+public class ChatAdapter extends ArrayAdapter<ChatMessage> {
+
+ private final SubsonicActivity activity;
+ private ArrayList<ChatMessage> messages;
+
+ private static final String phoneRegex = "1?\\W*([2-9][0-8][0-9])\\W*([2-9][0-9]{2})\\W*([0-9]{4})"; //you can just place your support phone here
+ private static final Pattern phoneMatcher = Pattern.compile(phoneRegex);
+
+ public ChatAdapter(SubsonicActivity activity, ArrayList<ChatMessage> messages) {
+ super(activity, R.layout.chat_item, messages);
+ this.activity = activity;
+ this.messages = messages;
+ }
+
+ @Override
+ public int getCount() {
+ return messages.size();
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ ChatMessage message = this.getItem(position);
+
+ ViewHolder holder;
+ int layout;
+
+ String messageUser = message.getUsername();
+ Date messageTime = new java.util.Date(message.getTime());
+ String messageText = message.getMessage();
+
+ String me = Util.getUserName(activity, Util.getActiveServer(activity));
+
+ if (messageUser.equals(me)) {
+ layout = R.layout.chat_item_reverse;
+ } else {
+ layout = R.layout.chat_item;
+ }
+
+ if (convertView == null)
+ {
+ holder = new ViewHolder();
+
+ convertView = LayoutInflater.from(activity).inflate(layout, parent, false);
+
+ TextView usernameView = (TextView) convertView.findViewById(R.id.chat_username);
+ TextView timeView = (TextView) convertView.findViewById(R.id.chat_time);
+ TextView messageView = (TextView) convertView.findViewById(R.id.chat_message);
+
+ messageView.setMovementMethod(LinkMovementMethod.getInstance());
+ Linkify.addLinks(messageView, Linkify.EMAIL_ADDRESSES);
+ Linkify.addLinks(messageView, Linkify.WEB_URLS);
+ Linkify.addLinks(messageView, phoneMatcher, "tel:");
+
+ holder.message = messageView;
+ holder.username = usernameView;
+ holder.time = timeView;
+
+ convertView.setTag(holder);
+ }
+ else
+ {
+ holder = (ViewHolder) convertView.getTag();
+ }
+
+ DateFormat timeFormat = android.text.format.DateFormat.getTimeFormat(activity);
+ String messageTimeFormatted = String.format("[%s]", timeFormat.format(messageTime));
+
+ holder.username.setText(messageUser);
+ holder.message.setText(messageText);
+ holder.time.setText(messageTimeFormatted);
+
+ return convertView;
+ }
+
+ private static class ViewHolder
+ {
+ TextView message;
+ TextView username;
+ TextView time;
+ }
+}
diff --git a/subsonic-android/src/github/daneren2005/dsub/view/EntryAdapter.java b/subsonic-android/src/github/daneren2005/dsub/view/EntryAdapter.java index 0d67c4c4..1a4544e0 100644 --- a/subsonic-android/src/github/daneren2005/dsub/view/EntryAdapter.java +++ b/subsonic-android/src/github/daneren2005/dsub/view/EntryAdapter.java @@ -54,11 +54,16 @@ public class EntryAdapter extends ArrayAdapter<MusicDirectory.Entry> { MusicDirectory.Entry entry = getItem(position); if (entry.isDirectory()) { - AlbumView view; - view = new AlbumView(activity); - view.setAlbum(entry, imageLoader); - return view; - + if(entry.getParent() != null) { + AlbumView view; + view = new AlbumView(activity); + view.setAlbum(entry, imageLoader); + return view; + } else { + ArtistEntryView view = new ArtistEntryView(activity); + view.setArtist(entry); + return view; + } } else { SongView view; if (convertView != null && convertView instanceof SongView) { diff --git a/subsonic-android/src/github/daneren2005/dsub/view/GenreAdapter.java b/subsonic-android/src/github/daneren2005/dsub/view/GenreAdapter.java new file mode 100644 index 00000000..b98efd20 --- /dev/null +++ b/subsonic-android/src/github/daneren2005/dsub/view/GenreAdapter.java @@ -0,0 +1,59 @@ +/*
+ This file is part of Subsonic.
+
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2010 (C) Sindre Mehus
+ */
+package github.daneren2005.dsub.view;
+
+import android.widget.ArrayAdapter;
+import android.widget.SectionIndexer;
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.domain.Genre;
+
+import java.util.List;
+import java.util.Set;
+import java.util.LinkedHashSet;
+import java.util.ArrayList;
+
+/**
+ * @author Sindre Mehus
+*/
+public class GenreAdapter extends ArrayAdapter<Genre>{
+ private Context activity;
+ private List<Genre> genres;
+
+ public GenreAdapter(Context context, List<Genre> genres) {
+ super(context, android.R.layout.simple_list_item_1, genres);
+ this.activity = context;
+ this.genres = genres;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ Genre genre = genres.get(position);
+ GenreView view;
+ if (convertView != null && convertView instanceof GenreView) {
+ view = (GenreView) convertView;
+ } else {
+ view = new GenreView(activity);
+ }
+ view.setGenre(genre);
+ return view;
+ }
+}
diff --git a/subsonic-android/src/github/daneren2005/dsub/view/GenreView.java b/subsonic-android/src/github/daneren2005/dsub/view/GenreView.java new file mode 100644 index 00000000..dbb0248b --- /dev/null +++ b/subsonic-android/src/github/daneren2005/dsub/view/GenreView.java @@ -0,0 +1,53 @@ +/*
+ This file is part of Subsonic.
+
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2009 (C) Sindre Mehus
+ */
+package github.daneren2005.dsub.view;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.domain.Genre;
+
+public class GenreView extends UpdateView {
+ private static final String TAG = GenreView.class.getSimpleName();
+
+ private TextView titleView;
+ private ImageButton starButton;
+ private ImageView moreButton;
+
+ public GenreView(Context context) {
+ super(context);
+ LayoutInflater.from(context).inflate(R.layout.artist_list_item, this, true);
+
+ titleView = (TextView) findViewById(R.id.artist_name);
+ starButton = (ImageButton) findViewById(R.id.artist_star);
+ moreButton = (ImageView) findViewById(R.id.artist_more);
+ moreButton.setClickable(false);
+ }
+
+ public void setGenre(Genre genre) {
+ titleView.setText(genre.getName());
+
+ starButton.setVisibility(View.GONE);
+ starButton.setFocusable(false);
+ }
+}
diff --git a/subsonic-android/src/github/daneren2005/dsub/view/SongView.java b/subsonic-android/src/github/daneren2005/dsub/view/SongView.java index 51927304..40bedad6 100644 --- a/subsonic-android/src/github/daneren2005/dsub/view/SongView.java +++ b/subsonic-android/src/github/daneren2005/dsub/view/SongView.java @@ -72,23 +72,6 @@ public class SongView extends UpdateView implements Checkable { public void setSong(MusicDirectory.Entry song, boolean checkable) { this.song = song; - if(Util.isOffline(context)) { - DownloadFile downloadFile = new DownloadFile(context, song, false); - File file = downloadFile.getCompleteFile(); - if(file.exists()) { - try { - MediaMetadataRetriever metadata = new MediaMetadataRetriever(); - metadata.setDataSource(file.getAbsolutePath()); - String bitrate = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE); - song.setBitRate(Integer.parseInt((bitrate != null) ? bitrate : "0") / 1000); - String length = metadata.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); - song.setDuration(Integer.parseInt(length) / 1000); - } catch(Exception e) { - Log.i(TAG, "Device doesn't properly support MediaMetadataRetreiver"); - } - } - } - StringBuilder artist = new StringBuilder(40); String bitRate = null; |