diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index b268ef3..4579e35 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -4,6 +4,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 13cae44..ebf27e6 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -4,16 +4,18 @@ plugins {
android {
namespace = "me.arimelody.aridroid"
- compileSdk = 34
+ compileSdk = 35
defaultConfig {
applicationId = "me.arimelody.aridroid"
minSdk = 28
- targetSdk = 34
- versionCode = 1
- versionName = "1.0"
+ targetSdk = 35
+ versionCode = 2
+ versionName = "1.1"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ resourceConfigurations += setOf()
+ setProperty("archivesBaseName", applicationId + "-v" + versionCode + "(" + versionName + ")")
}
buildTypes {
diff --git a/app/release/baselineProfiles/0/me.arimelody.aridroid-v2(1.1)-release.dm b/app/release/baselineProfiles/0/me.arimelody.aridroid-v2(1.1)-release.dm
new file mode 100644
index 0000000..328c4be
Binary files /dev/null and b/app/release/baselineProfiles/0/me.arimelody.aridroid-v2(1.1)-release.dm differ
diff --git a/app/release/baselineProfiles/1/me.arimelody.aridroid-v2(1.1)-release.dm b/app/release/baselineProfiles/1/me.arimelody.aridroid-v2(1.1)-release.dm
new file mode 100644
index 0000000..aea7f86
Binary files /dev/null and b/app/release/baselineProfiles/1/me.arimelody.aridroid-v2(1.1)-release.dm differ
diff --git a/app/release/me.arimelody.aridroid-v2(1.1)-release.apk b/app/release/me.arimelody.aridroid-v2(1.1)-release.apk
new file mode 100644
index 0000000..b7e5443
Binary files /dev/null and b/app/release/me.arimelody.aridroid-v2(1.1)-release.apk differ
diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json
new file mode 100644
index 0000000..67c29ec
--- /dev/null
+++ b/app/release/output-metadata.json
@@ -0,0 +1,37 @@
+{
+ "version": 3,
+ "artifactType": {
+ "type": "APK",
+ "kind": "Directory"
+ },
+ "applicationId": "me.arimelody.aridroid",
+ "variantName": "release",
+ "elements": [
+ {
+ "type": "SINGLE",
+ "filters": [],
+ "attributes": [],
+ "versionCode": 2,
+ "versionName": "1.1",
+ "outputFile": "me.arimelody.aridroid-v2(1.1)-release.apk"
+ }
+ ],
+ "elementType": "File",
+ "baselineProfiles": [
+ {
+ "minApi": 28,
+ "maxApi": 30,
+ "baselineProfiles": [
+ "baselineProfiles/1/me.arimelody.aridroid-v2(1.1)-release.dm"
+ ]
+ },
+ {
+ "minApi": 31,
+ "maxApi": 2147483647,
+ "baselineProfiles": [
+ "baselineProfiles/0/me.arimelody.aridroid-v2(1.1)-release.dm"
+ ]
+ }
+ ],
+ "minSdkVersionForDexing": 28
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 37bf20b..1f49721 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -11,7 +11,7 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
- android:theme="@style/Theme.AriDroid">
+ android:theme="@style/AriDroid.Base">
@@ -21,6 +21,9 @@
+
diff --git a/app/src/main/java/me/arimelody/aridroid/ArtistListAdapter.java b/app/src/main/java/me/arimelody/aridroid/ArtistListAdapter.java
new file mode 100644
index 0000000..7a86baa
--- /dev/null
+++ b/app/src/main/java/me/arimelody/aridroid/ArtistListAdapter.java
@@ -0,0 +1,127 @@
+package me.arimelody.aridroid;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.LruCache;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.ArrayList;
+
+public class ArtistListAdapter extends RecyclerView.Adapter {
+ Context context;
+ Handler handler;
+ ArrayList artists;
+ LruCache avatarCache;
+
+ public ArtistListAdapter(@NonNull Context context) {
+ this.context = context;
+ handler = new Handler(Looper.getMainLooper());
+ artists = new ArrayList<>();
+
+ avatarCache = new LruCache<>(32);
+ }
+
+ public void addArtist(ArtistModel artist) {
+ artists.add(artist);
+ handler.post(() -> notifyItemInserted(artists.size()));
+ }
+
+ public void removeArtist(int position) {
+ artists.remove(position);
+ handler.post(() -> notifyItemRemoved(position));
+ }
+
+ public void clearArtists() {
+ int size = artists.size();
+ artists.clear();
+ handler.post(() -> notifyItemRangeRemoved(0, size));
+ }
+
+ @NonNull
+ @Override
+ public ArtistViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ LayoutInflater inflater = LayoutInflater.from(context);
+ View view = inflater.inflate(R.layout.artist_list_item, parent, false);
+ return new ArtistViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ArtistViewHolder holder, int position) {
+ ArtistModel artist = artists.get(position);
+
+ holder.website.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(artist.getWebsite()));
+ context.startActivity(browserIntent);
+ }
+ });
+ holder.name.setText(artist.getName());
+ if (!artist.getAvatarURL().isEmpty())
+ holder.fetchAvatar(artist.getAvatarURL(), avatarCache);
+ }
+
+ @Override
+ public int getItemCount() {
+ return artists.size();
+ }
+
+ public static class ArtistViewHolder extends RecyclerView.ViewHolder {
+ ImageView avatar;
+ TextView name;
+ Button website;
+
+ public ArtistViewHolder(@NonNull View itemView) {
+ super(itemView);
+
+ avatar = itemView.findViewById(R.id.artistAvatar);
+ name = itemView.findViewById(R.id.artistName);
+ website = itemView.findViewById(R.id.artistWebsite);
+ }
+
+ void fetchAvatar(String avatarURL, LruCache 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();
+ }
+ }
+}
diff --git a/app/src/main/java/me/arimelody/aridroid/ArtistModel.java b/app/src/main/java/me/arimelody/aridroid/ArtistModel.java
new file mode 100644
index 0000000..0e97d7e
--- /dev/null
+++ b/app/src/main/java/me/arimelody/aridroid/ArtistModel.java
@@ -0,0 +1,31 @@
+package me.arimelody.aridroid;
+
+public class ArtistModel {
+ private final String id;
+ private final String name;
+ private final String website;
+ private final String avatarURL;
+
+ public ArtistModel(String id, String name, String website, String avatarURL) {
+ this.id = id;
+ this.name = name;
+ this.website = website;
+ this.avatarURL = avatarURL;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getWebsite() {
+ return website;
+ }
+
+ public String getAvatarURL() {
+ return avatarURL;
+ }
+}
diff --git a/app/src/main/java/me/arimelody/aridroid/MainActivity.java b/app/src/main/java/me/arimelody/aridroid/MainActivity.java
index c479504..caa1d12 100644
--- a/app/src/main/java/me/arimelody/aridroid/MainActivity.java
+++ b/app/src/main/java/me/arimelody/aridroid/MainActivity.java
@@ -1,9 +1,8 @@
package me.arimelody.aridroid;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.os.Bundle;
-import android.util.JsonReader;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
@@ -18,12 +17,9 @@ import org.json.JSONException;
import org.json.JSONObject;
import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
-import java.nio.channels.ClosedByInterruptException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -31,11 +27,8 @@ import java.util.Date;
import java.util.Locale;
public class MainActivity extends AppCompatActivity {
-
public static String BASE_URL;
-
- RecyclerView musicList;
- MusicListAdapter musicListAdapter;
+ public static String VERSION;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -48,55 +41,61 @@ public class MainActivity extends AppCompatActivity {
return insets;
});
+ try {
+ PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
+ VERSION = pInfo.versionName;
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+
BASE_URL = getResources().getString(R.string.base_url);
- musicList = findViewById(R.id.musicList);
+ ReleaseListAdapter releaseListAdapter = new ReleaseListAdapter(this);
+ fetchReleaseData(releaseListAdapter);
+ ArtistListAdapter artistListAdapter = new ArtistListAdapter(this);
+ fetchArtistData(artistListAdapter);
- ArrayList items = new ArrayList<>();
- fetchMusicData(items);
+ RecyclerView releaseList = findViewById(R.id.musicList);
+ releaseList.setAdapter(releaseListAdapter);
+ releaseList.setLayoutManager(new LinearLayoutManager(this));
- musicListAdapter = new MusicListAdapter(this, items);
-
- musicList.setAdapter(musicListAdapter);
- musicList.setLayoutManager(new LinearLayoutManager(this));
+ RecyclerView artistList = findViewById(R.id.artistList);
+ artistList.setAdapter(artistListAdapter);
+ artistList.setLayoutManager(new LinearLayoutManager(this));
}
- void fetchMusicData(ArrayList items) {
+ 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");
+ http.setRequestProperty("User-Agent", "aridroid/" + MainActivity.VERSION);
http.setRequestProperty("Accept", "application/json");
ByteArrayOutputStream stream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
- int length = 0;
+ int length;
while ((length = http.getInputStream().read(buffer)) != -1) {
stream.write(buffer, 0, length);
}
- updateReleaseList(stream.toString(), items);
+ updateReleaseList(stream.toString(), releaseListAdapter);
} catch (Exception e) {
- System.err.printf("Failed to fetch music data: %s\n", e.toString());
+ System.err.printf("Failed to fetch music data: %s\n", e);
}
});
thread.start();
}
- void updateReleaseList(String res, ArrayList items) {
+ void updateReleaseList(String res, ReleaseListAdapter releaseListAdapter) {
try {
JSONArray json = new JSONArray(res);
- items.clear();
- musicList.post(() -> {
- musicListAdapter.notifyItemRangeRemoved(0, items.size());
- });
+ releaseListAdapter.clearReleases();
for (int i = 0; i < json.length(); i++) {
JSONObject obj = json.getJSONObject(i);
- String id = obj.getString("id");
ArrayList credits = new ArrayList<>();
JSONArray jsonArtists = obj.getJSONArray("artists");
@@ -109,12 +108,12 @@ public class MainActivity extends AppCompatActivity {
try {
date = df.parse(obj.getString("releaseDate"));
} catch (ParseException e) {
- System.err.printf("Failed to parse date for release %s: %s\n", id, e.toString());
+ System.err.printf("Failed to parse date for release %s: %s\n", obj.getString("id"), e);
continue;
}
- MusicItemModel item = new MusicItemModel(
- id,
+ releaseListAdapter.addRelease(new ReleaseModel(
+ obj.getString("id"),
obj.getString("title"),
"",
obj.getString("type"),
@@ -124,15 +123,53 @@ public class MainActivity extends AppCompatActivity {
buylink,
obj.getString("copyright"),
null,
- credits);
-
- items.add(item);
- musicList.post(() -> {
- musicListAdapter.notifyItemInserted(items.size());
- });
+ credits));
}
} catch (JSONException e) {
- System.err.printf("Failed to parse JSON response: %s\n", e.toString());
+ System.err.printf("Failed to parse JSON response: %s\n", e);
+ }
+ }
+
+ void fetchArtistData(ArtistListAdapter artistListAdapter) {
+ Thread thread = new Thread(() -> {
+ try {
+ URL url = URI.create(BASE_URL + "/api/v1/artist").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);
+ }
+ updateArtistList(stream.toString(), artistListAdapter);
+ } catch (Exception e) {
+ System.err.printf("Failed to fetch artist data: %s\n", e);
+ }
+ });
+ thread.start();
+ }
+
+ void updateArtistList(String res, ArtistListAdapter artistListAdapter) {
+ try {
+ JSONArray json = new JSONArray(res);
+ artistListAdapter.clearArtists();
+
+ for (int i = 0; i < json.length(); i++) {
+ JSONObject obj = json.getJSONObject(i);
+
+ artistListAdapter.addArtist(new ArtistModel(
+ obj.getString("id"),
+ obj.getString("name"),
+ obj.getString("website"),
+ obj.getString("avatar")));
+ }
+ } catch (JSONException e) {
+ System.err.printf("Failed to parse JSON response: %s\n", e);
}
}
}
diff --git a/app/src/main/java/me/arimelody/aridroid/MusicListAdapter.java b/app/src/main/java/me/arimelody/aridroid/ReleaseListAdapter.java
similarity index 62%
rename from app/src/main/java/me/arimelody/aridroid/MusicListAdapter.java
rename to app/src/main/java/me/arimelody/aridroid/ReleaseListAdapter.java
index 81f64fe..3fb3656 100644
--- a/app/src/main/java/me/arimelody/aridroid/MusicListAdapter.java
+++ b/app/src/main/java/me/arimelody/aridroid/ReleaseListAdapter.java
@@ -3,6 +3,9 @@ package me.arimelody.aridroid;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.LruCache;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -10,7 +13,6 @@ import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import java.io.InputStream;
@@ -20,17 +22,19 @@ import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Locale;
-import java.util.Map;
-public class MusicListAdapter extends RecyclerView.Adapter {
+public class ReleaseListAdapter extends RecyclerView.Adapter {
Context context;
- ArrayList data;
+ Handler handler;
+ ArrayList releases;
+ LruCache artworkCache;
- public HashMap ReleaseTypes = new HashMap();
+ public static HashMap ReleaseTypes = new HashMap<>();
- public MusicListAdapter(Context context, ArrayList data) {
+ public ReleaseListAdapter(@NonNull Context context) {
this.context = context;
- this.data = data;
+ handler = new Handler(Looper.getMainLooper());
+ releases = new ArrayList<>();
ReleaseTypes.put("single", R.string.release_type_single);
ReleaseTypes.put("album", R.string.release_type_album);
@@ -38,21 +42,40 @@ public class MusicListAdapter extends RecyclerView.Adapter(32);
+ }
+
+ public void addRelease(ReleaseModel release) {
+ releases.add(release);
+ handler.post(() -> notifyItemInserted(releases.size()));
+ }
+
+ public void removeRelease(int position) {
+ releases.remove(position);
+ handler.post(() -> notifyItemRemoved(position));
+ }
+
+ public void clearReleases() {
+ int size = releases.size();
+ releases.clear();
+ handler.post(() -> notifyItemRangeRemoved(0, size));
}
@NonNull
@Override
- public MusicListAdapter.MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ public ReleaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(context);
- View view = inflater.inflate(R.layout.music_item, parent, false);
- return new MusicListAdapter.MyViewHolder(view);
+ View view = inflater.inflate(R.layout.release_list_item, parent, false);
+ return new ReleaseViewHolder(view);
}
@Override
- public void onBindViewHolder(@NonNull MusicListAdapter.MyViewHolder holder, int position) {
- MusicItemModel release = data.get(position);
+ public void onBindViewHolder(@NonNull ReleaseViewHolder holder, int position) {
+ ReleaseModel release = releases.get(position);
- holder.fetchReleaseArtwork(release.getArtwork());
+ if (!release.getArtworkURL().isEmpty())
+ holder.fetchReleaseArtwork(release.getArtworkURL(), artworkCache);
holder.artwork.setContentDescription(release.getTitle() + " artwork");
holder.title.setText(release.getTitle());
@@ -86,43 +109,51 @@ public class MusicListAdapter extends RecyclerView.Adapter 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");
+ http.setRequestProperty("User-Agent", "aridroid/" + MainActivity.VERSION);
http.setRequestProperty("Accept", "image/*");
InputStream stream = http.getInputStream();
Bitmap img = BitmapFactory.decodeStream(stream);
- // TODO: image caching
- // https://developer.android.com/topic/performance/graphics/cache-bitmap#java
+ // 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.toString());
+ System.err.printf("FATAL: Failed to fetch release artwork: %s\n", e);
}
});
thread.start();
diff --git a/app/src/main/java/me/arimelody/aridroid/MusicItemModel.java b/app/src/main/java/me/arimelody/aridroid/ReleaseModel.java
similarity index 79%
rename from app/src/main/java/me/arimelody/aridroid/MusicItemModel.java
rename to app/src/main/java/me/arimelody/aridroid/ReleaseModel.java
index 77866fd..44dae2b 100644
--- a/app/src/main/java/me/arimelody/aridroid/MusicItemModel.java
+++ b/app/src/main/java/me/arimelody/aridroid/ReleaseModel.java
@@ -4,26 +4,26 @@ import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
-public class MusicItemModel {
+public class ReleaseModel {
private final String id;
private final String title;
private final String description;
private final String type;
private final Date releaseDate;
- private final String artwork;
+ private final String artworkURL;
private final String buyname;
private final String buylink;
private final String copyright;
private final URL copyrightURL;
private final ArrayList credits;
- public MusicItemModel(String id, String title, String description, String type, Date releaseDate, String artwork, String buyname, String buylink, String copyright, URL copyrightURL, ArrayList credits) {
+ public ReleaseModel(String id, String title, String description, String type, Date releaseDate, String artwork, String buyname, String buylink, String copyright, URL copyrightURL, ArrayList credits) {
this.id = id;
this.title = title;
this.description = description;
this.type = type;
this.releaseDate = releaseDate;
- this.artwork = artwork;
+ this.artworkURL = artwork;
this.buyname = buyname;
this.buylink = buylink;
this.copyright = copyright;
@@ -51,8 +51,8 @@ public class MusicItemModel {
return releaseDate;
}
- public String getArtwork() {
- return artwork;
+ public String getArtworkURL() {
+ return artworkURL;
}
public String getBuyname() {
diff --git a/app/src/main/res/color/on_surface_dim.xml b/app/src/main/res/color/on_surface_dim.xml
new file mode 100644
index 0000000..4d7ea1b
--- /dev/null
+++ b/app/src/main/res/color/on_surface_dim.xml
@@ -0,0 +1,6 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/font/inter.xml b/app/src/main/res/font/inter.xml
new file mode 100644
index 0000000..71311f0
--- /dev/null
+++ b/app/src/main/res/font/inter.xml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/app/src/main/res/font/inter_black.xml b/app/src/main/res/font/inter_black.xml
new file mode 100644
index 0000000..9995dba
--- /dev/null
+++ b/app/src/main/res/font/inter_black.xml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/app/src/main/res/font/inter_bold.xml b/app/src/main/res/font/inter_bold.xml
new file mode 100644
index 0000000..fc40af7
--- /dev/null
+++ b/app/src/main/res/font/inter_bold.xml
@@ -0,0 +1,7 @@
+
+
+
diff --git a/app/src/main/res/font/monaspace.xml b/app/src/main/res/font/monaspace.xml
new file mode 100644
index 0000000..fe1090f
--- /dev/null
+++ b/app/src/main/res/font/monaspace.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/font/monaspace_bold.otf b/app/src/main/res/font/monaspace_bold.otf
new file mode 100644
index 0000000..75d618a
Binary files /dev/null and b/app/src/main/res/font/monaspace_bold.otf differ
diff --git a/app/src/main/res/font/monaspace_regular.otf b/app/src/main/res/font/monaspace_regular.otf
new file mode 100644
index 0000000..e7159da
Binary files /dev/null and b/app/src/main/res/font/monaspace_regular.otf differ
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index ce14471..0d846d8 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,19 +1,60 @@
-
-
+ app:layout_constraintLeft_toLeftOf="parent">
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/artist_list_item.xml b/app/src/main/res/layout/artist_list_item.xml
new file mode 100644
index 0000000..3dfd357
--- /dev/null
+++ b/app/src/main/res/layout/artist_list_item.xml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/music_item.xml b/app/src/main/res/layout/release_list_item.xml
similarity index 62%
rename from app/src/main/res/layout/music_item.xml
rename to app/src/main/res/layout/release_list_item.xml
index dab0d1e..0d57655 100644
--- a/app/src/main/res/layout/music_item.xml
+++ b/app/src/main/res/layout/release_list_item.xml
@@ -1,72 +1,82 @@
+ android:layout_marginHorizontal="10dp"
+ android:layout_marginVertical="5dp"
+ app:cardBackgroundColor="?attr/colorSurface"
+ app:cardCornerRadius="?attr/cornerRadius">
-
+ app:cardCornerRadius="5dp">
+
+
+ app:layout_constraintTop_toTopOf="@id/musicArtworkContainer">
+ app:layout_constraintTop_toTopOf="parent"
+ android:fontFamily="@font/inter_bold"
+ android:textColor="?attr/colorOnSurface"
+ android:text="@string/default_release_title" />
+ app:layout_constraintTop_toTopOf="@id/musicTitle"
+ android:fontFamily="@font/inter_bold"
+ android:textColor="@color/on_surface_dim"
+ android:text="@string/default_year" />
+ app:layout_constraintTop_toBottomOf="@id/musicTitle"
+ android:textColor="?attr/colorOnSurface"
+ android:text="@string/default_artist" />
+ app:layout_constraintTop_toBottomOf="@id/musicArtist"
+ android:textColor="@color/on_surface_dim"
+ android:text="@string/default_release_type" />
diff --git a/app/src/main/res/mipmap-hdpi/default_music_art.png b/app/src/main/res/mipmap-hdpi/default_music_art.png
new file mode 100644
index 0000000..8e19b8c
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/default_music_art.png differ
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
index ce53919..a4d7a14 100644
--- a/app/src/main/res/values-night/themes.xml
+++ b/app/src/main/res/values-night/themes.xml
@@ -1,7 +1,14 @@
-
-
-
+
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
deleted file mode 100644
index c8524cd..0000000
--- a/app/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- #FF000000
- #FFFFFFFF
-
\ No newline at end of file
diff --git a/app/src/main/res/values/colours.xml b/app/src/main/res/values/colours.xml
new file mode 100644
index 0000000..14bf7e5
--- /dev/null
+++ b/app/src/main/res/values/colours.xml
@@ -0,0 +1,19 @@
+
+
+ #FFB7FD49
+ #FF465E1F
+ #FFF8E05B
+ #FFC6A80F
+ #FFF788FE
+ #FFDC62E5
+
+ #FF000000
+ #FF101010
+ #FF202020
+ #FF404040
+
+ #FFFFFFFF
+ #FFF0F0F0
+ #FFD0D0D0
+ #FFB0B0B0
+
\ No newline at end of file
diff --git a/app/src/main/res/values/font_certs.xml b/app/src/main/res/values/font_certs.xml
new file mode 100644
index 0000000..d2226ac
--- /dev/null
+++ b/app/src/main/res/values/font_certs.xml
@@ -0,0 +1,17 @@
+
+
+
+ - @array/com_google_android_gms_fonts_certs_dev
+ - @array/com_google_android_gms_fonts_certs_prod
+
+
+ -
+ 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
+
+
+
diff --git a/app/src/main/res/values/preloaded_fonts.xml b/app/src/main/res/values/preloaded_fonts.xml
new file mode 100644
index 0000000..b0d075d
--- /dev/null
+++ b/app/src/main/res/values/preloaded_fonts.xml
@@ -0,0 +1,8 @@
+
+
+
+ - @font/inter
+ - @font/inter_black
+ - @font/inter_bold
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 107704e..ed0f435 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2,6 +2,7 @@
https://arimelody.me
AriDroid
+ Releases
%s artwork
Untitled Release
Untitled Track
@@ -9,7 +10,6 @@
2002
Release
0 tracks
-
Unknown
Single
Album
@@ -17,9 +17,13 @@
LP
Compilation
Upcoming
-
- (%d track)
- (%d tracks)
+
+ Artists
+ Unknown Artist
+ artist001
+ Website
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 9895922..a4c9a52 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,9 +1,19 @@
-
-
-
-
+
+
+
diff --git a/gradlew b/gradlew
old mode 100755
new mode 100644