Introduce the SelectMusicFolderView

Signed-off-by: James Wells <james@jameswells.net>
This commit is contained in:
James Wells 2021-04-01 23:00:01 -04:00
parent 97d68eb08f
commit 191f9512bb
No known key found for this signature in database
GPG Key ID: 7A9AB99C0B899FB7
9 changed files with 160 additions and 82 deletions

View File

@ -284,9 +284,9 @@ public class CachedMusicService implements MusicService
} }
@Override @Override
public MusicDirectory getAlbumList(String type, int size, int offset, Context context) throws Exception public MusicDirectory getAlbumList(String type, int size, int offset, String musicFolderId, Context context) throws Exception
{ {
return musicService.getAlbumList(type, size, offset, context); return musicService.getAlbumList(type, size, offset, musicFolderId, context);
} }
@Override @Override

View File

@ -90,7 +90,7 @@ public interface MusicService
void scrobble(String id, boolean submission, Context context) throws Exception; void scrobble(String id, boolean submission, Context context) throws Exception;
MusicDirectory getAlbumList(String type, int size, int offset, Context context) throws Exception; MusicDirectory getAlbumList(String type, int size, int offset, String musicFolderId, Context context) throws Exception;
MusicDirectory getAlbumList2(String type, int size, int offset, Context context) throws Exception; MusicDirectory getAlbumList2(String type, int size, int offset, Context context) throws Exception;

View File

@ -702,7 +702,7 @@ public class OfflineMusicService implements MusicService
} }
@Override @Override
public MusicDirectory getAlbumList(String type, int size, int offset, Context context) throws Exception public MusicDirectory getAlbumList(String type, int size, int offset, String musicFolderId, Context context) throws Exception
{ {
throw new OfflineException("Album lists not available in offline mode"); throw new OfflineException("Album lists not available in offline mode");
} }

View File

@ -24,7 +24,6 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.PopupMenu import android.widget.PopupMenu
import android.widget.RelativeLayout import android.widget.RelativeLayout
import android.widget.TextView import android.widget.TextView
@ -37,6 +36,7 @@ import org.moire.ultrasonic.domain.Artist
import org.moire.ultrasonic.domain.MusicDirectory import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.util.ImageLoader import org.moire.ultrasonic.util.ImageLoader
import org.moire.ultrasonic.util.Util import org.moire.ultrasonic.util.Util
import org.moire.ultrasonic.view.SelectMusicFolderView
/** /**
* Creates a Row in a RecyclerView which contains the details of an Artist * Creates a Row in a RecyclerView which contains the details of an Artist
@ -44,10 +44,9 @@ import org.moire.ultrasonic.util.Util
class ArtistRowAdapter( class ArtistRowAdapter(
private var artistList: List<Artist>, private var artistList: List<Artist>,
private var folderName: String, private var folderName: String,
private var shouldShowHeader: Boolean, private var selectFolderHeader: SelectMusicFolderView?,
val onArtistClick: (Artist) -> Unit, val onArtistClick: (Artist) -> Unit,
val onContextMenuClick: (MenuItem, Artist) -> Boolean, val onContextMenuClick: (MenuItem, Artist) -> Boolean,
val onFolderClick: (view: View) -> Unit,
private val imageLoader: ImageLoader private val imageLoader: ImageLoader
) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), SectionedAdapter { ) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), SectionedAdapter {
@ -80,16 +79,6 @@ class ArtistRowAdapter(
var coverArtId: String? = null var coverArtId: String? = null
} }
/**
* Holds the view properties of the Header row
*/
class HeaderViewHolder(
itemView: View
) : RecyclerView.ViewHolder(itemView) {
var folderName: TextView = itemView.findViewById(R.id.select_artist_folder_2)
var layout: LinearLayout = itemView.findViewById(R.id.select_artist_folder)
}
override fun onCreateViewHolder( override fun onCreateViewHolder(
parent: ViewGroup, parent: ViewGroup,
viewType: Int viewType: Int
@ -99,9 +88,7 @@ class ArtistRowAdapter(
.inflate(R.layout.artist_list_item, parent, false) .inflate(R.layout.artist_list_item, parent, false)
return ArtistViewHolder(row) return ArtistViewHolder(row)
} }
val header = LayoutInflater.from(parent.context) return selectFolderHeader!!
.inflate(R.layout.select_artist_header, parent, false)
return HeaderViewHolder(header)
} }
override fun onViewRecycled(holder: RecyclerView.ViewHolder) { override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
@ -113,7 +100,7 @@ class ArtistRowAdapter(
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is ArtistViewHolder) { if (holder is ArtistViewHolder) {
val listPosition = if (shouldShowHeader) position - 1 else position val listPosition = if (selectFolderHeader != null) position - 1 else position
holder.textView.text = artistList[listPosition].name holder.textView.text = artistList[listPosition].name
holder.section.text = getSectionForArtist(listPosition) holder.section.text = getSectionForArtist(listPosition)
holder.layout.setOnClickListener { onArtistClick(artistList[listPosition]) } holder.layout.setOnClickListener { onArtistClick(artistList[listPosition]) }
@ -130,20 +117,20 @@ class ArtistRowAdapter(
} else { } else {
holder.coverArt.visibility = View.GONE holder.coverArt.visibility = View.GONE
} }
} else if (holder is HeaderViewHolder) {
holder.folderName.text = folderName
holder.layout.setOnClickListener { onFolderClick(holder.layout) }
} }
} }
override fun getItemCount() = if (shouldShowHeader) artistList.size + 1 else artistList.size override fun getItemCount() = if (selectFolderHeader != null)
artistList.size + 1
else
artistList.size
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
return if (position == 0 && shouldShowHeader) TYPE_HEADER else TYPE_ITEM return if (position == 0 && selectFolderHeader != null) TYPE_HEADER else TYPE_ITEM
} }
override fun getSectionName(position: Int): String { override fun getSectionName(position: Int): String {
var listPosition = if (shouldShowHeader) position - 1 else position var listPosition = if (selectFolderHeader != null) position - 1 else position
// Show the first artist's initial in the popup when the list is // Show the first artist's initial in the popup when the list is
// scrolled up to the "Select Folder" row // scrolled up to the "Select Folder" row

View File

@ -24,13 +24,16 @@ import java.util.Collections
import java.util.LinkedList import java.util.LinkedList
import java.util.Random import java.util.Random
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.koin.android.viewmodel.ext.android.viewModel
import org.moire.ultrasonic.R import org.moire.ultrasonic.R
import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
import org.moire.ultrasonic.domain.MusicDirectory import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.fragment.FragmentTitle.Companion.getTitle import org.moire.ultrasonic.fragment.FragmentTitle.Companion.getTitle
import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle
import org.moire.ultrasonic.service.MediaPlayerController import org.moire.ultrasonic.service.MediaPlayerController
import org.moire.ultrasonic.service.MusicService import org.moire.ultrasonic.service.MusicService
import org.moire.ultrasonic.service.MusicServiceFactory
import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService
import org.moire.ultrasonic.subsonic.DownloadHandler import org.moire.ultrasonic.subsonic.DownloadHandler
import org.moire.ultrasonic.subsonic.ImageLoaderProvider import org.moire.ultrasonic.subsonic.ImageLoaderProvider
@ -45,6 +48,7 @@ import org.moire.ultrasonic.util.FragmentBackgroundTask
import org.moire.ultrasonic.util.Util import org.moire.ultrasonic.util.Util
import org.moire.ultrasonic.view.AlbumView import org.moire.ultrasonic.view.AlbumView
import org.moire.ultrasonic.view.EntryAdapter import org.moire.ultrasonic.view.EntryAdapter
import org.moire.ultrasonic.view.SelectMusicFolderView
import org.moire.ultrasonic.view.SongView import org.moire.ultrasonic.view.SongView
import timber.log.Timber import timber.log.Timber
@ -57,6 +61,7 @@ class SelectAlbumFragment : Fragment() {
private var refreshAlbumListView: SwipeRefreshLayout? = null private var refreshAlbumListView: SwipeRefreshLayout? = null
private var albumListView: ListView? = null private var albumListView: ListView? = null
private var header: View? = null private var header: View? = null
private var selectFolderHeader: SelectMusicFolderView? = null
private var albumButtons: View? = null private var albumButtons: View? = null
private var emptyView: View? = null private var emptyView: View? = null
private var selectButton: ImageView? = null private var selectButton: ImageView? = null
@ -73,6 +78,7 @@ class SelectAlbumFragment : Fragment() {
private var playAllButton: MenuItem? = null private var playAllButton: MenuItem? = null
private var shareButton: MenuItem? = null private var shareButton: MenuItem? = null
private var showHeader = true private var showHeader = true
private var showSelectFolderHeader = false
private val random: Random = SecureRandom() private val random: Random = SecureRandom()
private val mediaPlayerController: MediaPlayerController by inject() private val mediaPlayerController: MediaPlayerController by inject()
@ -82,6 +88,9 @@ class SelectAlbumFragment : Fragment() {
private val imageLoaderProvider: ImageLoaderProvider by inject() private val imageLoaderProvider: ImageLoaderProvider by inject()
private val shareHandler: ShareHandler by inject() private val shareHandler: ShareHandler by inject()
private var cancellationToken: CancellationToken? = null private var cancellationToken: CancellationToken? = null
private val activeServerProvider: ActiveServerProvider by inject()
private val serverSettingsModel: ServerSettingsModel by viewModel()
private val artistListModel: ArtistListModel by viewModel()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
Util.applyTheme(this.context) Util.applyTheme(this.context)
@ -117,6 +126,24 @@ class SelectAlbumFragment : Fragment() {
false false
) )
selectFolderHeader = SelectMusicFolderView(
requireContext(), albumListView!!,
MusicServiceFactory.getMusicService(requireContext()).getMusicFolders(
false, requireContext()
),
activeServerProvider.getActiveServer().musicFolderId,
{ _, selectedFolderId ->
if (!ActiveServerProvider.isOffline(context)) {
val currentSetting = activeServerProvider.getActiveServer()
currentSetting.musicFolderId = selectedFolderId
serverSettingsModel.updateItem(currentSetting)
}
artistListModel.refresh(refreshAlbumListView!!)
this.updateDisplay(true)
}
)
albumListView!!.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE) albumListView!!.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE)
albumListView!!.setOnItemClickListener( albumListView!!.setOnItemClickListener(
OnItemClickListener OnItemClickListener
@ -735,6 +762,7 @@ class SelectAlbumFragment : Fragment() {
private fun getAlbumList(albumListType: String, albumListTitle: Int, size: Int, offset: Int) { private fun getAlbumList(albumListType: String, albumListTitle: Int, size: Int, offset: Int) {
showHeader = false showHeader = false
showSelectFolderHeader = !isOffline(context)
setTitle(this, albumListTitle) setTitle(this, albumListTitle)
// setActionBarSubtitle(albumListTitle); // setActionBarSubtitle(albumListTitle);
@ -747,10 +775,12 @@ class SelectAlbumFragment : Fragment() {
} }
override fun load(service: MusicService): MusicDirectory { override fun load(service: MusicService): MusicDirectory {
val musicFolderId =
this@SelectAlbumFragment.activeServerProvider.getActiveServer().musicFolderId
return if (Util.getShouldUseId3Tags(context)) return if (Util.getShouldUseId3Tags(context))
service.getAlbumList2(albumListType, size, offset, context) service.getAlbumList2(albumListType, size, offset, context)
else else
service.getAlbumList(albumListType, size, offset, context) service.getAlbumList(albumListType, size, offset, musicFolderId, context)
} }
override fun done(result: Pair<MusicDirectory, Boolean>) { override fun done(result: Pair<MusicDirectory, Boolean>) {
@ -1012,6 +1042,12 @@ class SelectAlbumFragment : Fragment() {
} }
} }
} else { } else {
if (showSelectFolderHeader) {
if (albumListView!!.headerViewsCount == 0) {
albumListView!!.addHeaderView(selectFolderHeader!!.itemView, null, false)
}
}
pinButton!!.visibility = View.GONE pinButton!!.visibility = View.GONE
unpinButton!!.visibility = View.GONE unpinButton!!.visibility = View.GONE
downloadButton!!.visibility = View.GONE downloadButton!!.visibility = View.GONE

View File

@ -5,7 +5,6 @@ import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.PopupMenu
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
@ -19,10 +18,12 @@ import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.domain.Artist import org.moire.ultrasonic.domain.Artist
import org.moire.ultrasonic.domain.MusicFolder import org.moire.ultrasonic.domain.MusicFolder
import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle
import org.moire.ultrasonic.service.MusicServiceFactory
import org.moire.ultrasonic.subsonic.DownloadHandler import org.moire.ultrasonic.subsonic.DownloadHandler
import org.moire.ultrasonic.subsonic.ImageLoaderProvider import org.moire.ultrasonic.subsonic.ImageLoaderProvider
import org.moire.ultrasonic.util.Constants import org.moire.ultrasonic.util.Constants
import org.moire.ultrasonic.util.Util import org.moire.ultrasonic.util.Util
import org.moire.ultrasonic.view.SelectMusicFolderView
/** /**
* Displays the list of Artists from the media library * Displays the list of Artists from the media library
@ -39,6 +40,7 @@ class SelectArtistFragment : Fragment() {
private var musicFolders: List<MusicFolder>? = null private var musicFolders: List<MusicFolder>? = null
private lateinit var viewManager: RecyclerView.LayoutManager private lateinit var viewManager: RecyclerView.LayoutManager
private lateinit var viewAdapter: ArtistRowAdapter private lateinit var viewAdapter: ArtistRowAdapter
private var selectFolderHeader: SelectMusicFolderView? = null
@Override @Override
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -60,10 +62,26 @@ class SelectArtistFragment : Fragment() {
artistListModel.refresh(refreshArtistListView!!) artistListModel.refresh(refreshArtistListView!!)
} }
val shouldShowHeader = ( if (!ActiveServerProvider.isOffline(this.context) &&
!ActiveServerProvider.isOffline(this.context) && !Util.getShouldUseId3Tags(this.context)
!Util.getShouldUseId3Tags(this.context) ) {
selectFolderHeader = SelectMusicFolderView(
requireContext(), view as ViewGroup,
MusicServiceFactory.getMusicService(requireContext()).getMusicFolders(
false, requireContext()
),
activeServerProvider.getActiveServer().musicFolderId,
{ musicFolderName, selectedFolderId ->
if (!ActiveServerProvider.isOffline(context)) {
val currentSetting = activeServerProvider.getActiveServer()
currentSetting.musicFolderId = selectedFolderId
serverSettingsModel.updateItem(currentSetting)
}
viewAdapter.setFolderName(musicFolderName)
artistListModel.refresh(refreshArtistListView!!)
}
) )
}
val title = arguments?.getString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE) val title = arguments?.getString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE)
@ -102,10 +120,9 @@ class SelectArtistFragment : Fragment() {
viewAdapter = ArtistRowAdapter( viewAdapter = ArtistRowAdapter(
artists.value ?: listOf(), artists.value ?: listOf(),
getText(R.string.select_artist_all_folders).toString(), getText(R.string.select_artist_all_folders).toString(),
shouldShowHeader, selectFolderHeader,
{ artist -> onItemClick(artist) }, { artist -> onItemClick(artist) },
{ menuItem, artist -> onArtistMenuItemSelected(menuItem, artist) }, { menuItem, artist -> onArtistMenuItemSelected(menuItem, artist) },
{ onFolderClick(it) },
imageLoaderProvider.getImageLoader() imageLoaderProvider.getImageLoader()
) )
@ -138,31 +155,6 @@ class SelectArtistFragment : Fragment() {
findNavController().navigate(R.id.selectArtistToSelectAlbum, bundle) findNavController().navigate(R.id.selectArtistToSelectAlbum, bundle)
} }
private fun onFolderClick(view: View) {
val popup = PopupMenu(this.context, view)
val musicFolderId = activeServerProvider.getActiveServer().musicFolderId
var menuItem = popup.menu.add(
MENU_GROUP_MUSIC_FOLDER, -1, 0, R.string.select_artist_all_folders
)
if (musicFolderId == null || musicFolderId.isEmpty()) {
menuItem.isChecked = true
}
if (musicFolders != null) {
for (i in musicFolders!!.indices) {
val (id, name) = musicFolders!![i]
menuItem = popup.menu.add(MENU_GROUP_MUSIC_FOLDER, i, i + 1, name)
if (id == musicFolderId) {
menuItem.isChecked = true
}
}
}
popup.menu.setGroupCheckable(MENU_GROUP_MUSIC_FOLDER, true, true)
popup.setOnMenuItemClickListener { item -> onFolderMenuItemSelected(item) }
popup.show()
}
private fun onArtistMenuItemSelected(menuItem: MenuItem, artist: Artist): Boolean { private fun onArtistMenuItemSelected(menuItem: MenuItem, artist: Artist): Boolean {
when (menuItem.itemId) { when (menuItem.itemId) {
R.id.artist_menu_play_now -> R.id.artist_menu_play_now ->
@ -246,23 +238,4 @@ class SelectArtistFragment : Fragment() {
} }
return true return true
} }
private fun onFolderMenuItemSelected(menuItem: MenuItem): Boolean {
val selectedFolder = if (menuItem.itemId == -1) null else musicFolders!![menuItem.itemId]
val musicFolderId = selectedFolder?.id
val musicFolderName = selectedFolder?.name
?: getString(R.string.select_artist_all_folders)
if (!ActiveServerProvider.isOffline(this.context)) {
val currentSetting = activeServerProvider.getActiveServer()
currentSetting.musicFolderId = musicFolderId
serverSettingsModel.updateItem(currentSetting)
}
viewAdapter.setFolderName(musicFolderName)
artistListModel.refresh(refreshArtistListView!!)
return true
}
companion object {
private const val MENU_GROUP_MUSIC_FOLDER = 10
}
} }

View File

@ -445,10 +445,11 @@ open class RESTMusicService(
type: String, type: String,
size: Int, size: Int,
offset: Int, offset: Int,
musicFolderId: String?,
context: Context context: Context
): MusicDirectory { ): MusicDirectory {
val response = responseChecker.callWithResponseCheck { api -> val response = responseChecker.callWithResponseCheck { api ->
api.getAlbumList(fromName(type), size, offset, null, null, null, null) api.getAlbumList(fromName(type), size, offset, null, null, null, musicFolderId)
.execute() .execute()
} }

View File

@ -0,0 +1,81 @@
package org.moire.ultrasonic.view
import android.content.Context
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.PopupMenu
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import org.moire.ultrasonic.R
import org.moire.ultrasonic.domain.MusicFolder
/**
* This little view shows the currently selected Folder (or catalog) on the music server.
* When clicked it will drop down a list of all available Folders and allow you to
* select one. The intended usage us to supply a filter to lists of artists, albums, etc
*/
class SelectMusicFolderView(
private val context: Context,
root: ViewGroup,
private val musicFolders: List<MusicFolder>,
private var selectedFolderId: String?,
private val onUpdate: (String, String?) -> Unit
) : RecyclerView.ViewHolder(
LayoutInflater.from(context).inflate(
R.layout.select_folder_header, root, false
)
) {
private val folderName: TextView = itemView.findViewById(R.id.select_folder_2)
private val layout: LinearLayout = itemView.findViewById(R.id.select_folder_header)
init {
if (selectedFolderId != null) {
for ((id, name) in musicFolders) {
if (id == selectedFolderId) {
folderName.text = name
break
}
}
}
layout.setOnClickListener { onFolderClick() }
}
private fun onFolderClick() {
val popup = PopupMenu(context, layout)
val MENU_GROUP_MUSIC_FOLDER = 10
var menuItem = popup.menu.add(
MENU_GROUP_MUSIC_FOLDER, -1, 0, R.string.select_artist_all_folders
)
if (selectedFolderId == null || selectedFolderId!!.isEmpty()) {
menuItem.isChecked = true
}
for (i in musicFolders.indices) {
val (id, name) = musicFolders[i]
menuItem = popup.menu.add(MENU_GROUP_MUSIC_FOLDER, i, i + 1, name)
if (id == selectedFolderId) {
menuItem.isChecked = true
}
}
popup.menu.setGroupCheckable(MENU_GROUP_MUSIC_FOLDER, true, true)
popup.setOnMenuItemClickListener { item -> onFolderMenuItemSelected(item) }
popup.show()
}
private fun onFolderMenuItemSelected(menuItem: MenuItem): Boolean {
val selectedFolder = if (menuItem.itemId == -1) null else musicFolders[menuItem.itemId]
val musicFolderName = selectedFolder?.name
?: context.getString(R.string.select_artist_all_folders)
selectedFolderId = selectedFolder?.id
menuItem.isChecked = true
folderName.text = musicFolderName
onUpdate(musicFolderName, selectedFolderId)
return true
}
}

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:a="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:a="http://schemas.android.com/apk/res/android"
a:id="@+id/select_artist_folder" a:id="@+id/select_folder_header"
a:layout_width="fill_parent" a:layout_width="fill_parent"
a:layout_height="wrap_content" a:layout_height="wrap_content"
a:minHeight="?android:attr/listPreferredItemHeight" a:minHeight="?android:attr/listPreferredItemHeight"
@ -24,7 +24,7 @@
a:orientation="vertical" > a:orientation="vertical" >
<TextView <TextView
a:id="@+id/select_artist_folder_1" a:id="@+id/select_folder_1"
a:layout_width="fill_parent" a:layout_width="fill_parent"
a:layout_height="wrap_content" a:layout_height="wrap_content"
a:layout_marginLeft="10dip" a:layout_marginLeft="10dip"
@ -33,7 +33,7 @@
a:textAppearance="?android:attr/textAppearanceLarge" /> a:textAppearance="?android:attr/textAppearanceLarge" />
<TextView <TextView
a:id="@+id/select_artist_folder_2" a:id="@+id/select_folder_2"
a:layout_width="fill_parent" a:layout_width="fill_parent"
a:layout_height="wrap_content" a:layout_height="wrap_content"
a:layout_marginLeft="10dip" a:layout_marginLeft="10dip"