aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorScott Jackson <daneren2005@gmail.com>2014-06-11 17:40:55 -0700
committerScott Jackson <daneren2005@gmail.com>2014-06-11 17:40:55 -0700
commitc4cfca112c442861260f54f581e9e2d14d103c34 (patch)
treed278c0f1ea0e9a0f6d6fa9d269ea5aa61ce59455
parent4b7213dba44fed876aebb8821c38923182ca5e47 (diff)
parent3fc67575b4047005482321c940429cc4eb420a12 (diff)
downloaddsub-c4cfca112c442861260f54f581e9e2d14d103c34.tar.gz
dsub-c4cfca112c442861260f54f581e9e2d14d103c34.tar.bz2
dsub-c4cfca112c442861260f54f581e9e2d14d103c34.zip
Merge branch 'Admin'
-rw-r--r--res/drawable-hdpi/ic_menu_add_person_dark.pngbin0 -> 990 bytes
-rw-r--r--res/drawable-hdpi/ic_menu_add_person_light.pngbin0 -> 1191 bytes
-rw-r--r--res/drawable-hdpi/ic_menu_admin_dark.pngbin0 -> 1263 bytes
-rw-r--r--res/drawable-hdpi/ic_menu_admin_light.pngbin0 -> 1524 bytes
-rw-r--r--res/drawable-hdpi/ic_menu_password_dark.pngbin0 -> 843 bytes
-rw-r--r--res/drawable-hdpi/ic_menu_password_light.pngbin0 -> 958 bytes
-rw-r--r--res/drawable-hdpi/ic_social_person.pngbin0 -> 4518 bytes
-rw-r--r--res/drawable-mdpi/ic_menu_add_person_dark.pngbin0 -> 652 bytes
-rw-r--r--res/drawable-mdpi/ic_menu_add_person_light.pngbin0 -> 811 bytes
-rw-r--r--res/drawable-mdpi/ic_menu_admin_dark.pngbin0 -> 781 bytes
-rw-r--r--res/drawable-mdpi/ic_menu_admin_light.pngbin0 -> 966 bytes
-rw-r--r--res/drawable-mdpi/ic_menu_password_dark.pngbin0 -> 554 bytes
-rw-r--r--res/drawable-mdpi/ic_menu_password_light.pngbin0 -> 676 bytes
-rw-r--r--res/drawable-mdpi/ic_social_person.pngbin0 -> 2834 bytes
-rw-r--r--res/drawable-xhdpi/ic_menu_add_person_dark.pngbin0 -> 1284 bytes
-rw-r--r--res/drawable-xhdpi/ic_menu_add_person_light.pngbin0 -> 1534 bytes
-rw-r--r--res/drawable-xhdpi/ic_menu_admin_dark.pngbin0 -> 1807 bytes
-rw-r--r--res/drawable-xhdpi/ic_menu_admin_light.pngbin0 -> 2119 bytes
-rw-r--r--res/drawable-xhdpi/ic_menu_password_dark.pngbin0 -> 1067 bytes
-rw-r--r--res/drawable-xhdpi/ic_menu_password_light.pngbin0 -> 1234 bytes
-rw-r--r--res/drawable-xhdpi/ic_social_person.pngbin0 -> 5960 bytes
-rw-r--r--res/drawable-xxhdpi/ic_menu_add_person_dark.pngbin0 -> 2036 bytes
-rw-r--r--res/drawable-xxhdpi/ic_menu_add_person_light.pngbin0 -> 2350 bytes
-rw-r--r--res/drawable-xxhdpi/ic_menu_admin_dark.pngbin0 -> 2992 bytes
-rw-r--r--res/drawable-xxhdpi/ic_menu_admin_light.pngbin0 -> 3467 bytes
-rw-r--r--res/drawable-xxhdpi/ic_menu_password_dark.pngbin0 -> 1610 bytes
-rw-r--r--res/drawable-xxhdpi/ic_menu_password_light.pngbin0 -> 1852 bytes
-rw-r--r--res/drawable-xxhdpi/ic_social_person.pngbin0 -> 9169 bytes
-rw-r--r--res/layout/change_email.xml28
-rw-r--r--res/layout/change_password.xml28
-rw-r--r--res/layout/create_user.xml77
-rw-r--r--res/layout/user_header.xml57
-rw-r--r--res/layout/user_list_item.xml45
-rw-r--r--res/menu/admin.xml12
-rw-r--r--res/menu/admin_context.xml16
-rw-r--r--res/menu/admin_context_user.xml8
-rw-r--r--res/menu/empty.xml3
-rw-r--r--res/menu/user.xml24
-rw-r--r--res/menu/user_user.xml14
-rw-r--r--res/values/arrays.xml4
-rw-r--r--res/values/attrs.xml2
-rw-r--r--res/values/strings.xml27
-rw-r--r--res/values/themes.xml6
-rw-r--r--res/xml/settings.xml6
-rw-r--r--src/github/daneren2005/dsub/activity/SubsonicActivity.java11
-rw-r--r--src/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java6
-rw-r--r--src/github/daneren2005/dsub/domain/User.java86
-rw-r--r--src/github/daneren2005/dsub/fragments/AdminFragment.java146
-rw-r--r--src/github/daneren2005/dsub/fragments/MainFragment.java6
-rw-r--r--src/github/daneren2005/dsub/fragments/SelectListFragment.java6
-rw-r--r--src/github/daneren2005/dsub/fragments/UserFragment.java130
-rw-r--r--src/github/daneren2005/dsub/service/CachedMusicService.java75
-rw-r--r--src/github/daneren2005/dsub/service/MusicService.java17
-rw-r--r--src/github/daneren2005/dsub/service/OfflineMusicService.java45
-rw-r--r--src/github/daneren2005/dsub/service/RESTMusicService.java163
-rw-r--r--src/github/daneren2005/dsub/service/parser/UserParser.java77
-rw-r--r--src/github/daneren2005/dsub/util/Constants.java1
-rw-r--r--src/github/daneren2005/dsub/util/FileUtil.java28
-rw-r--r--src/github/daneren2005/dsub/util/ImageLoader.java59
-rw-r--r--src/github/daneren2005/dsub/util/UserUtil.java345
-rw-r--r--src/github/daneren2005/dsub/util/Util.java8
-rw-r--r--src/github/daneren2005/dsub/view/ChatAdapter.java3
-rw-r--r--src/github/daneren2005/dsub/view/SettingView.java68
-rw-r--r--src/github/daneren2005/dsub/view/SettingsAdapter.java58
-rw-r--r--src/github/daneren2005/dsub/view/UserAdapter.java51
-rw-r--r--src/github/daneren2005/dsub/view/UserView.java54
66 files changed, 1778 insertions, 22 deletions
diff --git a/res/drawable-hdpi/ic_menu_add_person_dark.png b/res/drawable-hdpi/ic_menu_add_person_dark.png
new file mode 100644
index 00000000..971048d5
--- /dev/null
+++ b/res/drawable-hdpi/ic_menu_add_person_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_menu_add_person_light.png b/res/drawable-hdpi/ic_menu_add_person_light.png
new file mode 100644
index 00000000..f94446d0
--- /dev/null
+++ b/res/drawable-hdpi/ic_menu_add_person_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_menu_admin_dark.png b/res/drawable-hdpi/ic_menu_admin_dark.png
new file mode 100644
index 00000000..76da5ade
--- /dev/null
+++ b/res/drawable-hdpi/ic_menu_admin_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_menu_admin_light.png b/res/drawable-hdpi/ic_menu_admin_light.png
new file mode 100644
index 00000000..5431889c
--- /dev/null
+++ b/res/drawable-hdpi/ic_menu_admin_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_menu_password_dark.png b/res/drawable-hdpi/ic_menu_password_dark.png
new file mode 100644
index 00000000..67fa3e84
--- /dev/null
+++ b/res/drawable-hdpi/ic_menu_password_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_menu_password_light.png b/res/drawable-hdpi/ic_menu_password_light.png
new file mode 100644
index 00000000..bd99c01f
--- /dev/null
+++ b/res/drawable-hdpi/ic_menu_password_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_social_person.png b/res/drawable-hdpi/ic_social_person.png
new file mode 100644
index 00000000..0a0a5ff2
--- /dev/null
+++ b/res/drawable-hdpi/ic_social_person.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_add_person_dark.png b/res/drawable-mdpi/ic_menu_add_person_dark.png
new file mode 100644
index 00000000..7d64f5d3
--- /dev/null
+++ b/res/drawable-mdpi/ic_menu_add_person_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_add_person_light.png b/res/drawable-mdpi/ic_menu_add_person_light.png
new file mode 100644
index 00000000..55c38c26
--- /dev/null
+++ b/res/drawable-mdpi/ic_menu_add_person_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_admin_dark.png b/res/drawable-mdpi/ic_menu_admin_dark.png
new file mode 100644
index 00000000..f88f5f15
--- /dev/null
+++ b/res/drawable-mdpi/ic_menu_admin_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_admin_light.png b/res/drawable-mdpi/ic_menu_admin_light.png
new file mode 100644
index 00000000..35cd14f4
--- /dev/null
+++ b/res/drawable-mdpi/ic_menu_admin_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_password_dark.png b/res/drawable-mdpi/ic_menu_password_dark.png
new file mode 100644
index 00000000..74d0095a
--- /dev/null
+++ b/res/drawable-mdpi/ic_menu_password_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_password_light.png b/res/drawable-mdpi/ic_menu_password_light.png
new file mode 100644
index 00000000..159f7889
--- /dev/null
+++ b/res/drawable-mdpi/ic_menu_password_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_social_person.png b/res/drawable-mdpi/ic_social_person.png
new file mode 100644
index 00000000..c09313d8
--- /dev/null
+++ b/res/drawable-mdpi/ic_social_person.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_menu_add_person_dark.png b/res/drawable-xhdpi/ic_menu_add_person_dark.png
new file mode 100644
index 00000000..30c78e5a
--- /dev/null
+++ b/res/drawable-xhdpi/ic_menu_add_person_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_menu_add_person_light.png b/res/drawable-xhdpi/ic_menu_add_person_light.png
new file mode 100644
index 00000000..b3fb3808
--- /dev/null
+++ b/res/drawable-xhdpi/ic_menu_add_person_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_menu_admin_dark.png b/res/drawable-xhdpi/ic_menu_admin_dark.png
new file mode 100644
index 00000000..09f90c15
--- /dev/null
+++ b/res/drawable-xhdpi/ic_menu_admin_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_menu_admin_light.png b/res/drawable-xhdpi/ic_menu_admin_light.png
new file mode 100644
index 00000000..4bd3beaf
--- /dev/null
+++ b/res/drawable-xhdpi/ic_menu_admin_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_menu_password_dark.png b/res/drawable-xhdpi/ic_menu_password_dark.png
new file mode 100644
index 00000000..d1fc0a97
--- /dev/null
+++ b/res/drawable-xhdpi/ic_menu_password_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_menu_password_light.png b/res/drawable-xhdpi/ic_menu_password_light.png
new file mode 100644
index 00000000..1cbf085c
--- /dev/null
+++ b/res/drawable-xhdpi/ic_menu_password_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_social_person.png b/res/drawable-xhdpi/ic_social_person.png
new file mode 100644
index 00000000..ed333afe
--- /dev/null
+++ b/res/drawable-xhdpi/ic_social_person.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_menu_add_person_dark.png b/res/drawable-xxhdpi/ic_menu_add_person_dark.png
new file mode 100644
index 00000000..446985ea
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_menu_add_person_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_menu_add_person_light.png b/res/drawable-xxhdpi/ic_menu_add_person_light.png
new file mode 100644
index 00000000..0f1d36bc
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_menu_add_person_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_menu_admin_dark.png b/res/drawable-xxhdpi/ic_menu_admin_dark.png
new file mode 100644
index 00000000..0e57c9ed
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_menu_admin_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_menu_admin_light.png b/res/drawable-xxhdpi/ic_menu_admin_light.png
new file mode 100644
index 00000000..63ab2f83
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_menu_admin_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_menu_password_dark.png b/res/drawable-xxhdpi/ic_menu_password_dark.png
new file mode 100644
index 00000000..a7cd1a6d
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_menu_password_dark.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_menu_password_light.png b/res/drawable-xxhdpi/ic_menu_password_light.png
new file mode 100644
index 00000000..5670a209
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_menu_password_light.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_social_person.png b/res/drawable-xxhdpi/ic_social_person.png
new file mode 100644
index 00000000..f81dc6a4
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_social_person.png
Binary files differ
diff --git a/res/layout/change_email.xml b/res/layout/change_email.xml
new file mode 100644
index 00000000..8b74215c
--- /dev/null
+++ b/res/layout/change_email.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:id="@+id/new_email_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="4dp"
+ android:textSize="20dp"
+ android:text="@string/admin.change_email_label" />
+ <EditText
+ android:id="@+id/new_email"
+ android:inputType="textEmailAddress"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_marginLeft="4dp" />
+ </LinearLayout>
+
+</LinearLayout> \ No newline at end of file
diff --git a/res/layout/change_password.xml b/res/layout/change_password.xml
new file mode 100644
index 00000000..ad3e9cd8
--- /dev/null
+++ b/res/layout/change_password.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:id="@+id/new_password_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="4dp"
+ android:textSize="20dp"
+ android:text="@string/admin.change_password_label" />
+ <EditText
+ android:id="@+id/new_password"
+ android:inputType="textPassword"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_marginLeft="4dp" />
+ </LinearLayout>
+
+</LinearLayout> \ No newline at end of file
diff --git a/res/layout/create_user.xml b/res/layout/create_user.xml
new file mode 100644
index 00000000..4d918fc9
--- /dev/null
+++ b/res/layout/create_user.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:id="@+id/username_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="4dp"
+ android:textSize="20dp"
+ android:text="@string/admin.add_user_username" />
+ <EditText
+ android:id="@+id/username"
+ android:inputType="text"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_marginLeft="4dp" />
+ </LinearLayout>
+
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:id="@+id/email_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="4dp"
+ android:textSize="20dp"
+ android:text="@string/admin.add_user_email" />
+ <EditText
+ android:id="@+id/email"
+ android:inputType="textEmailAddress"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_marginLeft="4dp" />
+ </LinearLayout>
+
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:id="@+id/password_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="4dp"
+ android:textSize="20dp"
+ android:text="@string/admin.add_user_password" />
+ <EditText
+ android:id="@+id/password"
+ android:inputType="textPassword"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_marginLeft="4dp" />
+ </LinearLayout>
+
+ <ListView
+ android:id="@+id/settings_list"
+ android:layout_width="fill_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1.0"
+ android:fastScrollEnabled="true"/>
+
+</LinearLayout> \ No newline at end of file
diff --git a/res/layout/user_header.xml b/res/layout/user_header.xml
new file mode 100644
index 00000000..5966e0ed
--- /dev/null
+++ b/res/layout/user_header.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/select_album_header"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <ImageView
+ android:id="@+id/user_avatar"
+ android:src="@drawable/ic_social_person"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true"
+ android:layout_marginRight="10dip"
+ android:scaleType="fitCenter"
+ android:contentDescription="@null"/>
+
+ <LinearLayout
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="@+id/user_avatar"
+ android:orientation="vertical"
+ android:layout_centerVertical="true">
+
+ <TextView
+ android:text="Username"
+ android:id="@+id/user_username"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textStyle="bold"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:marqueeRepeatLimit="marquee_forever"
+ android:scrollHorizontally="true"
+ android:focusable="true"
+ android:focusableInTouchMode="true">
+
+ <requestFocus android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:duplicateParentState="true" />
+ </TextView>
+
+ <TextView
+ android:text="Email"
+ android:id="@+id/user_email"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:autoLink="email"/>
+
+ </LinearLayout>
+</RelativeLayout>
+
+
diff --git a/res/layout/user_list_item.xml b/res/layout/user_list_item.xml
new file mode 100644
index 00000000..657dd910
--- /dev/null
+++ b/res/layout/user_list_item.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:background="@android:color/transparent">
+
+ <ImageView
+ android:id="@+id/item_avatar"
+ android:src="@drawable/ic_social_person"
+ android:layout_width="@dimen/AlbumArt.Small"
+ android:layout_height="@dimen/AlbumArt.Small"
+ android:layout_gravity="left|center_vertical"/>
+
+ <TextView
+ android:id="@+id/item_name"
+ android:layout_width="0dip"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:gravity="left|center_vertical"
+ android:paddingLeft="12dip"
+ android:paddingRight="6dip"
+ android:minHeight="50dip"
+ android:background="@android:color/transparent"/>
+
+ <ImageButton
+ android:id="@+id/item_star"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="right|center_vertical"
+ android:src="@drawable/ic_stat_star"
+ android:background="@android:color/transparent"
+ android:focusable="false"
+ android:visibility="gone"/>
+
+ <ImageView
+ android:id="@+id/item_more"
+ android:src="?attr/download_none"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:layout_gravity="right|center_vertical"
+ android:paddingRight="10dip"
+ android:background="@drawable/menubar_button"/>
+</LinearLayout> \ No newline at end of file
diff --git a/res/menu/admin.xml b/res/menu/admin.xml
new file mode 100644
index 00000000..f16e89f7
--- /dev/null
+++ b/res/menu/admin.xml
@@ -0,0 +1,12 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:compat="http://schemas.android.com/apk/res-auto">
+ <item
+ android:id="@+id/menu_add_user"
+ android:title="@string/menu.add_user"
+ android:icon="?attr/add_person"
+ compat:showAsAction="always|withText"/>
+
+ <item
+ android:id="@+id/menu_exit"
+ android:title="@string/menu.exit"/>
+</menu> \ No newline at end of file
diff --git a/res/menu/admin_context.xml b/res/menu/admin_context.xml
new file mode 100644
index 00000000..922a24a9
--- /dev/null
+++ b/res/menu/admin_context.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:compat="http://schemas.android.com/apk/res-auto">
+
+ <item
+ android:id="@+id/admin_change_email"
+ android:title="@string/admin.change_email"/>
+
+ <item
+ android:id="@+id/admin_change_password"
+ android:title="@string/admin.change_password"/>
+
+ <item
+ android:id="@+id/admin_delete_user"
+ android:title="@string/admin.delete_user"/>
+</menu>
diff --git a/res/menu/admin_context_user.xml b/res/menu/admin_context_user.xml
new file mode 100644
index 00000000..cc4c1ab9
--- /dev/null
+++ b/res/menu/admin_context_user.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:compat="http://schemas.android.com/apk/res-auto">
+
+ <item
+ android:id="@+id/admin_change_password"
+ android:title="@string/admin.change_password"/>
+</menu> \ No newline at end of file
diff --git a/res/menu/empty.xml b/res/menu/empty.xml
index cf7b82de..7abe954b 100644
--- a/res/menu/empty.xml
+++ b/res/menu/empty.xml
@@ -2,4 +2,7 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:compat="http://schemas.android.com/apk/res-auto">
+ <item
+ android:id="@+id/menu_exit"
+ android:title="@string/menu.exit"/>
</menu>
diff --git a/res/menu/user.xml b/res/menu/user.xml
new file mode 100644
index 00000000..aea881fc
--- /dev/null
+++ b/res/menu/user.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:compat="http://schemas.android.com/apk/res-auto">
+
+ <item
+ android:id="@+id/menu_update_permissions"
+ android:title="@string/admin.update_permissions"
+ android:icon="?attr/save"
+ compat:showAsAction="always|withText"/>
+
+ <item
+ android:id="@+id/menu_change_password"
+ android:title="@string/admin.change_password"
+ android:icon="?attr/password"
+ compat:showAsAction="always|withText"/>
+
+ <item
+ android:id="@+id/menu_change_email"
+ android:title="@string/admin.change_email"/>
+
+ <item
+ android:id="@+id/menu_exit"
+ android:title="@string/menu.exit"/>
+</menu> \ No newline at end of file
diff --git a/res/menu/user_user.xml b/res/menu/user_user.xml
new file mode 100644
index 00000000..f66aa793
--- /dev/null
+++ b/res/menu/user_user.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:compat="http://schemas.android.com/apk/res-auto">
+
+ <item
+ android:id="@+id/menu_change_password"
+ android:title="@string/admin.change_password"
+ android:icon="?attr/password"
+ compat:showAsAction="always|withText"/>
+
+ <item
+ android:id="@+id/menu_exit"
+ android:title="@string/menu.exit"/>
+</menu> \ No newline at end of file
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index f6ee767f..deb8fdf5 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -8,6 +8,7 @@
<item>@string/button_bar.bookmarks</item>
<item>@string/button_bar.shares</item>
<item>@string/button_bar.chat</item>
+ <item>@string/button_bar.admin</item>
<item>@string/button_bar.downloading</item>
<item>@string/menu.settings</item>
</string-array>
@@ -20,6 +21,7 @@
<item>Bookmark</item>
<item>Share</item>
<item>Chat</item>
+ <item>Admin</item>
<item>Download</item>
<item>Settings</item>
</string-array>
@@ -32,6 +34,7 @@
<item>@drawable/ic_menu_bookmark_light</item>
<item>@drawable/ic_menu_share_light</item>
<item>@drawable/ic_menu_chat_light</item>
+ <item>@drawable/ic_menu_admin_light</item>
<item>@drawable/ic_menu_download_light</item>
<item>@drawable/ic_menu_settings_light</item>
</array>
@@ -44,6 +47,7 @@
<item>@drawable/ic_menu_bookmark_dark</item>
<item>@drawable/ic_menu_share_dark</item>
<item>@drawable/ic_menu_chat_dark</item>
+ <item>@drawable/ic_menu_admin_dark</item>
<item>@drawable/ic_menu_download_dark</item>
<item>@drawable/ic_menu_settings_dark</item>
</array>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index d025a48f..ac535487 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -21,5 +21,7 @@
<attr name="downloading" format="reference"/>
<attr name="bookmark" format="reference"/>
<attr name="share" format="reference"/>
+ <attr name="add_person" format="reference"/>
+ <attr name="password" format="reference"/>
<attr name="drawerItemsIcons" format="reference"/>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index fd0efec4..e4e9d299 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -35,6 +35,7 @@
<string name="button_bar.bookmarks">Bookmarks</string>
<string name="button_bar.shares">Shares</string>
<string name="button_bar.chat">Chat</string>
+ <string name="button_bar.admin">Admin</string>
<string name="button_bar.downloading">Downloading</string>
<string name="main.welcome_title">Welcome!</string>
@@ -99,6 +100,7 @@
<string name="menu.delete_cache">Delete Cache</string>
<string name="menu.cast">Cast To Device</string>
<string name="menu.faq">FAQ</string>
+ <string name="menu.add_user">Add User</string>
<string name="playlist.label">Playlists</string>
<string name="playlist.update_info">Update Information</string>
@@ -414,6 +416,8 @@
<string name="settings.play_now_after_summary">Play Now context menu for a song plays everything after selected item (like the Subsonic web GUI)</string>
<string name="settings.large_album_art">Large Album Art</string>
<string name="settings.large_album_art_summary">Display albums with large album art instead of in a list</string>
+ <string name="settings.admin_enabled">Admin Enabled</string>
+ <string name="settings.admin_enabled_summary">Whether or not to display the admin listing in the pull out drawer</string>
<string name="shuffle.title">Shuffle By</string>
<string name="shuffle.startYear">Start Year:</string>
@@ -441,6 +445,29 @@
<string name="share.via">Share via</string>
<string name="share.delete">Delete Share</string>
+ <string name="admin.add_user_username">Username:</string>
+ <string name="admin.add_user_email">Email:</string>
+ <string name="admin.add_user_password">Password:</string>
+ <string name="admin.create_user_success">Successfully created new user</string>
+ <string name="admin.create_user_error">Failed to create new user</string>
+ <string name="admin.change_username_invalid">Enter a valid username</string>
+ <string name="admin.update_permissions">Update Permissions</string>
+ <string name="admin.update_permissions_success">Successfully updated permission for %1$s</string>
+ <string name="admin.update_permissions_error">Failed to update permissions for %1$s</string>
+ <string name="admin.change_email">Change Email</string>
+ <string name="admin.change_email_success">Successfully changed email for %1$s</string>
+ <string name="admin.change_email_error">Failed to change email for %1$s</string>
+ <string name="admin.change_email_label">New Email:</string>
+ <string name="admin.change_email_invalid">Enter a valid email</string>
+ <string name="admin.change_password">Change Password</string>
+ <string name="admin.change_password_success">Successfully changed password for %1$s</string>
+ <string name="admin.change_password_error">Failed to change password for %1$s</string>
+ <string name="admin.change_password_label">New Password:</string>
+ <string name="admin.change_password_invalid">Enter a valid password</string>
+ <string name="admin.delete_user">Delete User</string>
+ <string name="admin.delete_user_success">Successfully deleted %1$s</string>
+ <string name="admin.delete_user_error">Failed to delete %1$s</string>
+
<string name="music_service.retry">A network error occurred. Retrying %1$d of %2$d.</string>
<string name="background_task.wait">Please wait...</string>
diff --git a/res/values/themes.xml b/res/values/themes.xml
index 677ee4e1..6611cff7 100644
--- a/res/values/themes.xml
+++ b/res/values/themes.xml
@@ -24,6 +24,8 @@
<item name="downloading">@drawable/downloading_light</item>
<item name="bookmark">@drawable/ic_menu_bookmark_light</item>
<item name="share">@drawable/ic_menu_share_light</item>
+ <item name="add_person">@drawable/ic_menu_add_person_light</item>
+ <item name="password">@drawable/ic_menu_password_light</item>
<item name="drawerItemsIcons">@array/drawerItemIconsLight</item>
<item name="android:textViewStyle">@style/DSub.TextViewStyle</item>
<item name="android:buttonStyle">@style/DSub.ButtonStyle.Light</item>
@@ -53,6 +55,8 @@
<item name="downloading">@drawable/downloading_dark</item>
<item name="bookmark">@drawable/ic_menu_bookmark_dark</item>
<item name="share">@drawable/ic_menu_share_dark</item>
+ <item name="add_person">@drawable/ic_menu_add_person_dark</item>
+ <item name="password">@drawable/ic_menu_password_dark</item>
<item name="drawerItemsIcons">@array/drawerItemIconsDark</item>
<item name="android:textViewStyle">@style/DSub.TextViewStyle</item>
<item name="android:buttonStyle">@style/DSub.ButtonStyle.Dark</item>
@@ -85,6 +89,8 @@
<item name="downloading">@drawable/downloading_dark</item>
<item name="bookmark">@drawable/ic_menu_bookmark_dark</item>
<item name="share">@drawable/ic_menu_share_dark</item>
+ <item name="add_person">@drawable/ic_menu_add_person_dark</item>
+ <item name="password">@drawable/ic_menu_password_dark</item>
<item name="drawerItemsIcons">@array/drawerItemIconsDark</item>
<item name="android:textViewStyle">@style/DSub.TextViewStyle</item>
<item name="android:buttonStyle">@style/DSub.ButtonStyle.Dark</item>
diff --git a/res/xml/settings.xml b/res/xml/settings.xml
index 9f1b5b67..cf3868f5 100644
--- a/res/xml/settings.xml
+++ b/res/xml/settings.xml
@@ -139,6 +139,12 @@
android:summary="@string/settings.shares_enabled_summary"
android:key="sharedEnabled"
android:defaultValue="true"/>
+
+ <CheckBoxPreference
+ android:title="@string/settings.admin_enabled"
+ android:summary="@string/settings.admin_enabled_summary"
+ android:key="adminEnabled"
+ android:defaultValue="true"/>
</PreferenceCategory>
<PreferenceCategory
diff --git a/src/github/daneren2005/dsub/activity/SubsonicActivity.java b/src/github/daneren2005/dsub/activity/SubsonicActivity.java
index 646fa295..edec6b90 100644
--- a/src/github/daneren2005/dsub/activity/SubsonicActivity.java
+++ b/src/github/daneren2005/dsub/activity/SubsonicActivity.java
@@ -75,7 +75,7 @@ public class SubsonicActivity extends ActionBarActivity implements OnItemSelecte
private String[] drawerItemsDescriptions;
private String[] drawerItems;
private boolean drawerIdle = true;
- private boolean[] enabledItems = {true, true, true, true};
+ private boolean[] enabledItems = {true, true, true, true, true};
private boolean destroyed = false;
private boolean finished = false;
protected List<SubsonicFragment> backStack = new ArrayList<SubsonicFragment>();
@@ -386,8 +386,9 @@ public class SubsonicActivity extends ActionBarActivity implements OnItemSelecte
boolean bookmarksEnabled = prefs.getBoolean(Constants.PREFERENCES_KEY_BOOKMARKS_ENABLED, true) && !Util.isOffline(this);
boolean sharedEnabled = prefs.getBoolean(Constants.PREFERENCES_KEY_SHARED_ENABLED, true) && !Util.isOffline(this);
boolean chatEnabled = prefs.getBoolean(Constants.PREFERENCES_KEY_CHAT_ENABLED, true) && !Util.isOffline(this);
+ boolean adminEnabled = prefs.getBoolean(Constants.PREFERENCES_KEY_ADMIN_ENABLED, true) && !Util.isOffline(this);
- if(drawerItems == null || !enabledItems[0] == podcastsEnabled || !enabledItems[1] == bookmarksEnabled || !enabledItems[2] == sharedEnabled || !enabledItems[3] == chatEnabled) {
+ if(drawerItems == null || !enabledItems[0] == podcastsEnabled || !enabledItems[1] == bookmarksEnabled || !enabledItems[2] == sharedEnabled || !enabledItems[3] == chatEnabled || !enabledItems[4] == adminEnabled) {
drawerItems = getResources().getStringArray(R.array.drawerItems);
drawerItemsDescriptions = getResources().getStringArray(R.array.drawerItemsDescriptions);
@@ -419,15 +420,19 @@ public class SubsonicActivity extends ActionBarActivity implements OnItemSelecte
if(!chatEnabled) {
drawerItemsVisibleList.set(6, false);
}
- if(!getIntent().hasExtra(Constants.INTENT_EXTRA_NAME_DOWNLOAD_VIEW)) {
+ if(!adminEnabled) {
drawerItemsVisibleList.set(7, false);
}
+ if(!getIntent().hasExtra(Constants.INTENT_EXTRA_NAME_DOWNLOAD_VIEW)) {
+ drawerItemsVisibleList.set(8, false);
+ }
drawerList.setAdapter(drawerAdapter = new DrawerAdapter(this, drawerItemsList, drawerItemsIconsList, drawerItemsVisibleList));
enabledItems[0] = podcastsEnabled;
enabledItems[1] = bookmarksEnabled;
enabledItems[2] = sharedEnabled;
enabledItems[3] = chatEnabled;
+ enabledItems[4] = adminEnabled;
String fragmentType = getIntent().getStringExtra(Constants.INTENT_EXTRA_FRAGMENT_TYPE);
if(fragmentType != null && lastSelectedPosition == 0) {
diff --git a/src/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java b/src/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java
index ebedf9b1..7fafae60 100644
--- a/src/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java
+++ b/src/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java
@@ -44,6 +44,7 @@ import java.util.concurrent.TimeUnit;
import github.daneren2005.dsub.R;
import github.daneren2005.dsub.domain.MusicDirectory;
import github.daneren2005.dsub.domain.PlayerState;
+import github.daneren2005.dsub.fragments.AdminFragment;
import github.daneren2005.dsub.fragments.ChatFragment;
import github.daneren2005.dsub.fragments.DownloadFragment;
import github.daneren2005.dsub.fragments.MainFragment;
@@ -62,6 +63,7 @@ import github.daneren2005.dsub.util.BackgroundTask;
import github.daneren2005.dsub.util.Constants;
import github.daneren2005.dsub.util.FileUtil;
import github.daneren2005.dsub.util.SilentBackgroundTask;
+import github.daneren2005.dsub.util.UserUtil;
import github.daneren2005.dsub.util.Util;
import github.daneren2005.dsub.view.ChangeLog;
@@ -392,6 +394,8 @@ public class SubsonicFragmentActivity extends SubsonicActivity {
return new SelectBookmarkFragment();
} else if("Share".equals(fragmentType)) {
return new SelectShareFragment();
+ } else if("Admin".equals(fragmentType)) {
+ return new AdminFragment();
} else if("Download".equals(fragmentType)) {
return new DownloadFragment();
} else {
@@ -464,6 +468,8 @@ public class SubsonicFragmentActivity extends SubsonicActivity {
editor.putInt(Constants.PREFERENCES_KEY_SERVER_COUNT, 3);
editor.commit();
}
+
+ UserUtil.seedCurrentUser(this);
}
private void resetCacheLocation(SharedPreferences prefs) {
diff --git a/src/github/daneren2005/dsub/domain/User.java b/src/github/daneren2005/dsub/domain/User.java
new file mode 100644
index 00000000..ca124a58
--- /dev/null
+++ b/src/github/daneren2005/dsub/domain/User.java
@@ -0,0 +1,86 @@
+/*
+ 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 2014 (C) Scott Jackson
+*/
+
+package github.daneren2005.dsub.domain;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+public class User implements Serializable {
+ private String username;
+ private String password;
+ private String email;
+
+ private List<Setting> settings = new ArrayList<Setting>();
+
+ public User() {
+
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public List<Setting> getSettings() {
+ return settings;
+ }
+ public void addSetting(String name, Boolean value) {
+ settings.add(new Setting(name, value));
+ }
+
+ public static class Setting implements Serializable {
+ String name;
+ Boolean value;
+
+ public Setting() {
+
+ }
+ public Setting(String name, Boolean value) {
+ this.name = name;
+ this.value = value;
+ }
+
+ public String getName() {
+ return name;
+ }
+ public Boolean getValue() {
+ return value;
+ }
+ public void setValue(Boolean value) {
+ this.value = value;
+ }
+ }
+}
diff --git a/src/github/daneren2005/dsub/fragments/AdminFragment.java b/src/github/daneren2005/dsub/fragments/AdminFragment.java
new file mode 100644
index 00000000..19aa4b95
--- /dev/null
+++ b/src/github/daneren2005/dsub/fragments/AdminFragment.java
@@ -0,0 +1,146 @@
+/*
+ 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 2014 (C) Scott Jackson
+*/
+
+package github.daneren2005.dsub.fragments;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.domain.User;
+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.service.parser.SubsonicRESTException;
+import github.daneren2005.dsub.util.Constants;
+import github.daneren2005.dsub.util.ProgressListener;
+import github.daneren2005.dsub.util.SilentBackgroundTask;
+import github.daneren2005.dsub.util.UserUtil;
+import github.daneren2005.dsub.util.Util;
+import github.daneren2005.dsub.view.UserAdapter;
+
+public class AdminFragment extends SelectListFragment<User> {
+ private static String TAG = AdminFragment.class.getSimpleName();
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if(super.onOptionsItemSelected(item)) {
+ return true;
+ }
+
+ switch (item.getItemId()) {
+ case R.id.menu_add_user:
+ UserUtil.addNewUser(context, this);
+ break;
+ }
+
+ return false;
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
+ super.onCreateContextMenu(menu, view, menuInfo);
+
+ MenuInflater inflater = context.getMenuInflater();
+ if(UserUtil.isCurrentAdmin()) {
+ inflater.inflate(R.menu.admin_context, menu);
+ } else {
+ inflater.inflate(R.menu.admin_context_user, menu);
+ }
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem menuItem) {
+ AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo();
+ User user = objects.get(info.position);
+
+ switch(menuItem.getItemId()) {
+ case R.id.admin_change_email:
+ UserUtil.changeEmail(context, user);
+ break;
+ case R.id.admin_change_password:
+ UserUtil.changePassword(context, user);
+ break;
+ case R.id.admin_delete_user:
+ UserUtil.deleteUser(context, user, adapter);
+ break;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int getOptionsMenu() {
+ if(UserUtil.isCurrentAdmin()) {
+ return R.menu.admin;
+ } else {
+ return R.menu.empty;
+ }
+ }
+
+ @Override
+ public ArrayAdapter getAdapter(List<User> objs) {
+ return new UserAdapter(context, objs, getImageLoader());
+ }
+
+ @Override
+ public List<User> getObjects(MusicService musicService, boolean refresh, ProgressListener listener) throws Exception {
+ try {
+ // Will only work if user is admin
+ return musicService.getUsers(refresh, context, listener);
+ } catch(SubsonicRESTException e) {
+ // Delete cached users if not allowed to get them
+ String s = Util.getRestUrl(context, null, false);
+ String cache = "users-" + s.hashCode() + ".ser";
+ File file = new File(context.getCacheDir(), cache);
+ file.delete();
+
+ List<User> users = new ArrayList<User>();
+ users.add(musicService.getUser(refresh, UserUtil.getCurrentUsername(context), context, listener));
+ return users;
+ }
+ }
+
+ @Override
+ public int getTitleResource() {
+ return R.string.button_bar_admin;
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ User user = (User) parent.getItemAtPosition(position);
+
+ SubsonicFragment fragment = new UserFragment();
+ Bundle args = new Bundle();
+ args.putSerializable(Constants.INTENT_EXTRA_NAME_ID, user);
+ fragment.setArguments(args);
+
+ replaceFragment(fragment);
+ }
+}
diff --git a/src/github/daneren2005/dsub/fragments/MainFragment.java b/src/github/daneren2005/dsub/fragments/MainFragment.java
index 88baf717..e9f06e45 100644
--- a/src/github/daneren2005/dsub/fragments/MainFragment.java
+++ b/src/github/daneren2005/dsub/fragments/MainFragment.java
@@ -25,6 +25,7 @@ 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.UserUtil;
import github.daneren2005.dsub.view.MergeAdapter;
import github.daneren2005.dsub.util.Util;
import github.daneren2005.dsub.service.MusicService;
@@ -207,6 +208,7 @@ public class MainFragment extends SubsonicFragment {
}
Util.setActiveServer(context, instance);
context.invalidate();
+ UserUtil.seedCurrentUser(context);
}
}
@@ -218,13 +220,15 @@ public class MainFragment extends SubsonicFragment {
if (service != null) {
service.setOnline(isOffline);
}
-
+
+ // Coming back online
if(isOffline) {
int scrobblesCount = Util.offlineScrobblesCount(context);
int starsCount = Util.offlineStarsCount(context);
if(scrobblesCount > 0 || starsCount > 0){
showOfflineSyncDialog(scrobblesCount, starsCount);
}
+ UserUtil.seedCurrentUser(context);
}
}
diff --git a/src/github/daneren2005/dsub/fragments/SelectListFragment.java b/src/github/daneren2005/dsub/fragments/SelectListFragment.java
index 4938fb3b..1c77ad68 100644
--- a/src/github/daneren2005/dsub/fragments/SelectListFragment.java
+++ b/src/github/daneren2005/dsub/fragments/SelectListFragment.java
@@ -107,12 +107,14 @@ public abstract class SelectListFragment<T> extends SubsonicFragment implements
@Override
public boolean onOptionsItemSelected(MenuItem item) {
return super.onOptionsItemSelected(item);
-
}
@Override
protected void refresh(final boolean refresh) {
- setTitle(getTitleResource());
+ int titleRes = getTitleResource();
+ if(titleRes != 0) {
+ setTitle(getTitleResource());
+ }
listView.setVisibility(View.INVISIBLE);
emptyView.setVisibility(View.GONE);
diff --git a/src/github/daneren2005/dsub/fragments/UserFragment.java b/src/github/daneren2005/dsub/fragments/UserFragment.java
new file mode 100644
index 00000000..fbd672c0
--- /dev/null
+++ b/src/github/daneren2005/dsub/fragments/UserFragment.java
@@ -0,0 +1,130 @@
+/*
+ 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 2014 (C) Scott Jackson
+*/
+
+package github.daneren2005.dsub.fragments;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.support.v4.widget.SwipeRefreshLayout;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.activity.SubsonicActivity;
+import github.daneren2005.dsub.domain.User;
+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.ImageLoader;
+import github.daneren2005.dsub.util.SilentBackgroundTask;
+import github.daneren2005.dsub.util.UserUtil;
+import github.daneren2005.dsub.util.Util;
+import github.daneren2005.dsub.view.SettingsAdapter;
+
+public class UserFragment extends SubsonicFragment{
+ private ListView listView;
+ private User user;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
+ rootView = inflater.inflate(R.layout.abstract_list_fragment, container, false);
+
+ refreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.refresh_layout);
+ refreshLayout.setEnabled(false);
+
+ Bundle args = getArguments();
+ user = (User) args.getSerializable(Constants.INTENT_EXTRA_NAME_ID);
+
+ listView = (ListView)rootView.findViewById(R.id.fragment_list);
+ createHeader();
+ listView.setAdapter(new SettingsAdapter(context, user.getSettings(), UserUtil.isCurrentAdmin()));
+
+ setTitle(user.getUsername());
+
+ return rootView;
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ activity.invalidateOptionsMenu();
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) {
+ // For some reason this is called before onAttach
+ if(!primaryFragment || context == null) {
+ return;
+ }
+
+ if(UserUtil.isCurrentAdmin() && Util.checkServerVersion(context, "1.10")) {
+ menuInflater.inflate(R.menu.user, menu);
+ } else {
+ menuInflater.inflate(R.menu.user_user, menu);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if(super.onOptionsItemSelected(item)) {
+ return true;
+ }
+
+ switch (item.getItemId()) {
+ case R.id.menu_update_permissions:
+ UserUtil.updateSettings(context, user);
+ return true;
+ case R.id.menu_change_password:
+ UserUtil.changePassword(context, user);
+ return true;
+ case R.id.menu_change_email:
+ UserUtil.changeEmail(context, user);
+ return true;
+ }
+
+ return false;
+ }
+
+ private void createHeader() {
+ View header = LayoutInflater.from(context).inflate(R.layout.user_header, listView, false);
+
+ final ImageLoader imageLoader = getImageLoader();
+ ImageView coverArtView = (ImageView) header.findViewById(R.id.user_avatar);
+ imageLoader.loadAvatar(context, coverArtView, user.getUsername());
+
+ TextView usernameView = (TextView) header.findViewById(R.id.user_username);
+ usernameView.setText(user.getUsername());
+
+ final TextView emailView = (TextView) header.findViewById(R.id.user_email);
+ if(user.getEmail() != null) {
+ emailView.setText(user.getEmail());
+ } else {
+ emailView.setVisibility(View.GONE);
+ }
+
+ listView.addHeaderView(header);
+ }
+}
diff --git a/src/github/daneren2005/dsub/service/CachedMusicService.java b/src/github/daneren2005/dsub/service/CachedMusicService.java
index d0740704..bd43fbed 100644
--- a/src/github/daneren2005/dsub/service/CachedMusicService.java
+++ b/src/github/daneren2005/dsub/service/CachedMusicService.java
@@ -41,6 +41,7 @@ import github.daneren2005.dsub.domain.PodcastChannel;
import github.daneren2005.dsub.domain.SearchCritera;
import github.daneren2005.dsub.domain.SearchResult;
import github.daneren2005.dsub.domain.Share;
+import github.daneren2005.dsub.domain.User;
import github.daneren2005.dsub.domain.Version;
import github.daneren2005.dsub.util.SilentBackgroundTask;
import github.daneren2005.dsub.util.ProgressListener;
@@ -507,6 +508,80 @@ public class CachedMusicService implements MusicService {
}
@Override
+ public User getUser(boolean refresh, String username, Context context, ProgressListener progressListener) throws Exception {
+ User result = null;
+
+ if(!refresh) {
+ result = FileUtil.deserialize(context, getCacheName(context, "user-" + username), User.class);
+ }
+
+ if(result == null) {
+ result = musicService.getUser(refresh, username, context, progressListener);
+ FileUtil.serialize(context, result, getCacheName(context, "user-" + username));
+ }
+
+ return result;
+ }
+
+ @Override
+ public List<User> getUsers(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
+ List<User> result = null;
+
+ if(!refresh) {
+ result = FileUtil.deserialize(context, getCacheName(context, "users"), ArrayList.class);
+ }
+
+ if(result == null) {
+ result = musicService.getUsers(refresh, context, progressListener);
+ FileUtil.serialize(context, new ArrayList<User>(result), getCacheName(context, "users"));
+ }
+
+ return result;
+ }
+
+ @Override
+ public void createUser(User user, Context context, ProgressListener progressListener) throws Exception {
+ musicService.createUser(user, context, progressListener);
+ }
+
+ @Override
+ public void updateUser(User user, Context context, ProgressListener progressListener) throws Exception {
+ musicService.updateUser(user, context, progressListener);
+
+ // Delete cached users if anything updated
+ File file = new File(context.getCacheDir(), getCacheName(context, "users"));
+ file.delete();
+ }
+
+ @Override
+ public void deleteUser(String username, Context context, ProgressListener progressListener) throws Exception {
+ musicService.deleteUser(username, context, progressListener);
+
+ // Delete cached users if any have been removed from list
+ File file = new File(context.getCacheDir(), getCacheName(context, "users"));
+ file.delete();
+ }
+
+ @Override
+ public void changeEmail(String username, String email, Context context, ProgressListener progressListener) throws Exception {
+ musicService.changeEmail(username, email, context, progressListener);
+
+ // Delete cached users if any have been removed from list
+ File file = new File(context.getCacheDir(), getCacheName(context, "users"));
+ file.delete();
+ }
+
+ @Override
+ public void changePassword(String username, String password, Context context, ProgressListener progressListener) throws Exception {
+ musicService.changePassword(username, password, context, progressListener);
+ }
+
+ @Override
+ public Bitmap getAvatar(String username, int size, Context context, ProgressListener progressListener) throws Exception {
+ return musicService.getAvatar(username, size, context, progressListener);
+ }
+
+ @Override
public int processOfflineSyncs(final Context context, final ProgressListener progressListener) throws Exception{
return musicService.processOfflineSyncs(context, progressListener);
}
diff --git a/src/github/daneren2005/dsub/service/MusicService.java b/src/github/daneren2005/dsub/service/MusicService.java
index 0522a4be..cefd0c23 100644
--- a/src/github/daneren2005/dsub/service/MusicService.java
+++ b/src/github/daneren2005/dsub/service/MusicService.java
@@ -38,6 +38,7 @@ import github.daneren2005.dsub.domain.PodcastChannel;
import github.daneren2005.dsub.domain.SearchCritera;
import github.daneren2005.dsub.domain.SearchResult;
import github.daneren2005.dsub.domain.Share;
+import github.daneren2005.dsub.domain.User;
import github.daneren2005.dsub.domain.Version;
import github.daneren2005.dsub.util.SilentBackgroundTask;
import github.daneren2005.dsub.util.ProgressListener;
@@ -160,6 +161,22 @@ public interface MusicService {
void createBookmark(String id, int position, String comment, Context context, ProgressListener progressListener) throws Exception;
void deleteBookmark(String id, Context context, ProgressListener progressListener) throws Exception;
+
+ User getUser(boolean refresh, String username, Context context, ProgressListener progressListener) throws Exception;
+
+ List<User> getUsers(boolean refresh, Context context, ProgressListener progressListener) throws Exception;
+
+ void createUser(User user, Context context, ProgressListener progressListener) throws Exception;
+
+ void updateUser(User user, Context context, ProgressListener progressListener) throws Exception;
+
+ void deleteUser(String username, Context context, ProgressListener progressListener) throws Exception;
+
+ void changeEmail(String username, String email, Context context, ProgressListener progressListener) throws Exception;
+
+ void changePassword(String username, String password, Context context, ProgressListener progressListener) throws Exception;
+
+ Bitmap getAvatar(String username, int size, Context context, ProgressListener progressListener) throws Exception;
int processOfflineSyncs(final Context context, final ProgressListener progressListener) throws Exception;
diff --git a/src/github/daneren2005/dsub/service/OfflineMusicService.java b/src/github/daneren2005/dsub/service/OfflineMusicService.java
index cf697441..a92e41d1 100644
--- a/src/github/daneren2005/dsub/service/OfflineMusicService.java
+++ b/src/github/daneren2005/dsub/service/OfflineMusicService.java
@@ -46,6 +46,7 @@ import github.daneren2005.dsub.domain.Playlist;
import github.daneren2005.dsub.domain.PodcastChannel;
import github.daneren2005.dsub.domain.SearchCritera;
import github.daneren2005.dsub.domain.SearchResult;
+import github.daneren2005.dsub.domain.User;
import github.daneren2005.dsub.util.Constants;
import github.daneren2005.dsub.util.FileUtil;
import github.daneren2005.dsub.util.ProgressListener;
@@ -688,8 +689,48 @@ public class OfflineMusicService extends RESTMusicService {
public void deleteBookmark(String id, Context context, ProgressListener progressListener) throws Exception {
throw new OfflineException("Deleting bookmarks not available in offline mode");
}
-
- @Override
+
+ @Override
+ public User getUser(boolean refresh, String username, Context context, ProgressListener progressListener) throws Exception {
+ throw new OfflineException("Getting user not available in offline mode");
+ }
+
+ @Override
+ public List<User> getUsers(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
+ throw new OfflineException("Getting users not available in offline mode");
+ }
+
+ @Override
+ public void createUser(User user, Context context, ProgressListener progressListener) throws Exception {
+ throw new OfflineException("Creating users not available in offline mode");
+ }
+
+ @Override
+ public void updateUser(User user, Context context, ProgressListener progressListener) throws Exception {
+ throw new OfflineException("Updating users not available in offline mode");
+ }
+
+ @Override
+ public void deleteUser(String username, Context context, ProgressListener progressListener) throws Exception {
+ throw new OfflineException("Deleting users not available in offline mode");
+ }
+
+ @Override
+ public void changeEmail(String username, String email, Context context, ProgressListener progressListener) throws Exception {
+ throw new OfflineException("Changing email not available in offline mode");
+ }
+
+ @Override
+ public void changePassword(String username, String password, Context context, ProgressListener progressListener) throws Exception {
+ throw new OfflineException("Changing passwords not available in offline mode");
+ }
+
+ @Override
+ public Bitmap getAvatar(String username, int size, Context context, ProgressListener progressListener) throws Exception {
+ return null;
+ }
+
+ @Override
public int processOfflineSyncs(final Context context, final ProgressListener progressListener) throws Exception{
throw new OfflineException("Offline scrobble cached can not be processes while in offline mode");
}
diff --git a/src/github/daneren2005/dsub/service/RESTMusicService.java b/src/github/daneren2005/dsub/service/RESTMusicService.java
index eeddcaa2..3299ec4b 100644
--- a/src/github/daneren2005/dsub/service/RESTMusicService.java
+++ b/src/github/daneren2005/dsub/service/RESTMusicService.java
@@ -88,6 +88,7 @@ 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.UserParser;
import github.daneren2005.dsub.service.parser.VersionParser;
import github.daneren2005.dsub.service.ssl.SSLSocketFactory;
import github.daneren2005.dsub.service.ssl.TrustSelfSignedStrategy;
@@ -1190,6 +1191,168 @@ public class RESTMusicService implements MusicService {
}
@Override
+ public User getUser(boolean refresh, String username, Context context, ProgressListener progressListener) throws Exception {
+ Reader reader = getReader(context, progressListener, "getUser", null, Arrays.asList("username"), Arrays.<Object>asList(username));
+ try {
+ List<User> users = new UserParser(context).parse(reader, progressListener);
+ if(users.size() > 0) {
+ // Should only have returned one anyways
+ return users.get(0);
+ } else {
+ return null;
+ }
+ } finally {
+ Util.close(reader);
+ }
+ }
+
+ @Override
+ public List<User> getUsers(boolean refresh, Context context, ProgressListener progressListener) throws Exception {
+ checkServerVersion(context, "1.8", "Getting user list is not supported");
+
+ Reader reader = getReader(context, progressListener, "getUsers", null);
+ try {
+ return new UserParser(context).parse(reader, progressListener);
+ } finally {
+ Util.close(reader);
+ }
+ }
+
+ @Override
+ public void createUser(User user, Context context, ProgressListener progressListener) throws Exception {
+ List<String> names = new ArrayList<String>();
+ List<Object> values = new ArrayList<Object>();
+
+ names.add("username");
+ values.add(user.getUsername());
+ names.add("email");
+ values.add(user.getEmail());
+ names.add("password");
+ values.add(user.getPassword());
+
+ for(User.Setting setting: user.getSettings()) {
+ names.add(setting.getName());
+ values.add(setting.getValue());
+ }
+
+ Reader reader = getReader(context, progressListener, "createUser", null, names, values);
+ try {
+ new ErrorParser(context).parse(reader);
+ } finally {
+ Util.close(reader);
+ }
+ }
+
+ @Override
+ public void updateUser(User user, Context context, ProgressListener progressListener) throws Exception {
+ checkServerVersion(context, "1.10", "Updating user is not supported");
+
+ List<String> names = new ArrayList<String>();
+ List<Object> values = new ArrayList<Object>();
+
+ names.add("username");
+ values.add(user.getUsername());
+
+ for(User.Setting setting: user.getSettings()) {
+ if(setting.getName().indexOf("Role") != -1) {
+ names.add(setting.getName());
+ values.add(setting.getValue());
+ }
+ }
+
+ Reader reader = getReader(context, progressListener, "updateUser", null, names, values);
+ try {
+ new ErrorParser(context).parse(reader);
+ } finally {
+ Util.close(reader);
+ }
+ }
+
+ @Override
+ public void deleteUser(String username, Context context, ProgressListener progressListener) throws Exception {
+ Reader reader = getReader(context, progressListener, "deleteUser", null, Arrays.asList("username"), Arrays.<Object>asList(username));
+ try {
+ new ErrorParser(context).parse(reader);
+ } finally {
+ Util.close(reader);
+ }
+ }
+
+ @Override
+ public void changeEmail(String username, String email, Context context, ProgressListener progressListener) throws Exception {
+ Reader reader = getReader(context, progressListener, "updateUser", null, Arrays.asList("username", "email"), Arrays.<Object>asList(username, email));
+ try {
+ new ErrorParser(context).parse(reader);
+ } finally {
+ Util.close(reader);
+ }
+ }
+
+ @Override
+ public void changePassword(String username, String password, Context context, ProgressListener progressListener) throws Exception {
+ Reader reader = getReader(context, progressListener, "changePassword", null, Arrays.asList("username", "password"), Arrays.<Object>asList(username, password));
+ try {
+ new ErrorParser(context).parse(reader);
+ } finally {
+ Util.close(reader);
+ }
+ }
+
+ @Override
+ public Bitmap getAvatar(String username, int size, Context context, ProgressListener progressListener) throws Exception {
+ // Return silently if server is too old
+ if (!Util.checkServerVersion(context, "1.8")) {
+ return null;
+ }
+
+ // Synchronize on the username so that we don't download concurrently for
+ // the same user.
+ synchronized (username) {
+ // Use cached file, if existing.
+ Bitmap bitmap = FileUtil.getAvatarBitmap(context, username, size);
+ if(bitmap != null) {
+ return bitmap;
+ }
+
+ String url = Util.getRestUrl(context, "getAvatar");
+ InputStream in = null;
+ try
+ {
+ List<String> parameterNames;
+ List<Object> parameterValues;
+
+ parameterNames = Collections.singletonList("username");
+ parameterValues = Arrays.<Object>asList(username);
+
+ HttpEntity entity = getEntityForURL(context, url, null, parameterNames, parameterValues, progressListener);
+ in = entity.getContent();
+
+ // If content type is XML, an error occurred. Get it.
+ String contentType = Util.getContentType(entity);
+ if (contentType != null && contentType.startsWith("text/xml"))
+ {
+ new ErrorParser(context).parse(new InputStreamReader(in, Constants.UTF_8));
+ return null; // Never reached.
+ }
+
+ byte[] bytes = Util.toByteArray(in);
+ OutputStream out = null;
+ try {
+ out = new FileOutputStream(FileUtil.getAvatarFile(context, username));
+ out.write(bytes);
+ } finally {
+ Util.close(out);
+ }
+
+ return FileUtil.getSampledBitmap(bytes, size);
+ }
+ finally {
+ Util.close(in);
+ }
+ }
+ }
+
+ @Override
public int processOfflineSyncs(final Context context, final ProgressListener progressListener) throws Exception{
return processOfflineScrobbles(context, progressListener) + processOfflineStars(context, progressListener);
}
diff --git a/src/github/daneren2005/dsub/service/parser/UserParser.java b/src/github/daneren2005/dsub/service/parser/UserParser.java
new file mode 100644
index 00000000..5b2f8fc1
--- /dev/null
+++ b/src/github/daneren2005/dsub/service/parser/UserParser.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 2014 (C) Scott Jackson
+*/
+
+package github.daneren2005.dsub.service.parser;
+
+import android.content.Context;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+
+import github.daneren2005.dsub.domain.User;
+import github.daneren2005.dsub.util.ProgressListener;
+
+public class UserParser extends AbstractParser {
+
+ public UserParser(Context context) {
+ super(context);
+ }
+
+ public List<User> parse(Reader reader, ProgressListener progressListener) throws Exception {
+ init(reader);
+ List<User> result = new ArrayList<User>();
+ int eventType;
+
+ do {
+ eventType = nextParseEvent();
+ if (eventType == XmlPullParser.START_TAG) {
+ String name = getElementName();
+ if ("user".equals(name)) {
+ User user = new User();
+
+ user.setUsername(get("username"));
+ user.setEmail(get("email"));
+ parseSetting(user, "scrobblingEnabled");
+ parseSetting(user, "adminRole");
+ parseSetting(user, "settingsRole");
+ parseSetting(user, "downloadRole");
+ parseSetting(user, "uploadRole");
+ // Depreciated: parseSetting(user, "playlistRole");
+ parseSetting(user, "coverArtRole");
+ parseSetting(user, "commentRole");
+ parseSetting(user, "podcastRole");
+ parseSetting(user, "streamRole");
+ parseSetting(user, "jukeboxRole");
+ parseSetting(user, "shareRole");
+
+ result.add(user);
+ } else if ("error".equals(name)) {
+ handleError();
+ }
+ }
+ } while (eventType != XmlPullParser.END_DOCUMENT);
+
+ validate();
+
+ return result;
+ }
+
+ private void parseSetting(User user, String name) {
+ user.addSetting(name, getBoolean(name));
+ }
+} \ No newline at end of file
diff --git a/src/github/daneren2005/dsub/util/Constants.java b/src/github/daneren2005/dsub/util/Constants.java
index 2b7f8757..34bf734d 100644
--- a/src/github/daneren2005/dsub/util/Constants.java
+++ b/src/github/daneren2005/dsub/util/Constants.java
@@ -141,6 +141,7 @@ public final class Constants {
public static final String PREFERENCES_KEY_OVERRIDE_SYSTEM_LANGUAGE = "overrideSystemLanguage";
public static final String PREFERENCES_KEY_PLAY_NOW_AFTER = "playNowAfter";
public static final String PREFERENCES_KEY_LARGE_ALBUM_ART = "largeAlbumArt";
+ public static final String PREFERENCES_KEY_ADMIN_ENABLED = "adminEnabled";
public static final String OFFLINE_SCROBBLE_COUNT = "scrobbleCount";
public static final String OFFLINE_SCROBBLE_ID = "scrobbleID";
diff --git a/src/github/daneren2005/dsub/util/FileUtil.java b/src/github/daneren2005/dsub/util/FileUtil.java
index a68ded45..e84f6eb2 100644
--- a/src/github/daneren2005/dsub/util/FileUtil.java
+++ b/src/github/daneren2005/dsub/util/FileUtil.java
@@ -200,6 +200,34 @@ public class FileUtil {
}
return null;
}
+
+ public static File getAvatarDirectory(Context context) {
+ File avatarDir = new File(getSubsonicDirectory(context), "avatars");
+ ensureDirectoryExistsAndIsReadWritable(avatarDir);
+ ensureDirectoryExistsAndIsReadWritable(new File(avatarDir, ".nomedia"));
+ return avatarDir;
+ }
+
+ public static File getAvatarFile(Context context, String username) {
+ return new File(getAvatarDirectory(context), Util.md5Hex(username) + ".jpeg");
+ }
+
+ public static Bitmap getAvatarBitmap(Context context, String username, int size) {
+ File avatarFile = getAvatarFile(context, username);
+ if (avatarFile.exists()) {
+ final BitmapFactory.Options opt = new BitmapFactory.Options();
+ opt.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(avatarFile.getPath(), opt);
+ opt.inPurgeable = true;
+ opt.inSampleSize = Util.calculateInSampleSize(opt, size, Util.getScaledHeight(opt.outHeight, opt.outWidth, size));
+ opt.inJustDecodeBounds = false;
+
+ Bitmap bitmap = BitmapFactory.decodeFile(avatarFile.getPath(), opt);
+ return bitmap == null ? null : getScaledBitmap(bitmap, size);
+ }
+ return null;
+ }
+
public static Bitmap getSampledBitmap(byte[] bytes, int size) {
final BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inJustDecodeBounds = true;
diff --git a/src/github/daneren2005/dsub/util/ImageLoader.java b/src/github/daneren2005/dsub/util/ImageLoader.java
index b1b00cc5..f9c5fed5 100644
--- a/src/github/daneren2005/dsub/util/ImageLoader.java
+++ b/src/github/daneren2005/dsub/util/ImageLoader.java
@@ -18,7 +18,6 @@
*/
package github.daneren2005.dsub.util;
-import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
@@ -26,7 +25,6 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
import android.media.RemoteControlClient;
import android.os.Build;
-import android.os.Handler;
import android.util.DisplayMetrics;
import android.util.Log;
import android.support.v4.util.LruCache;
@@ -38,9 +36,6 @@ import github.daneren2005.dsub.domain.MusicDirectory;
import github.daneren2005.dsub.service.MusicService;
import github.daneren2005.dsub.service.MusicServiceFactory;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-
/**
* Asynchronous loading of images, with caching.
* <p/>
@@ -56,6 +51,7 @@ public class ImageLoader {
private Bitmap nowPlaying;
private final int imageSizeDefault;
private final int imageSizeLarge;
+ private final int avatarSizeDefault;
private Drawable largeUnknownImage;
public ImageLoader(Context context) {
@@ -86,6 +82,7 @@ public class ImageLoader {
imageSizeDefault = context.getResources().getDrawable(R.drawable.unknown_album).getIntrinsicHeight();
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
imageSizeLarge = Math.round(Math.min(metrics.widthPixels, metrics.heightPixels));
+ avatarSizeDefault = context.getResources().getDrawable(R.drawable.ic_social_person).getIntrinsicHeight();
createLargeUnknownImage(context);
}
@@ -173,6 +170,17 @@ public class ImageLoader {
new RemoteControlClientImageTask(context, entry, imageSizeLarge, imageSizeLarge, false, remoteControl).execute();
}
+ public void loadAvatar(Context context, ImageView view, String username) {
+ Bitmap bitmap = cache.get(username);
+ if (bitmap != null && !bitmap.isRecycled()) {
+ Drawable drawable = Util.createDrawableFromBitmap(this.context, bitmap);
+ view.setImageDrawable(drawable);
+ return;
+ }
+
+ new AvatarTask(context, view, username).execute();
+ }
+
private String getKey(String coverArtId, int size) {
return coverArtId + size;
}
@@ -322,4 +330,45 @@ public class ImageLoader {
setImage(mRemoteControl, mDrawable);
}
}
+
+ private class AvatarTask extends SilentBackgroundTask<Void> {
+ private final Context mContext;
+ private final String mUsername;
+ private final ImageView mView;
+ private Drawable mDrawable;
+
+ public AvatarTask(Context context, ImageView view, String username) {
+ super(context);
+ mContext = context;
+ mView = view;
+ mUsername = username;
+ }
+
+ @Override
+ protected Void doInBackground() throws Throwable {
+ try {
+ MusicService musicService = MusicServiceFactory.getMusicService(mContext);
+ Bitmap bitmap = musicService.getAvatar(mUsername, avatarSizeDefault, mContext, null);
+ if(bitmap != null) {
+ cache.put(mUsername, bitmap);
+ // Make sure key is the most recently "used"
+ cache.get(mUsername);
+
+ mDrawable = Util.createDrawableFromBitmap(mContext, bitmap);
+ }
+ } catch (Throwable x) {
+ Log.e(TAG, "Failed to download album art.", x);
+ cancelled = true;
+ }
+
+ return null;
+ }
+
+ @Override
+ protected void done(Void result) {
+ if(mDrawable != null) {
+ mView.setImageDrawable(mDrawable);
+ }
+ }
+ }
}
diff --git a/src/github/daneren2005/dsub/util/UserUtil.java b/src/github/daneren2005/dsub/util/UserUtil.java
new file mode 100644
index 00000000..3e6d9ce7
--- /dev/null
+++ b/src/github/daneren2005/dsub/util/UserUtil.java
@@ -0,0 +1,345 @@
+/*
+ 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 2014 (C) Scott Jackson
+*/
+
+package github.daneren2005.dsub.util;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.SharedPreferences;
+import android.view.View;
+import android.widget.Adapter;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.io.File;
+
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.domain.User;
+import github.daneren2005.dsub.fragments.SubsonicFragment;
+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.view.SettingsAdapter;
+
+public final class UserUtil {
+ private static User currentUser;
+
+ public static void seedCurrentUser(final Context context) {
+ // Only try to seed if online
+ if(Util.isOffline(context)) {
+ return;
+ }
+
+ new SilentBackgroundTask<Void>(context) {
+ @Override
+ protected Void doInBackground() throws Throwable {
+ currentUser = MusicServiceFactory.getMusicService(context).getUser(false, getCurrentUsername(context), context, null);
+ return null;
+ }
+ }.execute();
+ }
+
+ public static User getCurrentUser(Context context) {
+ return currentUser;
+ }
+
+ public static String getCurrentUsername(Context context, int instance) {
+ SharedPreferences prefs = Util.getPreferences(context);
+ return prefs.getString(Constants.PREFERENCES_KEY_USERNAME + instance, null);
+ }
+
+ public static String getCurrentUsername(Context context) {
+ return getCurrentUsername(context, Util.getActiveServer(context));
+ }
+
+ public static boolean isCurrentAdmin() {
+ if(currentUser == null) {
+ return false;
+ } else {
+ return isCurrentRole("adminRole");
+ }
+ }
+
+ public static boolean isCurrentRole(String role) {
+ if(currentUser == null) {
+ return false;
+ }
+
+ for(User.Setting setting: currentUser.getSettings()) {
+ if(setting.getName().equals(role)) {
+ return setting.getValue() == true;
+ }
+ }
+
+ return false;
+ }
+
+ public static void changePassword(final Activity context, final User user) {
+ View layout = context.getLayoutInflater().inflate(R.layout.change_password, null);
+ final TextView passwordView = (TextView) layout.findViewById(R.id.new_password);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.admin_change_password)
+ .setView(layout)
+ .setPositiveButton(R.string.common_save, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ final String password = passwordView.getText().toString();
+ // Don't allow blank passwords
+ if ("".equals(password)) {
+ Util.toast(context, R.string.admin_change_password_invalid);
+ return;
+ }
+
+ new SilentBackgroundTask<Void>(context) {
+ @Override
+ protected Void doInBackground() throws Throwable {
+ MusicService musicService = MusicServiceFactory.getMusicService(context);
+ musicService.changePassword(user.getUsername(), password, context, null);
+ return null;
+ }
+
+ @Override
+ protected void done(Void v) {
+ Util.toast(context, context.getResources().getString(R.string.admin_change_password_success, user.getUsername()));
+ }
+
+ @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.admin_change_password_error, user.getUsername());
+ }
+
+ Util.toast(context, msg);
+ }
+ }.execute();
+ }
+ })
+ .setNegativeButton(R.string.common_cancel, null)
+ .setCancelable(true);
+
+ AlertDialog dialog = builder.create();
+ dialog.show();
+ }
+
+ public static void updateSettings(final Context context, final User user) {
+ new SilentBackgroundTask<Void>(context) {
+ @Override
+ protected Void doInBackground() throws Throwable {
+ MusicService musicService = MusicServiceFactory.getMusicService(context);
+ musicService.updateUser(user, context, null);
+ return null;
+ }
+
+ @Override
+ protected void done(Void v) {
+ Util.toast(context, context.getResources().getString(R.string.admin_update_permissions_success, user.getUsername()));
+ }
+
+ @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.admin_update_permissions_error, user.getUsername());
+ }
+
+ Util.toast(context, msg);
+ }
+ }.execute();
+ }
+
+ public static void changeEmail(final Activity context, final User user) {
+ View layout = context.getLayoutInflater().inflate(R.layout.change_email, null);
+ final TextView emailView = (TextView) layout.findViewById(R.id.new_email);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.admin_change_email)
+ .setView(layout)
+ .setPositiveButton(R.string.common_save, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ final String email = emailView.getText().toString();
+ // Don't allow blank emails
+ if ("".equals(email)) {
+ Util.toast(context, R.string.admin_change_email_invalid);
+ return;
+ }
+
+ new SilentBackgroundTask<Void>(context) {
+ @Override
+ protected Void doInBackground() throws Throwable {
+ MusicService musicService = MusicServiceFactory.getMusicService(context);
+ musicService.changeEmail(user.getUsername(), email, context, null);
+ return null;
+ }
+
+ @Override
+ protected void done(Void v) {
+ Util.toast(context, context.getResources().getString(R.string.admin_change_email_success, user.getUsername()));
+ }
+
+ @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.admin_change_email_error, user.getUsername());
+ }
+
+ Util.toast(context, msg);
+ }
+ }.execute();
+ }
+ })
+ .setNegativeButton(R.string.common_cancel, null)
+ .setCancelable(true);
+
+ AlertDialog dialog = builder.create();
+ dialog.show();
+ }
+
+ public static void deleteUser(final Context context, final User user, final ArrayAdapter adapter) {
+ Util.confirmDialog(context, R.string.common_delete, user.getUsername(), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ new SilentBackgroundTask<Void>(context) {
+ @Override
+ protected Void doInBackground() throws Throwable {
+ MusicService musicService = MusicServiceFactory.getMusicService(context);
+ musicService.deleteUser(user.getUsername(), context, null);
+ return null;
+ }
+
+ @Override
+ protected void done(Void v) {
+ if(adapter != null) {
+ adapter.remove(user);
+ adapter.notifyDataSetChanged();
+ }
+
+ Util.toast(context, context.getResources().getString(R.string.admin_delete_user_success, user.getUsername()));
+ }
+
+ @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.admin_delete_user_error, user.getUsername());
+ }
+
+ Util.toast(context, msg);
+ }
+ }.execute();
+ }
+ });
+ }
+
+ public static void addNewUser(final Activity context, final SubsonicFragment fragment) {
+ final User user = new User();
+ user.addSetting("adminRole", false);
+ user.addSetting("settingsRole", true);
+ user.addSetting("downloadRole", false);
+ user.addSetting("uploadRole", false);
+ user.addSetting("coverArtRole", false);
+ user.addSetting("commentRole", false);
+ user.addSetting("podcastRole", false);
+ user.addSetting("streamRole", true);
+ user.addSetting("jukeboxRole", false);
+ user.addSetting("shareRole", false);
+
+ View layout = context.getLayoutInflater().inflate(R.layout.create_user, null);
+ final TextView usernameView = (TextView) layout.findViewById(R.id.username);
+ final TextView emailView = (TextView) layout.findViewById(R.id.email);
+ final TextView passwordView = (TextView) layout.findViewById(R.id.password);
+ final ListView listView = (ListView) layout.findViewById(R.id.settings_list);
+ listView.setAdapter(new SettingsAdapter(context, user, true));
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.menu_add_user)
+ .setView(layout)
+ .setPositiveButton(R.string.common_save, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ final String username = usernameView.getText().toString();
+ // Don't allow blank emails
+ if ("".equals(username)) {
+ Util.toast(context, R.string.admin_change_username_invalid);
+ return;
+ }
+
+ final String email = emailView.getText().toString();
+ // Don't allow blank emails
+ if ("".equals(email)) {
+ Util.toast(context, R.string.admin_change_email_invalid);
+ return;
+ }
+
+ final String password = passwordView.getText().toString();
+ if ("".equals(password)) {
+ Util.toast(context, R.string.admin_change_password_invalid);
+ return;
+ }
+
+ user.setUsername(username);
+ user.setEmail(email);
+ user.setPassword(password);
+
+ new SilentBackgroundTask<Void>(context) {
+ @Override
+ protected Void doInBackground() throws Throwable {
+ MusicService musicService = MusicServiceFactory.getMusicService(context);
+ musicService.createUser(user, context, null);
+ return null;
+ }
+
+ @Override
+ protected void done(Void v) {
+ fragment.onRefresh();
+ Util.toast(context, context.getResources().getString(R.string.admin_create_user_success));
+ }
+
+ @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.admin_create_user_error);
+ }
+
+ Util.toast(context, msg);
+ }
+ }.execute();
+ }
+ })
+ .setNegativeButton(R.string.common_cancel, null)
+ .setCancelable(true);
+
+ AlertDialog dialog = builder.create();
+ dialog.show();
+ }
+}
diff --git a/src/github/daneren2005/dsub/util/Util.java b/src/github/daneren2005/dsub/util/Util.java
index b62b3382..931aa590 100644
--- a/src/github/daneren2005/dsub/util/Util.java
+++ b/src/github/daneren2005/dsub/util/Util.java
@@ -226,14 +226,6 @@ public final class Util {
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) {
int instance = getActiveServer(context);
diff --git a/src/github/daneren2005/dsub/view/ChatAdapter.java b/src/github/daneren2005/dsub/view/ChatAdapter.java
index 518f81ef..c7bc13f2 100644
--- a/src/github/daneren2005/dsub/view/ChatAdapter.java
+++ b/src/github/daneren2005/dsub/view/ChatAdapter.java
@@ -10,6 +10,7 @@ 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.UserUtil;
import github.daneren2005.dsub.util.Util;
import java.text.DateFormat;
@@ -47,7 +48,7 @@ public class ChatAdapter extends ArrayAdapter<ChatMessage> {
Date messageTime = new java.util.Date(message.getTime());
String messageText = message.getMessage();
- String me = Util.getUserName(activity, Util.getActiveServer(activity));
+ String me = UserUtil.getCurrentUsername(activity);
if (messageUser.equals(me)) {
layout = R.layout.chat_item_reverse;
diff --git a/src/github/daneren2005/dsub/view/SettingView.java b/src/github/daneren2005/dsub/view/SettingView.java
new file mode 100644
index 00000000..f2669551
--- /dev/null
+++ b/src/github/daneren2005/dsub/view/SettingView.java
@@ -0,0 +1,68 @@
+/*
+ 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 2014 (C) Scott Jackson
+*/
+
+package github.daneren2005.dsub.view;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.CheckedTextView;
+
+import static github.daneren2005.dsub.domain.User.Setting;
+
+public class SettingView extends UpdateView {
+ Setting setting;
+
+ CheckedTextView view;
+
+ public SettingView(Context context) {
+ super(context);
+ this.context = context;
+ LayoutInflater.from(context).inflate(android.R.layout.simple_list_item_multiple_choice, this, true);
+
+ view = (CheckedTextView) findViewById(android.R.id.text1);
+ }
+
+ protected void setObjectImpl(Object obj, Object editable) {
+ this.setting = (Setting) obj;
+
+ String display = setting.getName();
+ // Can't edit non-role parts
+ if(display.indexOf("Role") == -1) {
+ editable = false;
+ }
+ display = display.replace("Role", "");
+ display = Character.toUpperCase(display.charAt(0)) + display.substring(1);
+
+ view.setText(display);
+ if(setting.getValue()) {
+ view.setChecked(setting.getValue());
+ } else {
+ view.setChecked(false);
+ }
+
+ if((Boolean) editable) {
+ view.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ view.toggle();
+ setting.setValue(view.isChecked());
+ }
+ });
+ } else {
+ view.setOnClickListener(null);
+ }
+ }
+}
diff --git a/src/github/daneren2005/dsub/view/SettingsAdapter.java b/src/github/daneren2005/dsub/view/SettingsAdapter.java
new file mode 100644
index 00000000..ce6c12ed
--- /dev/null
+++ b/src/github/daneren2005/dsub/view/SettingsAdapter.java
@@ -0,0 +1,58 @@
+/*
+ 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 2014 (C) Scott Jackson
+*/
+
+package github.daneren2005.dsub.view;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+
+import java.util.List;
+
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.domain.User;
+
+import static github.daneren2005.dsub.domain.User.Setting;
+
+public class SettingsAdapter extends ArrayAdapter<Setting> {
+ private final Context context;
+ private final boolean editable;
+
+ public SettingsAdapter(Context context, User user, boolean editable) {
+ super(context, R.layout.basic_list_item, user.getSettings());
+ this.context = context;
+ this.editable = editable;
+ }
+
+ public SettingsAdapter(Context context, List<Setting> settings, boolean editable) {
+ super(context, R.layout.basic_list_item, settings);
+ this.context = context;
+ this.editable = editable;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ Setting entry = getItem(position);
+ SettingView view;
+ if (convertView != null && convertView instanceof SettingView) {
+ view = (SettingView) convertView;
+ } else {
+ view = new SettingView(context);
+ }
+ view.setObject(entry, editable);
+ return view;
+ }
+}
diff --git a/src/github/daneren2005/dsub/view/UserAdapter.java b/src/github/daneren2005/dsub/view/UserAdapter.java
new file mode 100644
index 00000000..70a1748a
--- /dev/null
+++ b/src/github/daneren2005/dsub/view/UserAdapter.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 2014 (C) Scott Jackson
+*/
+
+package github.daneren2005.dsub.view;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+
+import java.util.List;
+
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.domain.User;
+import github.daneren2005.dsub.util.ImageLoader;
+
+public class UserAdapter extends ArrayAdapter<User> {
+ private final Context activity;
+ private final ImageLoader imageLoader;
+
+ public UserAdapter(Context activity, List<User> users, ImageLoader imageLoader) {
+ super(activity, R.layout.basic_list_item, users);
+ this.activity = activity;
+ this.imageLoader = imageLoader;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ User entry = getItem(position);
+ UserView view;
+ if (convertView != null && convertView instanceof UserView) {
+ view = (UserView) convertView;
+ } else {
+ view = new UserView(activity);
+ }
+ view.setObject(entry, imageLoader);
+ return view;
+ }
+} \ No newline at end of file
diff --git a/src/github/daneren2005/dsub/view/UserView.java b/src/github/daneren2005/dsub/view/UserView.java
new file mode 100644
index 00000000..31e02d99
--- /dev/null
+++ b/src/github/daneren2005/dsub/view/UserView.java
@@ -0,0 +1,54 @@
+/*
+ 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 2014 (C) Scott Jackson
+*/
+
+package github.daneren2005.dsub.view;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import github.daneren2005.dsub.R;
+import github.daneren2005.dsub.domain.User;
+import github.daneren2005.dsub.util.ImageLoader;
+
+public class UserView extends UpdateView {
+ private User user;
+
+ private TextView usernameView;
+ private ImageView avatarView;
+
+ public UserView(Context context) {
+ super(context);
+ this.context = context;
+ LayoutInflater.from(context).inflate(R.layout.user_list_item, this, true);
+
+ usernameView = (TextView) findViewById(R.id.item_name);
+ avatarView = (ImageView) findViewById(R.id.item_avatar);
+ moreButton = (ImageView) findViewById(R.id.item_more);
+ moreButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ v.showContextMenu();
+ }
+ });
+ }
+
+ protected void setObjectImpl(Object obj, Object obj2) {
+ this.user = (User) obj;
+ usernameView.setText(user.getUsername());
+ ((ImageLoader)obj2).loadAvatar(context, avatarView, user.getUsername());
+ }
+}