early release view (+ they call me ms. refactor)
This commit is contained in:
parent
dc64cb65b1
commit
3b71cdb02a
|
@ -4,10 +4,30 @@
|
||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
<DropdownSelection timestamp="2024-09-13T01:49:05.448371200Z">
|
<DropdownSelection timestamp="2024-09-13T18:37:24.710439100Z">
|
||||||
<Target type="DEFAULT_BOOT">
|
<Target type="DEFAULT_BOOT">
|
||||||
<handle>
|
<handle>
|
||||||
<DeviceId pluginId="PhysicalDevice" identifier="serial=3374c0d40804" />
|
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\ari\.android\avd\Medium_Phone_API_35.avd" />
|
||||||
|
</handle>
|
||||||
|
</Target>
|
||||||
|
</DropdownSelection>
|
||||||
|
<DialogSelection />
|
||||||
|
</SelectionState>
|
||||||
|
<SelectionState runConfigName="Test_App.app.unitTest">
|
||||||
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
</SelectionState>
|
||||||
|
<SelectionState runConfigName="Test_App.app.main">
|
||||||
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
</SelectionState>
|
||||||
|
<SelectionState runConfigName="Test_App.app">
|
||||||
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
</SelectionState>
|
||||||
|
<SelectionState runConfigName="Test_App.app.androidTest">
|
||||||
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
|
<DropdownSelection timestamp="2024-09-13T18:37:02.586183700Z">
|
||||||
|
<Target type="DEFAULT_BOOT">
|
||||||
|
<handle>
|
||||||
|
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\ari\.android\avd\Medium_Phone_API_35.avd" />
|
||||||
</handle>
|
</handle>
|
||||||
</Target>
|
</Target>
|
||||||
</DropdownSelection>
|
</DropdownSelection>
|
||||||
|
|
6
.idea/inspectionProfiles/Project_Default.xml
Normal file
6
.idea/inspectionProfiles/Project_Default.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="ReassignedVariable" enabled="false" level="TEXT ATTRIBUTES" enabled_by_default="false" />
|
||||||
|
</profile>
|
||||||
|
</component>
|
6
.idea/render.experimental.xml
Normal file
6
.idea/render.experimental.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RenderSettings">
|
||||||
|
<option name="useLiveRendering" value="false" />
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -17,10 +17,10 @@
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity android:name=".ReleaseViewActivity"/>
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="preloaded_fonts"
|
android:name="preloaded_fonts"
|
||||||
android:resource="@array/preloaded_fonts" />
|
android:resource="@array/preloaded_fonts" />
|
||||||
|
|
|
@ -3,11 +3,10 @@ package me.arimelody.aridroid;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Handler;
|
import android.util.Log;
|
||||||
import android.os.Looper;
|
|
||||||
import android.util.LruCache;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
@ -16,41 +15,36 @@ import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.content.res.ResourcesCompat;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.net.MalformedURLException;
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public class ArtistListAdapter extends RecyclerView.Adapter<ArtistListAdapter.ArtistViewHolder> {
|
public class ArtistListAdapter extends RecyclerView.Adapter<ArtistListAdapter.ArtistViewHolder> {
|
||||||
Context context;
|
private final Context context;
|
||||||
Handler handler;
|
private final ArrayList<ArtistModel> artists;
|
||||||
ArrayList<ArtistModel> artists;
|
|
||||||
LruCache<URL, Bitmap> avatarCache;
|
|
||||||
|
|
||||||
public ArtistListAdapter(@NonNull Context context) {
|
public ArtistListAdapter(@NonNull Context context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
handler = new Handler(Looper.getMainLooper());
|
|
||||||
artists = new ArrayList<>();
|
artists = new ArrayList<>();
|
||||||
|
|
||||||
avatarCache = new LruCache<>(32);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addArtist(ArtistModel artist) {
|
public void addArtist(ArtistModel artist) {
|
||||||
artists.add(artist);
|
artists.add(artist);
|
||||||
handler.post(() -> notifyItemInserted(artists.size()));
|
notifyItemInserted(artists.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeArtist(int position) {
|
public void removeArtist(int position) {
|
||||||
artists.remove(position);
|
artists.remove(position);
|
||||||
handler.post(() -> notifyItemRemoved(position));
|
notifyItemRemoved(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearArtists() {
|
public void clearArtists() {
|
||||||
int size = artists.size();
|
int size = artists.size();
|
||||||
artists.clear();
|
artists.clear();
|
||||||
handler.post(() -> notifyItemRangeRemoved(0, size));
|
notifyItemRangeRemoved(0, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
@ -65,15 +59,43 @@ public class ArtistListAdapter extends RecyclerView.Adapter<ArtistListAdapter.Ar
|
||||||
public void onBindViewHolder(@NonNull ArtistViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull ArtistViewHolder holder, int position) {
|
||||||
ArtistModel artist = artists.get(position);
|
ArtistModel artist = artists.get(position);
|
||||||
|
|
||||||
holder.website.setOnClickListener(new View.OnClickListener() {
|
holder.website.setOnClickListener(v -> {
|
||||||
public void onClick(View v) {
|
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(artist.getWebsite()));
|
||||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(artist.getWebsite()));
|
context.startActivity(browserIntent);
|
||||||
context.startActivity(browserIntent);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
holder.name.setText(artist.getName());
|
holder.name.setText(artist.getName());
|
||||||
if (!artist.getAvatarURL().isEmpty())
|
|
||||||
holder.fetchAvatar(artist.getAvatarURL(), avatarCache);
|
if (!artist.getAvatarURL().isEmpty()) {
|
||||||
|
Thread thread = new Thread(() -> {
|
||||||
|
URL url;
|
||||||
|
try {
|
||||||
|
url = new URL(context.getResources().getString(R.string.base_url) + artist.getAvatarURL());
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check cache
|
||||||
|
Drawable cachedImage = Cache.drawables.get(url.toString());
|
||||||
|
if (cachedImage != null) {
|
||||||
|
holder.avatar.post(() -> holder.avatar.setImageDrawable(cachedImage));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// cache miss. download it!
|
||||||
|
Drawable img;
|
||||||
|
Bitmap bmp = SimpleDownloader.getImage(url);
|
||||||
|
if (bmp != null) {
|
||||||
|
img = new BitmapDrawable(context.getResources(), bmp);
|
||||||
|
} else {
|
||||||
|
img = ResourcesCompat.getDrawable(context.getResources(), R.mipmap.ic_launcher, null);
|
||||||
|
}
|
||||||
|
// store image in memory cache
|
||||||
|
Cache.drawables.put(url.toString(), img);
|
||||||
|
holder.avatar.post(() -> holder.avatar.setImageDrawable(img));
|
||||||
|
});
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
holder.avatar.setContentDescription(context.getString(R.string.artist_avatar_description, artist.getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -82,9 +104,9 @@ public class ArtistListAdapter extends RecyclerView.Adapter<ArtistListAdapter.Ar
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ArtistViewHolder extends RecyclerView.ViewHolder {
|
public static class ArtistViewHolder extends RecyclerView.ViewHolder {
|
||||||
ImageView avatar;
|
public final ImageView avatar;
|
||||||
TextView name;
|
public final TextView name;
|
||||||
Button website;
|
public final Button website;
|
||||||
|
|
||||||
public ArtistViewHolder(@NonNull View itemView) {
|
public ArtistViewHolder(@NonNull View itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
|
@ -93,35 +115,5 @@ public class ArtistListAdapter extends RecyclerView.Adapter<ArtistListAdapter.Ar
|
||||||
name = itemView.findViewById(R.id.artistName);
|
name = itemView.findViewById(R.id.artistName);
|
||||||
website = itemView.findViewById(R.id.artistWebsite);
|
website = itemView.findViewById(R.id.artistWebsite);
|
||||||
}
|
}
|
||||||
|
|
||||||
void fetchAvatar(String avatarURL, LruCache<URL, Bitmap> avatarCache) {
|
|
||||||
Thread thread = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
URL url = new URL(MainActivity.BASE_URL + avatarURL);
|
|
||||||
|
|
||||||
// check cache
|
|
||||||
Bitmap cachedImage = avatarCache.get(url);
|
|
||||||
if (cachedImage != null) {
|
|
||||||
avatar.post(() -> avatar.setImageBitmap(cachedImage));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.printf("Fetching %s...\n", url);
|
|
||||||
HttpURLConnection http = (HttpURLConnection) url.openConnection();
|
|
||||||
http.setRequestMethod("GET");
|
|
||||||
http.setRequestProperty("User-Agent", "aridroid/" + MainActivity.VERSION);
|
|
||||||
http.setRequestProperty("Accept", "image/*");
|
|
||||||
|
|
||||||
InputStream stream = http.getInputStream();
|
|
||||||
Bitmap img = BitmapFactory.decodeStream(stream);
|
|
||||||
// store image in memory cache
|
|
||||||
avatarCache.put(url, img);
|
|
||||||
avatar.post(() -> avatar.setImageBitmap(img));
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.err.printf("FATAL: Failed to fetch release artwork: %s\n", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
thread.start();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
package me.arimelody.aridroid;
|
package me.arimelody.aridroid;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
public class ArtistModel {
|
public class ArtistModel {
|
||||||
private final String id;
|
private final String id;
|
||||||
private final String name;
|
private final String name;
|
||||||
|
@ -13,6 +16,13 @@ public class ArtistModel {
|
||||||
this.avatarURL = avatarURL;
|
this.avatarURL = avatarURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ArtistModel(JSONObject json) {
|
||||||
|
this.id = json.optString("id");
|
||||||
|
this.name = json.optString("name");
|
||||||
|
this.website = json.optString("website");
|
||||||
|
this.avatarURL = json.optString("avatar");
|
||||||
|
}
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
12
app/src/main/java/me/arimelody/aridroid/Cache.java
Normal file
12
app/src/main/java/me/arimelody/aridroid/Cache.java
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package me.arimelody.aridroid;
|
||||||
|
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.util.LruCache;
|
||||||
|
|
||||||
|
public class Cache {
|
||||||
|
|
||||||
|
public static final LruCache<String, Drawable> drawables = new LruCache<>(32);
|
||||||
|
public static LruCache<String, ReleaseModel> releases = new LruCache<>(32);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
7
app/src/main/java/me/arimelody/aridroid/Constants.java
Normal file
7
app/src/main/java/me/arimelody/aridroid/Constants.java
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package me.arimelody.aridroid;
|
||||||
|
|
||||||
|
public class Constants {
|
||||||
|
|
||||||
|
public static final String TAG = "me.arimelody.aridroid";
|
||||||
|
|
||||||
|
}
|
|
@ -3,8 +3,9 @@ package me.arimelody.aridroid;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.activity.EdgeToEdge;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.core.graphics.Insets;
|
import androidx.core.graphics.Insets;
|
||||||
import androidx.core.view.ViewCompat;
|
import androidx.core.view.ViewCompat;
|
||||||
|
@ -14,26 +15,23 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.IOException;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.text.ParseException;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
public class MainActivity extends AppCompatActivity {
|
||||||
public static String BASE_URL;
|
|
||||||
public static String VERSION;
|
public static String VERSION;
|
||||||
|
|
||||||
|
RecyclerView releaseListView;
|
||||||
|
RecyclerView artistListView;
|
||||||
|
ReleaseListAdapter releaseListAdapter;
|
||||||
|
ArtistListAdapter artistListAdapter;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
EdgeToEdge.enable(this);
|
|
||||||
setContentView(R.layout.activity_main);
|
setContentView(R.layout.activity_main);
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
|
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
|
||||||
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
|
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
|
||||||
|
@ -48,128 +46,101 @@ public class MainActivity extends AppCompatActivity {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
BASE_URL = getResources().getString(R.string.base_url);
|
releaseListView = findViewById(R.id.musicList);
|
||||||
|
releaseListAdapter = new ReleaseListAdapter(this);
|
||||||
|
releaseListView.setAdapter(releaseListAdapter);
|
||||||
|
releaseListView.setLayoutManager(new LinearLayoutManager(this));
|
||||||
|
|
||||||
ReleaseListAdapter releaseListAdapter = new ReleaseListAdapter(this);
|
artistListView = findViewById(R.id.artistList);
|
||||||
fetchReleaseData(releaseListAdapter);
|
artistListAdapter = new ArtistListAdapter(this);
|
||||||
ArtistListAdapter artistListAdapter = new ArtistListAdapter(this);
|
artistListView.setAdapter(artistListAdapter);
|
||||||
fetchArtistData(artistListAdapter);
|
artistListView.setLayoutManager(new LinearLayoutManager(this));
|
||||||
|
|
||||||
RecyclerView releaseList = findViewById(R.id.musicList);
|
Thread releaseFetchThread = new Thread(() -> {
|
||||||
releaseList.setAdapter(releaseListAdapter);
|
|
||||||
releaseList.setLayoutManager(new LinearLayoutManager(this));
|
|
||||||
|
|
||||||
RecyclerView artistList = findViewById(R.id.artistList);
|
|
||||||
artistList.setAdapter(artistListAdapter);
|
|
||||||
artistList.setLayoutManager(new LinearLayoutManager(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
void fetchReleaseData(ReleaseListAdapter releaseListAdapter) {
|
|
||||||
Thread thread = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
URL url = URI.create(BASE_URL + "/api/v1/music").toURL();
|
|
||||||
System.out.printf("Fetching %s...\n", url);
|
|
||||||
HttpURLConnection http = (HttpURLConnection) url.openConnection();
|
|
||||||
http.setRequestMethod("GET");
|
|
||||||
http.setRequestProperty("User-Agent", "aridroid/" + MainActivity.VERSION);
|
|
||||||
http.setRequestProperty("Accept", "application/json");
|
|
||||||
|
|
||||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
|
||||||
byte[] buffer = new byte[1024];
|
|
||||||
int length;
|
|
||||||
while ((length = http.getInputStream().read(buffer)) != -1) {
|
|
||||||
stream.write(buffer, 0, length);
|
|
||||||
}
|
|
||||||
updateReleaseList(stream.toString(), releaseListAdapter);
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.err.printf("Failed to fetch music data: %s\n", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
thread.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateReleaseList(String res, ReleaseListAdapter releaseListAdapter) {
|
|
||||||
try {
|
|
||||||
JSONArray json = new JSONArray(res);
|
|
||||||
releaseListAdapter.clearReleases();
|
|
||||||
|
|
||||||
for (int i = 0; i < json.length(); i++) {
|
|
||||||
JSONObject obj = json.getJSONObject(i);
|
|
||||||
|
|
||||||
ArrayList<MusicCreditModel> credits = new ArrayList<>();
|
|
||||||
|
|
||||||
JSONArray jsonArtists = obj.getJSONArray("artists");
|
|
||||||
for (int ci = 0; ci < jsonArtists.length(); ci++)
|
|
||||||
credits.add(new MusicCreditModel("", jsonArtists.getString(ci), "", true));
|
|
||||||
String buylink = obj.getString("buylink");
|
|
||||||
|
|
||||||
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.GERMANY);
|
|
||||||
Date date;
|
|
||||||
try {
|
|
||||||
date = df.parse(obj.getString("releaseDate"));
|
|
||||||
} catch (ParseException e) {
|
|
||||||
System.err.printf("Failed to parse date for release %s: %s\n", obj.getString("id"), e);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
releaseListAdapter.addRelease(new ReleaseModel(
|
|
||||||
obj.getString("id"),
|
|
||||||
obj.getString("title"),
|
|
||||||
"",
|
|
||||||
obj.getString("type"),
|
|
||||||
date,
|
|
||||||
obj.getString("artwork"),
|
|
||||||
"Buy",
|
|
||||||
buylink,
|
|
||||||
obj.getString("copyright"),
|
|
||||||
null,
|
|
||||||
credits));
|
|
||||||
}
|
|
||||||
} catch (JSONException e) {
|
|
||||||
System.err.printf("Failed to parse JSON response: %s\n", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void fetchArtistData(ArtistListAdapter artistListAdapter) {
|
|
||||||
Thread thread = new Thread(() -> {
|
|
||||||
try {
|
try {
|
||||||
URL url = URI.create(BASE_URL + "/api/v1/artist").toURL();
|
URL url;
|
||||||
System.out.printf("Fetching %s...\n", url);
|
try {
|
||||||
HttpURLConnection http = (HttpURLConnection) url.openConnection();
|
url = URI.create(getResources().getString(R.string.base_url) + "/api/v1/music").toURL();
|
||||||
http.setRequestMethod("GET");
|
} catch (MalformedURLException e) {
|
||||||
http.setRequestProperty("User-Agent", "aridroid/" + MainActivity.VERSION);
|
throw new RuntimeException(e);
|
||||||
http.setRequestProperty("Accept", "application/json");
|
|
||||||
|
|
||||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
|
||||||
byte[] buffer = new byte[1024];
|
|
||||||
int length;
|
|
||||||
while ((length = http.getInputStream().read(buffer)) != -1) {
|
|
||||||
stream.write(buffer, 0, length);
|
|
||||||
}
|
}
|
||||||
updateArtistList(stream.toString(), artistListAdapter);
|
|
||||||
} catch (Exception e) {
|
JSONArray json;
|
||||||
System.err.printf("Failed to fetch artist data: %s\n", e);
|
try {
|
||||||
|
json = SimpleDownloader.getJSONArray(url);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Toast.makeText(this, "Failed to fetch release data", Toast.LENGTH_SHORT).show();
|
||||||
|
Log.e(Constants.TAG, "Failed to fetch release data: " + e);
|
||||||
|
return;
|
||||||
|
} catch (JSONException e) {
|
||||||
|
Toast.makeText(this, "Failed to parse release data", Toast.LENGTH_SHORT).show();
|
||||||
|
Log.e(Constants.TAG, "Failed to parse release data: " + e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
releaseListAdapter.clearReleases();
|
||||||
|
|
||||||
|
for (int i = 0; i < json.length(); i++) {
|
||||||
|
ReleaseModel release;
|
||||||
|
try {
|
||||||
|
release = new ReleaseModel(json.getJSONObject(i));
|
||||||
|
} catch (JSONException e) {
|
||||||
|
Log.w(Constants.TAG, "Failed to parse release data at index " + i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
releaseListView.post(() -> releaseListAdapter.addRelease(release));
|
||||||
|
if (i == 0) releaseListView.post(() -> findViewById(R.id.musicListThrobber).setVisibility(RecyclerView.GONE));
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
releaseListView.post(() -> findViewById(R.id.musicListThrobber).setVisibility(RecyclerView.GONE));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
thread.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateArtistList(String res, ArtistListAdapter artistListAdapter) {
|
Thread artistFetchThread = new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
JSONArray json = new JSONArray(res);
|
URL url;
|
||||||
artistListAdapter.clearArtists();
|
try {
|
||||||
|
url = URI.create(getResources().getString(R.string.base_url) + "/api/v1/artist").toURL();
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < json.length(); i++) {
|
JSONArray json;
|
||||||
JSONObject obj = json.getJSONObject(i);
|
try {
|
||||||
|
json = SimpleDownloader.getJSONArray(url);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Toast.makeText(this, "Failed to fetch artist data", Toast.LENGTH_SHORT).show();
|
||||||
|
Log.e(Constants.TAG, "Failed to fetch artist data: " + e);
|
||||||
|
return;
|
||||||
|
} catch (JSONException e) {
|
||||||
|
Toast.makeText(this, "Failed to parse artist data", Toast.LENGTH_SHORT).show();
|
||||||
|
Log.e(Constants.TAG, "Failed to parse artist data: " + e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
artistListAdapter.clearArtists();
|
||||||
|
|
||||||
artistListAdapter.addArtist(new ArtistModel(
|
for (int i = 0; i < json.length(); i++) {
|
||||||
obj.getString("id"),
|
ArtistModel artist;
|
||||||
obj.getString("name"),
|
try {
|
||||||
obj.getString("website"),
|
artist = new ArtistModel(json.getJSONObject(i));
|
||||||
obj.getString("avatar")));
|
} catch (JSONException e) {
|
||||||
|
Log.w(Constants.TAG, "Failed to parse artist data at index " + i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean first = i == 0;
|
||||||
|
artistListView.post(() -> {
|
||||||
|
artistListAdapter.addArtist(artist);
|
||||||
|
if (first) findViewById(R.id.artistListThrobber).setVisibility(RecyclerView.GONE);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
artistListView.post(() -> findViewById(R.id.artistListThrobber).setVisibility(RecyclerView.GONE));
|
||||||
}
|
}
|
||||||
} catch (JSONException e) {
|
});
|
||||||
System.err.printf("Failed to parse JSON response: %s\n", e);
|
|
||||||
}
|
releaseFetchThread.start();
|
||||||
|
artistFetchThread.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,18 @@
|
||||||
package me.arimelody.aridroid;
|
package me.arimelody.aridroid;
|
||||||
|
|
||||||
public class MusicCreditModel {
|
public class MusicCreditModel {
|
||||||
private final String id;
|
private final ArtistModel artist;
|
||||||
private final String name;
|
|
||||||
private final String role;
|
private final String role;
|
||||||
private final boolean primary;
|
private final boolean primary;
|
||||||
|
|
||||||
public MusicCreditModel(String id, String name, String role, boolean primary) {
|
public MusicCreditModel(ArtistModel artist, String role, boolean primary) {
|
||||||
this.id = id;
|
this.artist = artist;
|
||||||
this.name = name;
|
|
||||||
this.role = role;
|
this.role = role;
|
||||||
this.primary = primary;
|
this.primary = primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getId() {
|
public ArtistModel getArtist() {
|
||||||
return id;
|
return artist;
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRole() {
|
public String getRole() {
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
package me.arimelody.aridroid;
|
package me.arimelody.aridroid;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
import android.os.Handler;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Looper;
|
|
||||||
import android.util.LruCache;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
@ -13,10 +12,10 @@ import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.content.res.ResourcesCompat;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.net.MalformedURLException;
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
@ -24,42 +23,39 @@ import java.util.HashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
public class ReleaseListAdapter extends RecyclerView.Adapter<ReleaseListAdapter.ReleaseViewHolder> {
|
public class ReleaseListAdapter extends RecyclerView.Adapter<ReleaseListAdapter.ReleaseViewHolder> {
|
||||||
Context context;
|
private final Context context;
|
||||||
Handler handler;
|
private final ArrayList<ReleaseModel> releases;
|
||||||
ArrayList<ReleaseModel> releases;
|
private static HashMap<String, Integer> ReleaseTypes;
|
||||||
LruCache<URL, Bitmap> artworkCache;
|
|
||||||
|
|
||||||
public static HashMap<String, Integer> ReleaseTypes = new HashMap<>();
|
|
||||||
|
|
||||||
public ReleaseListAdapter(@NonNull Context context) {
|
public ReleaseListAdapter(@NonNull Context context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
handler = new Handler(Looper.getMainLooper());
|
|
||||||
releases = new ArrayList<>();
|
releases = new ArrayList<>();
|
||||||
|
|
||||||
ReleaseTypes.put("single", R.string.release_type_single);
|
if (ReleaseTypes == null) {
|
||||||
ReleaseTypes.put("album", R.string.release_type_album);
|
ReleaseTypes = new HashMap<>();
|
||||||
ReleaseTypes.put("ep", R.string.release_type_ep);
|
ReleaseTypes.put("single", R.string.release_type_single);
|
||||||
ReleaseTypes.put("lp", R.string.release_type_lp);
|
ReleaseTypes.put("album", R.string.release_type_album);
|
||||||
ReleaseTypes.put("compilation", R.string.release_type_compilation);
|
ReleaseTypes.put("ep", R.string.release_type_ep);
|
||||||
ReleaseTypes.put("upcoming", R.string.release_type_upcoming);
|
ReleaseTypes.put("lp", R.string.release_type_lp);
|
||||||
|
ReleaseTypes.put("compilation", R.string.release_type_compilation);
|
||||||
artworkCache = new LruCache<>(32);
|
ReleaseTypes.put("upcoming", R.string.release_type_upcoming);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addRelease(ReleaseModel release) {
|
public void addRelease(ReleaseModel release) {
|
||||||
releases.add(release);
|
releases.add(release);
|
||||||
handler.post(() -> notifyItemInserted(releases.size()));
|
notifyItemInserted(releases.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeRelease(int position) {
|
public void removeRelease(int position) {
|
||||||
releases.remove(position);
|
releases.remove(position);
|
||||||
handler.post(() -> notifyItemRemoved(position));
|
notifyItemRemoved(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearReleases() {
|
public void clearReleases() {
|
||||||
int size = releases.size();
|
int size = releases.size();
|
||||||
releases.clear();
|
releases.clear();
|
||||||
handler.post(() -> notifyItemRangeRemoved(0, size));
|
notifyItemRangeRemoved(0, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
@ -74,9 +70,42 @@ public class ReleaseListAdapter extends RecyclerView.Adapter<ReleaseListAdapter.
|
||||||
public void onBindViewHolder(@NonNull ReleaseViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull ReleaseViewHolder holder, int position) {
|
||||||
ReleaseModel release = releases.get(position);
|
ReleaseModel release = releases.get(position);
|
||||||
|
|
||||||
if (!release.getArtworkURL().isEmpty())
|
holder.itemView.setOnClickListener(v -> {
|
||||||
holder.fetchReleaseArtwork(release.getArtworkURL(), artworkCache);
|
Intent intent = new Intent(context, ReleaseViewActivity.class);
|
||||||
holder.artwork.setContentDescription(release.getTitle() + " artwork");
|
intent.putExtra("release", release.getId());
|
||||||
|
context.startActivity(intent);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!release.getArtworkURL().isEmpty()) {
|
||||||
|
Thread thread = new Thread(() -> {
|
||||||
|
URL url;
|
||||||
|
try {
|
||||||
|
url = new URL(context.getResources().getString(R.string.base_url) + release.getArtworkURL());
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check cache
|
||||||
|
Drawable cachedImage = Cache.drawables.get(url.toString());
|
||||||
|
if (cachedImage != null) {
|
||||||
|
holder.artwork.post(() -> holder.artwork.setImageDrawable(cachedImage));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// cache miss. download it!
|
||||||
|
Drawable img;
|
||||||
|
Bitmap bmp = SimpleDownloader.getImage(url);
|
||||||
|
if (bmp != null) {
|
||||||
|
img = new BitmapDrawable(context.getResources(), bmp);
|
||||||
|
} else {
|
||||||
|
img = ResourcesCompat.getDrawable(context.getResources(), R.mipmap.default_music_art, null);
|
||||||
|
}
|
||||||
|
// store image in memory cache
|
||||||
|
Cache.drawables.put(url.toString(), img);
|
||||||
|
holder.artwork.post(() -> holder.artwork.setImageDrawable(img));
|
||||||
|
});
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
holder.artwork.setContentDescription(context.getString(R.string.music_artwork_description, release.getTitle()));
|
||||||
|
|
||||||
holder.title.setText(release.getTitle());
|
holder.title.setText(release.getTitle());
|
||||||
|
|
||||||
|
@ -92,14 +121,14 @@ public class ReleaseListAdapter extends RecyclerView.Adapter<ReleaseListAdapter.
|
||||||
}
|
}
|
||||||
for (int i = 0; i < primaryCredits.size(); i++) {
|
for (int i = 0; i < primaryCredits.size(); i++) {
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
artist.append(primaryCredits.get(i).getName());
|
artist.append(primaryCredits.get(i).getArtist().getName());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (i == primaryCredits.size() - 1) {
|
if (i == primaryCredits.size() - 1) {
|
||||||
artist.append(" & ").append(primaryCredits.get(i).getName());
|
artist.append(" & ").append(primaryCredits.get(i).getArtist().getName());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
artist.append(primaryCredits.get(i).getName()).append(", ");
|
artist.append(", ").append(primaryCredits.get(i).getArtist().getName());
|
||||||
}
|
}
|
||||||
holder.artist.setText(artist.toString());
|
holder.artist.setText(artist.toString());
|
||||||
|
|
||||||
|
@ -113,11 +142,11 @@ public class ReleaseListAdapter extends RecyclerView.Adapter<ReleaseListAdapter.
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ReleaseViewHolder extends RecyclerView.ViewHolder {
|
public static class ReleaseViewHolder extends RecyclerView.ViewHolder {
|
||||||
ImageView artwork;
|
public final ImageView artwork;
|
||||||
TextView title;
|
public final TextView title;
|
||||||
TextView year;
|
public final TextView year;
|
||||||
TextView artist;
|
public final TextView artist;
|
||||||
TextView type;
|
public final TextView type;
|
||||||
|
|
||||||
public ReleaseViewHolder(@NonNull View itemView) {
|
public ReleaseViewHolder(@NonNull View itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
|
@ -128,35 +157,5 @@ public class ReleaseListAdapter extends RecyclerView.Adapter<ReleaseListAdapter.
|
||||||
artist = itemView.findViewById(R.id.musicArtist);
|
artist = itemView.findViewById(R.id.musicArtist);
|
||||||
type = itemView.findViewById(R.id.musicType);
|
type = itemView.findViewById(R.id.musicType);
|
||||||
}
|
}
|
||||||
|
|
||||||
void fetchReleaseArtwork(String artworkURL, LruCache<URL, Bitmap> artworkCache) {
|
|
||||||
Thread thread = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
URL url = new URL(MainActivity.BASE_URL + artworkURL);
|
|
||||||
|
|
||||||
// check cache
|
|
||||||
Bitmap cachedImage = artworkCache.get(url);
|
|
||||||
if (cachedImage != null) {
|
|
||||||
artwork.post(() -> artwork.setImageBitmap(cachedImage));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.printf("Fetching %s...\n", url);
|
|
||||||
HttpURLConnection http = (HttpURLConnection) url.openConnection();
|
|
||||||
http.setRequestMethod("GET");
|
|
||||||
http.setRequestProperty("User-Agent", "aridroid/" + MainActivity.VERSION);
|
|
||||||
http.setRequestProperty("Accept", "image/*");
|
|
||||||
|
|
||||||
InputStream stream = http.getInputStream();
|
|
||||||
Bitmap img = BitmapFactory.decodeStream(stream);
|
|
||||||
// store image in memory cache
|
|
||||||
artworkCache.put(url, img);
|
|
||||||
artwork.post(() -> artwork.setImageBitmap(img));
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.err.printf("FATAL: Failed to fetch release artwork: %s\n", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
thread.start();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,18 @@
|
||||||
package me.arimelody.aridroid;
|
package me.arimelody.aridroid;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
public class ReleaseModel {
|
public class ReleaseModel {
|
||||||
private final String id;
|
private final String id;
|
||||||
|
@ -31,6 +41,67 @@ public class ReleaseModel {
|
||||||
this.credits = credits;
|
this.credits = credits;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ReleaseModel(JSONObject json) throws JSONException {
|
||||||
|
this.id = json.getString("id");
|
||||||
|
this.title = json.getString("title");
|
||||||
|
this.description = json.optString("description");
|
||||||
|
this.type = json.getString("type");
|
||||||
|
|
||||||
|
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.GERMANY);
|
||||||
|
Date releaseDate;
|
||||||
|
try {
|
||||||
|
releaseDate = df.parse(json.getString("releaseDate"));
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.w(Constants.TAG, "Failed to parse release date for " + this.id);
|
||||||
|
releaseDate = Date.from(Instant.EPOCH);
|
||||||
|
}
|
||||||
|
this.releaseDate = releaseDate;
|
||||||
|
|
||||||
|
credits = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
if (json.has("credits")) {
|
||||||
|
JSONArray jsonArtists = json.getJSONArray("credits");
|
||||||
|
for (int i = 0; i < jsonArtists.length(); i++) {
|
||||||
|
try {
|
||||||
|
JSONObject credit = jsonArtists.getJSONObject(i);
|
||||||
|
boolean primary = credit.getBoolean("primary");
|
||||||
|
ArtistModel artist = new ArtistModel(credit);
|
||||||
|
credits.add(new MusicCreditModel(
|
||||||
|
artist,
|
||||||
|
credit.optString("role"),
|
||||||
|
primary));
|
||||||
|
} catch (JSONException e) {
|
||||||
|
Log.w(Constants.TAG, "Failed to parse credit for " + this.id + " at index " + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (json.has("artists")) {
|
||||||
|
JSONArray jsonArtists = json.getJSONArray("artists");
|
||||||
|
for (int i = 0; i < jsonArtists.length(); i++) {
|
||||||
|
try {
|
||||||
|
credits.add(new MusicCreditModel(new ArtistModel("", jsonArtists.getString(i), "", ""), "", true));
|
||||||
|
} catch (JSONException e2) {
|
||||||
|
Log.w(Constants.TAG, "Failed to parse credit for " + this.id + " at index " + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(JSONException e){
|
||||||
|
Log.w(Constants.TAG, "Failed to get credits for " + this.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.artworkURL = json.optString("artwork");
|
||||||
|
this.buyname = json.optString("buyname");
|
||||||
|
this.buylink = json.optString("buylink");
|
||||||
|
this.copyright = json.optString("copyright");
|
||||||
|
|
||||||
|
URL copyrightURL;
|
||||||
|
try {
|
||||||
|
copyrightURL = URI.create(json.getString("copyrightURL")).toURL();
|
||||||
|
} catch (Exception e) {
|
||||||
|
copyrightURL = null;
|
||||||
|
}
|
||||||
|
this.copyrightURL = copyrightURL;
|
||||||
|
}
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
134
app/src/main/java/me/arimelody/aridroid/ReleaseViewActivity.java
Normal file
134
app/src/main/java/me/arimelody/aridroid/ReleaseViewActivity.java
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
package me.arimelody.aridroid;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.activity.EdgeToEdge;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.core.content.res.ResourcesCompat;
|
||||||
|
import androidx.core.graphics.Insets;
|
||||||
|
import androidx.core.view.ViewCompat;
|
||||||
|
import androidx.core.view.WindowInsetsCompat;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
public class ReleaseViewActivity extends AppCompatActivity {
|
||||||
|
ImageView releaseArtwork;
|
||||||
|
TextView releaseTitle;
|
||||||
|
TextView releaseArtist;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
Bundle bundle = getIntent().getExtras();
|
||||||
|
if (bundle == null) throw new RuntimeException("ReleaseViewActivity started without a release ID");
|
||||||
|
|
||||||
|
String releaseID = bundle.getString("release");
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_view_release);
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.release_view), (v, insets) -> {
|
||||||
|
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
|
||||||
|
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
|
||||||
|
return insets;
|
||||||
|
});
|
||||||
|
|
||||||
|
releaseArtwork = findViewById(R.id.release_artwork);
|
||||||
|
releaseTitle = findViewById(R.id.release_title);
|
||||||
|
releaseArtist = findViewById(R.id.release_artist);
|
||||||
|
|
||||||
|
Thread fetchThread = new Thread(() -> {
|
||||||
|
JSONObject data;
|
||||||
|
ReleaseModel release = Cache.releases.get(releaseID);
|
||||||
|
if (release == null) {
|
||||||
|
// FETCH RELEASE DATA
|
||||||
|
try {
|
||||||
|
URL url = URI.create(getResources().getString(R.string.base_url) + "/api/v1/music/" + releaseID).toURL();
|
||||||
|
data = SimpleDownloader.getJSONObject(url);
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.printf("Failed to fetch data for %s: %s\n", releaseID, e);
|
||||||
|
Looper.prepare();
|
||||||
|
Toast.makeText(this, "Failed to fetch release data", Toast.LENGTH_SHORT).show();
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PARSE RELEASE DATA
|
||||||
|
try {
|
||||||
|
release = new ReleaseModel(data);
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.printf("Failed to parse data for %s: %s\n", releaseID, e);
|
||||||
|
Looper.prepare();
|
||||||
|
Toast.makeText(this, "Failed to parse release data", Toast.LENGTH_SHORT).show();
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Cache.releases.put(releaseID, release);
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder artists = new StringBuilder();
|
||||||
|
for (int i = 0; i < release.getCredits().size(); i++) {
|
||||||
|
if (!release.getCredits().get(i).isPrimary()) continue;
|
||||||
|
ArtistModel artist = release.getCredits().get(i).getArtist();
|
||||||
|
if (i == 0) {
|
||||||
|
artists.append(artist.getName());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (i == release.getCredits().size() - 1) {
|
||||||
|
artists.append(" & ").append(artist.getName());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
artists.append(", ").append(artist.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
String artworkURL = release.getArtworkURL();
|
||||||
|
Thread artworkThread = new Thread(() -> {
|
||||||
|
URL url;
|
||||||
|
try {
|
||||||
|
url = new URI(getResources().getString(R.string.base_url) + artworkURL).toURL();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.w(Constants.TAG, "Failed to fetch release artwork " + artworkURL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check cache
|
||||||
|
Drawable cachedImg = Cache.drawables.get(url.toString());
|
||||||
|
if (cachedImg != null) {
|
||||||
|
findViewById(R.id.release_view).post(() -> releaseArtwork.setImageDrawable(cachedImg));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// cache miss. download it!
|
||||||
|
Drawable img;
|
||||||
|
Bitmap bmp = SimpleDownloader.getImage(url);
|
||||||
|
if (bmp != null) {
|
||||||
|
img = new BitmapDrawable(getResources(), bmp);
|
||||||
|
} else {
|
||||||
|
img = ResourcesCompat.getDrawable(getResources(), R.mipmap.ic_launcher, null);
|
||||||
|
}
|
||||||
|
// store image in memory cache
|
||||||
|
Cache.drawables.put(url.toString(), img);
|
||||||
|
findViewById(R.id.release_view).post(() -> releaseArtwork.setImageDrawable(img));
|
||||||
|
});
|
||||||
|
artworkThread.start();
|
||||||
|
|
||||||
|
String title = release.getTitle();
|
||||||
|
findViewById(R.id.release_view).post(() -> {
|
||||||
|
releaseTitle.setText(title);
|
||||||
|
releaseArtist.setText(artists);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
fetchThread.start();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package me.arimelody.aridroid;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
public class SimpleDownloader {
|
||||||
|
|
||||||
|
public static JSONObject getJSONObject(URL url) throws IOException, JSONException {
|
||||||
|
return new JSONObject(getJSONString(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JSONArray getJSONArray(URL url) throws IOException, JSONException {
|
||||||
|
return new JSONArray(getJSONString(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getJSONString(URL url) throws IOException {
|
||||||
|
Log.d(Constants.TAG, String.format("Fetching %s...\n", url));
|
||||||
|
HttpURLConnection http = (HttpURLConnection) url.openConnection();
|
||||||
|
http.setRequestMethod("GET");
|
||||||
|
http.setRequestProperty("User-Agent", "aridroid/" + MainActivity.VERSION);
|
||||||
|
http.setRequestProperty("Accept", "application/json");
|
||||||
|
|
||||||
|
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
int length;
|
||||||
|
while ((length = http.getInputStream().read(buffer)) != -1) {
|
||||||
|
stream.write(buffer, 0, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stream.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Bitmap getImage(URL url) {
|
||||||
|
try {
|
||||||
|
Log.d(Constants.TAG, String.format("Fetching %s...\n", url));
|
||||||
|
HttpURLConnection http = (HttpURLConnection) url.openConnection();
|
||||||
|
http.setRequestMethod("GET");
|
||||||
|
http.setRequestProperty("User-Agent", "aridroid/" + MainActivity.VERSION);
|
||||||
|
http.setRequestProperty("Accept", "image/*");
|
||||||
|
|
||||||
|
InputStream stream = http.getInputStream();
|
||||||
|
return BitmapFactory.decodeStream(stream);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.w(Constants.TAG, "Failed to download image " + url);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -30,12 +30,24 @@
|
||||||
android:textColor="?attr/colorOnSurface"
|
android:textColor="?attr/colorOnSurface"
|
||||||
android:textSize="32sp" />
|
android:textSize="32sp" />
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<FrameLayout
|
||||||
android:id="@+id/musicList"
|
android:id="@+id/musicListContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginVertical="5dp"
|
app:layout_constraintTop_toBottomOf="@id/musicListHeader">
|
||||||
app:layout_constraintTop_toBottomOf="@id/musicListHeader"/>
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/musicList"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginVertical="5dp">
|
||||||
|
</androidx.recyclerview.widget.RecyclerView>
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/musicListThrobber"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginVertical="50dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/musicListHeader" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/artistListHeader"
|
android:id="@+id/artistListHeader"
|
||||||
|
@ -43,17 +55,28 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginHorizontal="10dp"
|
android:layout_marginHorizontal="10dp"
|
||||||
android:layout_marginVertical="10dp"
|
android:layout_marginVertical="10dp"
|
||||||
app:layout_constraintTop_toBottomOf="@id/musicList"
|
app:layout_constraintTop_toBottomOf="@id/musicListContainer"
|
||||||
android:textSize="32sp"
|
android:textSize="32sp"
|
||||||
android:fontFamily="@font/inter_black"
|
android:fontFamily="@font/inter_black"
|
||||||
android:textColor="?attr/colorOnSurface"
|
android:textColor="?attr/colorOnSurface"
|
||||||
android:text="@string/artists_list_title" />
|
android:text="@string/artists_list_title" />
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<FrameLayout
|
||||||
android:id="@+id/artistList"
|
android:id="@+id/artistListContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginVertical="5dp"
|
app:layout_constraintTop_toBottomOf="@id/artistListHeader">
|
||||||
app:layout_constraintTop_toBottomOf="@id/artistListHeader"/>
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/artistList"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginVertical="5dp" />
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/artistListThrobber"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginVertical="50dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/musicListHeader" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
54
app/src/main/res/layout/activity_view_release.xml
Normal file
54
app/src/main/res/layout/activity_view_release.xml
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/release_view"
|
||||||
|
android:theme="@style/AriDroid.Theme"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="?attr/colorSurface"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingVertical="20dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/release_artwork"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="300dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:contentDescription="@string/music_artwork_description"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@mipmap/default_music_art"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/release_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/release_artwork"
|
||||||
|
android:layout_marginHorizontal="20dp"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:textSize="32sp"
|
||||||
|
android:textColor="?attr/colorOnSurface"
|
||||||
|
android:fontFamily="@font/inter_black"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:text="@string/default_release_title" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/release_artist"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/release_title"
|
||||||
|
android:layout_marginHorizontal="20dp"
|
||||||
|
android:layout_marginTop="-10dp"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="?attr/colorOnSurface"
|
||||||
|
android:fontFamily="@font/inter_bold"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:text="@string/default_artist" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
|
@ -1,7 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:id="@+id/musicItem"
|
android:id="@+id/musicItem"
|
||||||
android:theme="@style/AriDroid.Card"
|
android:theme="@style/AriDroid.Card"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -28,7 +27,7 @@
|
||||||
android:id="@+id/artistAvatar"
|
android:id="@+id/artistAvatar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:contentDescription="@string/music_artwork_description"
|
android:contentDescription="@string/artist_avatar_description"
|
||||||
android:src="@mipmap/ic_launcher"/>
|
android:src="@mipmap/ic_launcher"/>
|
||||||
</androidx.cardview.widget.CardView>
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
@ -49,7 +48,7 @@
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
android:fontFamily="@font/inter_bold"
|
android:fontFamily="@font/inter_bold"
|
||||||
android:textSize="16dp"
|
android:textSize="16sp"
|
||||||
android:textColor="?attr/colorOnSurface"
|
android:textColor="?attr/colorOnSurface"
|
||||||
android:text="@string/default_artist_name" />
|
android:text="@string/default_artist_name" />
|
||||||
|
|
||||||
|
@ -61,23 +60,11 @@
|
||||||
android:paddingVertical="0dp"
|
android:paddingVertical="0dp"
|
||||||
android:text="@string/artist_website"
|
android:text="@string/artist_website"
|
||||||
android:textColor="?attr/colorSurface"
|
android:textColor="?attr/colorSurface"
|
||||||
android:textColorLink="#77B7CE"
|
android:textColorLink="?attr/colorOnSurface"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<!-- <TextView-->
|
|
||||||
<!-- android:id="@+id/artistID"-->
|
|
||||||
<!-- android:layout_width="wrap_content"-->
|
|
||||||
<!-- android:layout_height="wrap_content"-->
|
|
||||||
<!-- android:layout_marginStart="5dp"-->
|
|
||||||
<!-- app:layout_constraintRight_toRightOf="parent"-->
|
|
||||||
<!-- app:layout_constraintTop_toTopOf="@id/artistName"-->
|
|
||||||
<!-- android:fontFamily="@font/monaspace"-->
|
|
||||||
<!-- android:textColor="@color/on_surface_dim"-->
|
|
||||||
<!-- android:text="@string/default_artist_id" />-->
|
|
||||||
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</androidx.cardview.widget.CardView>
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
82
app/src/main/res/layout/top_release_card.xml
Normal file
82
app/src/main/res/layout/top_release_card.xml
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:theme="@style/AriDroid.Theme"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/colorSurface"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/header_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/release_artwork"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="300dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
android:contentDescription="@string/music_artwork_description"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:alpha="0.5"
|
||||||
|
android:src="@mipmap/default_music_art"/>
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:layout_marginHorizontal="20dp"
|
||||||
|
app:cardBackgroundColor="?attr/colorPrimary"
|
||||||
|
app:cardCornerRadius="50dp">
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginVertical="5dp"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:fontFamily="@font/inter_bold"
|
||||||
|
android:textColor="?attr/colorOnPrimary"
|
||||||
|
android:text="@string/new_item" />
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/release_artist"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/release_title"
|
||||||
|
android:layout_marginHorizontal="20dp"
|
||||||
|
android:layout_marginBottom="-14dp"
|
||||||
|
android:textSize="24sp"
|
||||||
|
android:textColor="?attr/colorOnSurface"
|
||||||
|
android:fontFamily="@font/inter_bold"
|
||||||
|
android:text="@string/default_artist" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/release_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/tap_to_view"
|
||||||
|
android:layout_marginHorizontal="20dp"
|
||||||
|
android:layout_marginBottom="-10dp"
|
||||||
|
android:textSize="40sp"
|
||||||
|
android:textColor="?attr/colorOnSurface"
|
||||||
|
android:fontFamily="@font/inter_black"
|
||||||
|
android:text="@string/default_release_title" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tap_to_view"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/release_artwork"
|
||||||
|
android:layout_marginHorizontal="20dp"
|
||||||
|
android:layout_marginBottom="10dp"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="?attr/colorOnSurface"
|
||||||
|
android:text="@string/tap_to_view" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -1,14 +1,17 @@
|
||||||
<resources>
|
<resources>
|
||||||
<style name="AriDroid.Theme" parent="AriDroid.Base">
|
<style name="AriDroid.Theme" parent="AriDroid.Base">
|
||||||
<item name="colorPrimary">@color/brand_green</item>
|
<item name="colorPrimary">@color/brand_green</item>
|
||||||
|
<item name="colorOnPrimary">@color/dark100</item>
|
||||||
<item name="colorSecondary">@color/brand_pink</item>
|
<item name="colorSecondary">@color/brand_pink</item>
|
||||||
|
<item name="colorOnSecondary">@color/dark100</item>
|
||||||
<item name="colorSurface">@color/dark100</item>
|
<item name="colorSurface">@color/dark100</item>
|
||||||
<item name="colorOnSurface">@color/white90</item>
|
<item name="colorOnSurface">@color/white90</item>
|
||||||
<item name="android:fontFamily">@font/inter</item>
|
<item name="colorOnSurfaceVariant">@color/white50</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="AriDroid.Card" parent="AriDroid.Theme">
|
<style name="AriDroid.Card" parent="AriDroid.Theme">
|
||||||
<item name="colorSurface">@color/dark90</item>
|
<item name="colorSurface">@color/dark90</item>
|
||||||
<item name="colorOnSurface">@color/white100</item>
|
<item name="colorOnSurface">@color/white90</item>
|
||||||
|
<item name="cornerRadius">10dp</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="brand_green">#FFB7FD49</color>
|
<color name="brand_green">#FFB7FD49</color>
|
||||||
<color name="brand_green_dark">#FF465E1F</color>
|
<color name="brand_green_dark">#FF5E7F2A</color>
|
||||||
<color name="brand_yellow">#FFF8E05B</color>
|
<color name="brand_yellow">#FFF8E05B</color>
|
||||||
<color name="brand_yellow_dark">#FFC6A80F</color>
|
<color name="brand_yellow_dark">#FFC6A80F</color>
|
||||||
<color name="brand_pink">#FFF788FE</color>
|
<color name="brand_pink">#FFF788FE</color>
|
||||||
<color name="brand_pink_dark">#FFDC62E5</color>
|
<color name="brand_pink_dark">#FFB448BB</color>
|
||||||
|
|
||||||
<color name="dark100">#FF000000</color>
|
<color name="dark100">#FF000000</color>
|
||||||
<color name="dark90">#FF101010</color>
|
<color name="dark90">#FF101010</color>
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
<string name="base_url">https://arimelody.me</string>
|
<string name="base_url">https://arimelody.me</string>
|
||||||
<string name="app_name">AriDroid</string>
|
<string name="app_name">AriDroid</string>
|
||||||
|
|
||||||
|
<string name="new_item">NEW</string>
|
||||||
|
<string name="tap_to_view">Tap to view</string>
|
||||||
|
|
||||||
<string name="releases_list_title">Releases</string>
|
<string name="releases_list_title">Releases</string>
|
||||||
<string name="music_artwork_description">%s artwork</string>
|
<string name="music_artwork_description">%s artwork</string>
|
||||||
<string name="default_release_title">Untitled Release</string>
|
<string name="default_release_title">Untitled Release</string>
|
||||||
|
@ -26,4 +29,5 @@
|
||||||
<string name="default_artist_name">Unknown Artist</string>
|
<string name="default_artist_name">Unknown Artist</string>
|
||||||
<string name="default_artist_id">artist001</string>
|
<string name="default_artist_id">artist001</string>
|
||||||
<string name="artist_website">Website</string>
|
<string name="artist_website">Website</string>
|
||||||
|
<string name="artist_avatar_description">%s\'s avatar</string>
|
||||||
</resources>
|
</resources>
|
|
@ -4,11 +4,13 @@
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="AriDroid.Theme" parent="AriDroid.Base">
|
<style name="AriDroid.Theme" parent="AriDroid.Base">
|
||||||
<item name="colorPrimary">@color/brand_green_dark</item>
|
<item name="colorPrimary">@color/brand_pink_dark</item>
|
||||||
<item name="colorSecondary">@color/brand_pink_dark</item>
|
<item name="colorOnPrimary">@color/white100</item>
|
||||||
|
<item name="colorSecondary">@color/brand_green_dark</item>
|
||||||
|
<item name="colorOnSecondary">@color/white100</item>
|
||||||
<item name="colorSurface">@color/white90</item>
|
<item name="colorSurface">@color/white90</item>
|
||||||
<item name="colorOnSurfaceVariant">@color/white50</item>
|
|
||||||
<item name="colorOnSurface">@color/dark90</item>
|
<item name="colorOnSurface">@color/dark90</item>
|
||||||
|
<item name="colorOnSurfaceVariant">@color/dark50</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="AriDroid.Card" parent="AriDroid.Theme">
|
<style name="AriDroid.Card" parent="AriDroid.Theme">
|
||||||
|
|
Loading…
Reference in a new issue