parent
34a6413f10
commit
47e5675d1e
|
@ -31,6 +31,8 @@ public interface ImageLoader {
|
||||||
boolean highQuality
|
boolean highQuality
|
||||||
);
|
);
|
||||||
|
|
||||||
|
void cancel(String coverArt);
|
||||||
|
|
||||||
Bitmap getImageBitmap(String username, int size);
|
Bitmap getImageBitmap(String username, int size);
|
||||||
|
|
||||||
Bitmap getImageBitmap(MusicDirectory.Entry entry, boolean large, int size);
|
Bitmap getImageBitmap(MusicDirectory.Entry entry, boolean large, int size);
|
||||||
|
|
|
@ -38,6 +38,8 @@ import org.moire.ultrasonic.service.MusicServiceFactory;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.SortedSet;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
@ -203,6 +205,16 @@ public class LegacyImageLoader implements Runnable, ImageLoader {
|
||||||
queue.offer(new Task(view, entry, size, large, crossFade, highQuality));
|
queue.offer(new Task(view, entry, size, large, crossFade, highQuality));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void cancel(String coverArt) {
|
||||||
|
for (Object taskObject : queue.toArray()) {
|
||||||
|
Task task = (Task)taskObject;
|
||||||
|
if ((task.entry.getCoverArt() != null) && (coverArt.compareTo(task.entry.getCoverArt()) == 0)) {
|
||||||
|
queue.remove(taskObject);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static String getKey(String coverArtId, int size) {
|
private static String getKey(String coverArtId, int size) {
|
||||||
return String.format("%s:%d", coverArtId, size);
|
return String.format("%s:%d", coverArtId, size);
|
||||||
}
|
}
|
||||||
|
@ -381,14 +393,7 @@ public class LegacyImageLoader implements Runnable, ImageLoader {
|
||||||
private final boolean crossFade;
|
private final boolean crossFade;
|
||||||
private final boolean highQuality;
|
private final boolean highQuality;
|
||||||
|
|
||||||
Task(
|
Task(View view, MusicDirectory.Entry entry, int size, boolean saveToFile, boolean crossFade, boolean highQuality) {
|
||||||
View view,
|
|
||||||
MusicDirectory.Entry entry,
|
|
||||||
int size,
|
|
||||||
boolean saveToFile,
|
|
||||||
boolean crossFade,
|
|
||||||
boolean highQuality
|
|
||||||
) {
|
|
||||||
this.view = view;
|
this.view = view;
|
||||||
this.entry = entry;
|
this.entry = entry;
|
||||||
this.username = null;
|
this.username = null;
|
||||||
|
@ -399,14 +404,7 @@ public class LegacyImageLoader implements Runnable, ImageLoader {
|
||||||
handler = new Handler();
|
handler = new Handler();
|
||||||
}
|
}
|
||||||
|
|
||||||
Task(
|
Task(View view, String username, int size, boolean saveToFile, boolean crossFade, boolean highQuality) {
|
||||||
View view,
|
|
||||||
String username,
|
|
||||||
int size,
|
|
||||||
boolean saveToFile,
|
|
||||||
boolean crossFade,
|
|
||||||
boolean highQuality
|
|
||||||
) {
|
|
||||||
this.view = view;
|
this.view = view;
|
||||||
this.entry = null;
|
this.entry = null;
|
||||||
this.username = username;
|
this.username = username;
|
||||||
|
@ -421,9 +419,9 @@ public class LegacyImageLoader implements Runnable, ImageLoader {
|
||||||
try {
|
try {
|
||||||
MusicService musicService = MusicServiceFactory.getMusicService(view.getContext());
|
MusicService musicService = MusicServiceFactory.getMusicService(view.getContext());
|
||||||
final boolean isAvatar = this.username != null && this.entry == null;
|
final boolean isAvatar = this.username != null && this.entry == null;
|
||||||
final Bitmap bitmap = this.entry != null
|
final Bitmap bitmap = this.entry != null ?
|
||||||
? musicService.getCoverArt(view.getContext(), entry, size, saveToFile, highQuality, null)
|
musicService.getCoverArt(view.getContext(), entry, size, saveToFile, highQuality, null) :
|
||||||
: musicService.getAvatar(view.getContext(), username, size, saveToFile, highQuality, null);
|
musicService.getAvatar(view.getContext(), username, size, saveToFile, highQuality, null);
|
||||||
|
|
||||||
if (bitmap == null) {
|
if (bitmap == null) {
|
||||||
Timber.d("Found empty album art.");
|
Timber.d("Found empty album art.");
|
||||||
|
|
|
@ -36,6 +36,9 @@ import org.moire.ultrasonic.service.CommunicationErrorHandler
|
||||||
import org.moire.ultrasonic.service.MusicServiceFactory
|
import org.moire.ultrasonic.service.MusicServiceFactory
|
||||||
import org.moire.ultrasonic.util.Util
|
import org.moire.ultrasonic.util.Util
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides ViewModel which contains the list of available Artists
|
||||||
|
*/
|
||||||
class ArtistListModel(
|
class ArtistListModel(
|
||||||
private val activeServerProvider: ActiveServerProvider,
|
private val activeServerProvider: ActiveServerProvider,
|
||||||
private val context: Context
|
private val context: Context
|
||||||
|
@ -43,23 +46,31 @@ class ArtistListModel(
|
||||||
private val musicFolders: MutableLiveData<List<MusicFolder>> = MutableLiveData()
|
private val musicFolders: MutableLiveData<List<MusicFolder>> = MutableLiveData()
|
||||||
private val artists: MutableLiveData<List<Artist>> = MutableLiveData()
|
private val artists: MutableLiveData<List<Artist>> = MutableLiveData()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the available Artists in a LiveData
|
||||||
|
*/
|
||||||
fun getArtists(refresh: Boolean, swipe: SwipeRefreshLayout): LiveData<List<Artist>> {
|
fun getArtists(refresh: Boolean, swipe: SwipeRefreshLayout): LiveData<List<Artist>> {
|
||||||
backgroundLoadFromServer(refresh, swipe)
|
backgroundLoadFromServer(refresh, swipe)
|
||||||
return artists
|
return artists
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMusicFolders(refresh: Boolean, swipe: SwipeRefreshLayout): LiveData<List<MusicFolder>> {
|
/**
|
||||||
backgroundLoadFromServer(refresh, swipe)
|
* Retrieves the available Music Folders in a LiveData
|
||||||
|
*/
|
||||||
|
fun getMusicFolders(): LiveData<List<MusicFolder>> {
|
||||||
return musicFolders
|
return musicFolders
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes the cached Artists from the server
|
||||||
|
*/
|
||||||
fun refresh(swipe: SwipeRefreshLayout) {
|
fun refresh(swipe: SwipeRefreshLayout) {
|
||||||
backgroundLoadFromServer(true, swipe)
|
backgroundLoadFromServer(true, swipe)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun backgroundLoadFromServer(refresh: Boolean, swipe: SwipeRefreshLayout) {
|
private fun backgroundLoadFromServer(refresh: Boolean, swipe: SwipeRefreshLayout) {
|
||||||
swipe.isRefreshing = true
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
swipe.isRefreshing = true
|
||||||
loadFromServer(refresh, swipe)
|
loadFromServer(refresh, swipe)
|
||||||
swipe.isRefreshing = false
|
swipe.isRefreshing = false
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import android.view.MenuInflater
|
||||||
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.ImageView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.PopupMenu
|
import android.widget.PopupMenu
|
||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
|
@ -31,34 +32,54 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.moire.ultrasonic.R
|
import org.moire.ultrasonic.R
|
||||||
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
|
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
|
||||||
import org.moire.ultrasonic.domain.Artist
|
import org.moire.ultrasonic.domain.Artist
|
||||||
|
import org.moire.ultrasonic.domain.MusicDirectory
|
||||||
|
import org.moire.ultrasonic.util.ImageLoader
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Row in a RecyclerView which contains the details of an Artist
|
||||||
|
*/
|
||||||
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 shouldShowHeader: Boolean,
|
||||||
val onArtistClick: (Artist) -> Unit,
|
val onArtistClick: (Artist) -> Unit,
|
||||||
val onContextMenuClick: (MenuItem, Artist) -> Boolean,
|
val onContextMenuClick: (MenuItem, Artist) -> Boolean,
|
||||||
val onFolderClick: (view: View) -> Unit
|
val onFolderClick: (view: View) -> Unit,
|
||||||
|
val imageLoader: ImageLoader
|
||||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the data to be displayed in the RecyclerView
|
||||||
|
*/
|
||||||
fun setData(data: List<Artist>) {
|
fun setData(data: List<Artist>) {
|
||||||
artistList = data.sortedBy { t -> t.name }
|
artistList = data.sortedBy { t -> t.name }
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the name of the folder to be displayed n the Header (first) row
|
||||||
|
*/
|
||||||
fun setFolderName(name: String) {
|
fun setFolderName(name: String) {
|
||||||
folderName = name
|
folderName = name
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds the view properties of an Artist row
|
||||||
|
*/
|
||||||
class ArtistViewHolder(
|
class ArtistViewHolder(
|
||||||
itemView: View
|
itemView: View
|
||||||
) : RecyclerView.ViewHolder(itemView) {
|
) : RecyclerView.ViewHolder(itemView) {
|
||||||
var section: TextView = itemView.findViewById(R.id.row_section)
|
var section: TextView = itemView.findViewById(R.id.row_section)
|
||||||
var textView: TextView = itemView.findViewById(R.id.row_artist_name)
|
var textView: TextView = itemView.findViewById(R.id.row_artist_name)
|
||||||
var layout: RelativeLayout = itemView.findViewById(R.id.row_artist_layout)
|
var layout: RelativeLayout = itemView.findViewById(R.id.row_artist_layout)
|
||||||
|
var coverArt: ImageView = itemView.findViewById(R.id.artist_coverart)
|
||||||
|
var coverArtId: String? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds the view properties of the Header row
|
||||||
|
*/
|
||||||
class HeaderViewHolder(
|
class HeaderViewHolder(
|
||||||
itemView: View
|
itemView: View
|
||||||
) : RecyclerView.ViewHolder(itemView) {
|
) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
@ -80,6 +101,13 @@ class ArtistRowAdapter(
|
||||||
return HeaderViewHolder(header)
|
return HeaderViewHolder(header)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
|
||||||
|
if ((holder is ArtistViewHolder) && (holder.coverArtId != null)) {
|
||||||
|
imageLoader.cancel(holder.coverArtId)
|
||||||
|
}
|
||||||
|
super.onViewRecycled(holder)
|
||||||
|
}
|
||||||
|
|
||||||
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 (shouldShowHeader) position - 1 else position
|
||||||
|
@ -87,6 +115,12 @@ class ArtistRowAdapter(
|
||||||
holder.section.text = getSectionForArtist(listPosition)
|
holder.section.text = getSectionForArtist(listPosition)
|
||||||
holder.layout.setOnClickListener { onArtistClick(artistList[listPosition]) }
|
holder.layout.setOnClickListener { onArtistClick(artistList[listPosition]) }
|
||||||
holder.layout.setOnLongClickListener { view -> createPopupMenu(view, listPosition) }
|
holder.layout.setOnLongClickListener { view -> createPopupMenu(view, listPosition) }
|
||||||
|
holder.coverArtId = artistList[listPosition].coverArt
|
||||||
|
imageLoader.loadImage(
|
||||||
|
holder.coverArt,
|
||||||
|
MusicDirectory.Entry().apply { coverArt = holder.coverArtId },
|
||||||
|
false, 0, false, true
|
||||||
|
)
|
||||||
} else if (holder is HeaderViewHolder) {
|
} else if (holder is HeaderViewHolder) {
|
||||||
holder.folderName.text = folderName
|
holder.folderName.text = folderName
|
||||||
holder.layout.setOnClickListener { onFolderClick(holder.layout) }
|
holder.layout.setOnClickListener { onFolderClick(holder.layout) }
|
||||||
|
|
|
@ -37,6 +37,9 @@ import org.moire.ultrasonic.domain.MusicFolder
|
||||||
import org.moire.ultrasonic.util.Constants
|
import org.moire.ultrasonic.util.Constants
|
||||||
import org.moire.ultrasonic.util.Util
|
import org.moire.ultrasonic.util.Util
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the available Artists in a list
|
||||||
|
*/
|
||||||
class SelectArtistActivity : SubsonicTabActivity() {
|
class SelectArtistActivity : SubsonicTabActivity() {
|
||||||
private val activeServerProvider: ActiveServerProvider by inject()
|
private val activeServerProvider: ActiveServerProvider by inject()
|
||||||
private val serverSettingsModel: ServerSettingsModel by viewModel()
|
private val serverSettingsModel: ServerSettingsModel by viewModel()
|
||||||
|
@ -77,7 +80,7 @@ class SelectArtistActivity : SubsonicTabActivity() {
|
||||||
|
|
||||||
val refresh = intent.getBooleanExtra(Constants.INTENT_EXTRA_NAME_REFRESH, false)
|
val refresh = intent.getBooleanExtra(Constants.INTENT_EXTRA_NAME_REFRESH, false)
|
||||||
|
|
||||||
artistListModel.getMusicFolders(refresh, refreshArtistListView!!)
|
artistListModel.getMusicFolders()
|
||||||
.observe(
|
.observe(
|
||||||
this,
|
this,
|
||||||
Observer { changedFolders ->
|
Observer { changedFolders ->
|
||||||
|
@ -100,7 +103,8 @@ class SelectArtistActivity : SubsonicTabActivity() {
|
||||||
shouldShowHeader,
|
shouldShowHeader,
|
||||||
{ artist -> onItemClick(artist) },
|
{ artist -> onItemClick(artist) },
|
||||||
{ menuItem, artist -> onArtistMenuItemSelected(menuItem, artist) },
|
{ menuItem, artist -> onArtistMenuItemSelected(menuItem, artist) },
|
||||||
{ view -> onFolderClick(view) }
|
{ view -> onFolderClick(view) },
|
||||||
|
imageLoader
|
||||||
)
|
)
|
||||||
|
|
||||||
artistListView = findViewById<RecyclerView>(R.id.select_artist_list).apply {
|
artistListView = findViewById<RecyclerView>(R.id.select_artist_list).apply {
|
||||||
|
|
|
@ -4,6 +4,7 @@ package org.moire.ultrasonic.di
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
import org.koin.android.ext.koin.androidContext
|
import org.koin.android.ext.koin.androidContext
|
||||||
|
import org.koin.android.viewmodel.dsl.viewModel
|
||||||
import org.koin.core.qualifier.named
|
import org.koin.core.qualifier.named
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
import org.moire.ultrasonic.BuildConfig
|
import org.moire.ultrasonic.BuildConfig
|
||||||
|
@ -72,5 +73,6 @@ val musicServiceModule = module {
|
||||||
}
|
}
|
||||||
|
|
||||||
single { SubsonicImageLoader(androidContext(), get()) }
|
single { SubsonicImageLoader(androidContext(), get()) }
|
||||||
single { ArtistListModel(get(), get()) }
|
|
||||||
|
viewModel { ArtistListModel(get(), androidContext()) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import org.moire.ultrasonic.api.subsonic.models.Artist as APIArtist
|
||||||
|
|
||||||
fun APIArtist.toDomainEntity(): Artist = Artist(
|
fun APIArtist.toDomainEntity(): Artist = Artist(
|
||||||
id = this@toDomainEntity.id,
|
id = this@toDomainEntity.id,
|
||||||
|
coverArt = this@toDomainEntity.coverArt,
|
||||||
name = this@toDomainEntity.name
|
name = this@toDomainEntity.name
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,10 @@ import org.moire.ultrasonic.subsonic.getLocalizedErrorMessage
|
||||||
import org.moire.ultrasonic.util.Util
|
import org.moire.ultrasonic.util.Util
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains helper functions to handle the exceptions
|
||||||
|
* thrown during the communication with a Subsonic server
|
||||||
|
*/
|
||||||
class CommunicationErrorHandler {
|
class CommunicationErrorHandler {
|
||||||
companion object {
|
companion object {
|
||||||
fun handleError(error: Throwable?, context: Context) {
|
fun handleError(error: Throwable?, context: Context) {
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
<corners
|
<corners
|
||||||
android:topLeftRadius="44dp"
|
android:topLeftRadius="44dp"
|
||||||
android:topRightRadius="44dp"
|
android:topRightRadius="44dp"
|
||||||
|
android:bottomRightRadius="44dp"
|
||||||
android:bottomLeftRadius="44dp" />
|
android:bottomLeftRadius="44dp" />
|
||||||
|
|
||||||
<padding
|
<padding
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:a="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:a="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
a:id="@+id/row_artist_layout"
|
a:id="@+id/row_artist_layout"
|
||||||
a:layout_height="wrap_content"
|
a:layout_height="wrap_content"
|
||||||
a:layout_width="match_parent"
|
a:layout_width="match_parent"
|
||||||
|
@ -13,24 +14,39 @@
|
||||||
a:layout_height="wrap_content"
|
a:layout_height="wrap_content"
|
||||||
a:gravity="center_horizontal|center_vertical"
|
a:gravity="center_horizontal|center_vertical"
|
||||||
a:minWidth="72dip"
|
a:minWidth="72dip"
|
||||||
a:minHeight="50dip"
|
a:minHeight="56dip"
|
||||||
a:paddingStart="16dip"
|
a:paddingStart="16dip"
|
||||||
a:paddingLeft="16dip"
|
a:paddingLeft="16dip"
|
||||||
a:paddingEnd="32dip"
|
a:paddingEnd="16dip"
|
||||||
a:paddingRight="32dip"
|
a:paddingRight="16dip"
|
||||||
a:text="A"
|
a:text="A"
|
||||||
a:textAppearance="?android:attr/textAppearanceLarge"
|
a:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
a:textColor="@color/cyan" />
|
a:textColor="@color/cyan" />
|
||||||
|
|
||||||
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
|
a:id="@+id/artist_coverart"
|
||||||
|
a:src="@drawable/unknown_album"
|
||||||
|
app:shapeAppearanceOverlay="@style/roundedImageView"
|
||||||
|
a:layout_width="40dp"
|
||||||
|
a:layout_height="40dp"
|
||||||
|
a:layout_toEndOf="@+id/row_section"
|
||||||
|
a:layout_toRightOf="@+id/row_section"
|
||||||
|
a:layout_gravity="center_horizontal|center_vertical"
|
||||||
|
a:layout_marginTop="8dp"
|
||||||
|
a:layout_marginLeft="8dp"
|
||||||
|
a:layout_marginRight="16dp"
|
||||||
|
a:layout_marginStart="8dp"
|
||||||
|
a:layout_marginEnd="16dp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
a:id="@+id/row_artist_name"
|
a:id="@+id/row_artist_name"
|
||||||
a:layout_width="fill_parent"
|
a:layout_width="fill_parent"
|
||||||
a:layout_height="wrap_content"
|
a:layout_height="wrap_content"
|
||||||
a:layout_toEndOf="@+id/row_section"
|
a:layout_toEndOf="@+id/artist_coverart"
|
||||||
a:layout_toRightOf="@+id/row_section"
|
a:layout_toRightOf="@+id/artist_coverart"
|
||||||
a:drawablePadding="6dip"
|
a:drawablePadding="6dip"
|
||||||
a:gravity="center_vertical"
|
a:gravity="center_vertical"
|
||||||
a:minHeight="50dip"
|
a:minHeight="56dip"
|
||||||
a:paddingLeft="3dip"
|
a:paddingLeft="3dip"
|
||||||
a:paddingRight="3dip"
|
a:paddingRight="3dip"
|
||||||
a:textAppearance="?android:attr/textAppearanceMedium" />
|
a:textAppearance="?android:attr/textAppearanceMedium" />
|
||||||
|
|
|
@ -35,6 +35,11 @@
|
||||||
<item name="android:gravity">center_vertical</item>
|
<item name="android:gravity">center_vertical</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="roundedImageView" parent="">
|
||||||
|
<item name="cornerFamily">rounded</item>
|
||||||
|
<item name="cornerSize">8dp</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<attr name="star_hollow" format="reference"/>
|
<attr name="star_hollow" format="reference"/>
|
||||||
<attr name="star_full" format="reference"/>
|
<attr name="star_full" format="reference"/>
|
||||||
<attr name="about" format="reference"/>
|
<attr name="about" format="reference"/>
|
||||||
|
|
Loading…
Reference in New Issue