move activities to root directory

This commit is contained in:
dkanada 2020-12-13 22:08:21 +09:00
commit 53657919af
30 changed files with 54 additions and 54 deletions

View file

@ -0,0 +1,175 @@
package com.dkanada.gramophone.activities;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import androidx.annotation.NonNull;
import com.kabouzeid.appthemehelper.ThemeStore;
import com.dkanada.gramophone.databinding.ActivityAboutBinding;
import com.dkanada.gramophone.databinding.CardAboutAppBinding;
import com.dkanada.gramophone.databinding.CardAuthorBinding;
import com.dkanada.gramophone.databinding.CardSpecialThanksBinding;
import com.dkanada.gramophone.databinding.CardSupportDevelopmentBinding;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.activities.base.AbsBaseActivity;
@SuppressWarnings("FieldCanBeLocal")
public class AboutActivity extends AbsBaseActivity implements View.OnClickListener {
ActivityAboutBinding binding;
CardAboutAppBinding aboutBinding;
CardAuthorBinding authorBinding;
CardSupportDevelopmentBinding supportBinding;
CardSpecialThanksBinding thanksBinding;
private static String GITHUB = "https://github.com/dkanada/gelli";
private static String TWITTER = "https://twitter.com/karimjabouzeid";
private static String WEBSITE = "https://github.com/dkanada";
private static String TRANSLATE = "https://phonograph.oneskyapp.com/collaboration/project?id=26521";
private static String RATE_ON_GOOGLE_PLAY = "https://play.google.com/store/apps/details?id=com.kabouzeid.gramophone";
private static String AIDAN_FOLLESTAD_GOOGLE_PLUS = "https://google.com/+AidanFollestad";
private static String AIDAN_FOLLESTAD_GITHUB = "https://github.com/afollestad";
private static String MAARTEN_CORPEL_GOOGLE_PLUS = "https://google.com/+MaartenCorpel";
private static String ALEKSANDAR_TESIC_GOOGLE_PLUS = "https://google.com/+aleksandartešić";
private static String EUGENE_CHEUNG_GITHUB = "https://github.com/arkon";
private static String EUGENE_CHEUNG_WEBSITE = "https://echeung.me/";
private static String ADRIAN_TWITTER = "https://twitter.com/froschgames";
private static String ADRIAN_WEBSITE = "https://froschgames.com/";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityAboutBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
aboutBinding = CardAboutAppBinding.bind(findViewById(R.id.about_layout));
authorBinding = CardAuthorBinding.bind(findViewById(R.id.author_layout));
supportBinding = CardSupportDevelopmentBinding.bind(findViewById(R.id.support_layout));
thanksBinding = CardSpecialThanksBinding.bind(findViewById(R.id.thanks_layout));
setDrawUnderStatusbar();
setStatusbarColorAuto();
setNavigationbarColorAuto();
setTaskDescriptionColorAuto();
setUpViews();
}
private void setUpViews() {
setUpToolbar();
setUpAppVersion();
setUpOnClickListeners();
}
private void setUpToolbar() {
binding.toolbar.setBackgroundColor(ThemeStore.primaryColor(this));
setSupportActionBar(binding.toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
private void setUpAppVersion() {
aboutBinding.appVersion.setText(getCurrentVersionName(this));
}
private void setUpOnClickListeners() {
authorBinding.followOnTwitter.setOnClickListener(this);
aboutBinding.appSource.setOnClickListener(this);
authorBinding.visitWebsite.setOnClickListener(this);
supportBinding.reportBugs.setOnClickListener(this);
authorBinding.writeAnEmail.setOnClickListener(this);
supportBinding.translate.setOnClickListener(this);
supportBinding.rateOnGooglePlay.setOnClickListener(this);
supportBinding.donate.setOnClickListener(this);
thanksBinding.aidanFollestadGooglePlus.setOnClickListener(this);
thanksBinding.aidanFollestadGitHub.setOnClickListener(this);
thanksBinding.maartenCorpelGooglePlus.setOnClickListener(this);
thanksBinding.aleksandarTesicGooglePlus.setOnClickListener(this);
thanksBinding.eugeneCheungGitHub.setOnClickListener(this);
thanksBinding.eugeneCheungWebsite.setOnClickListener(this);
thanksBinding.adrianTwitter.setOnClickListener(this);
thanksBinding.adrianWebsite.setOnClickListener(this);
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
private static String getCurrentVersionName(@NonNull final Context context) {
try {
return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return "Unknown";
}
@Override
public void onClick(View v) {
if (v == authorBinding.followOnTwitter) {
openUrl(TWITTER);
} else if (v == aboutBinding.appSource) {
openUrl(GITHUB);
} else if (v == authorBinding.visitWebsite) {
openUrl(WEBSITE);
} else if (v == supportBinding.reportBugs) {
openUrl(GITHUB);
} else if (v == authorBinding.writeAnEmail) {
Intent intent = new Intent(Intent.ACTION_SENDTO);
intent.setData(Uri.parse("mailto:contact@kabouzeid.com"));
intent.putExtra(Intent.EXTRA_EMAIL, "contact@kabouzeid.com");
intent.putExtra(Intent.EXTRA_SUBJECT, "Phonograph");
startActivity(Intent.createChooser(intent, "E-Mail"));
} else if (v == supportBinding.translate) {
openUrl(TRANSLATE);
} else if (v == supportBinding.rateOnGooglePlay) {
openUrl(RATE_ON_GOOGLE_PLAY);
} else if (v == supportBinding.donate) {
openUrl(RATE_ON_GOOGLE_PLAY);
} else if (v == thanksBinding.aidanFollestadGooglePlus) {
openUrl(AIDAN_FOLLESTAD_GOOGLE_PLUS);
} else if (v == thanksBinding.aidanFollestadGitHub) {
openUrl(AIDAN_FOLLESTAD_GITHUB);
} else if (v == thanksBinding.maartenCorpelGooglePlus) {
openUrl(MAARTEN_CORPEL_GOOGLE_PLUS);
} else if (v == thanksBinding.aleksandarTesicGooglePlus) {
openUrl(ALEKSANDAR_TESIC_GOOGLE_PLUS);
} else if (v == thanksBinding.eugeneCheungGitHub) {
openUrl(EUGENE_CHEUNG_GITHUB);
} else if (v == thanksBinding.eugeneCheungWebsite) {
openUrl(EUGENE_CHEUNG_WEBSITE);
} else if (v == thanksBinding.adrianTwitter) {
openUrl(ADRIAN_TWITTER);
} else if (v == thanksBinding.adrianWebsite) {
openUrl(ADRIAN_WEBSITE);
}
}
private void openUrl(String url) {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(i);
}
}

View file

@ -0,0 +1,141 @@
package com.dkanada.gramophone.activities;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
import com.dkanada.gramophone.App;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.databinding.ActivityLoginBinding;
import com.dkanada.gramophone.activities.base.AbsBaseActivity;
import com.dkanada.gramophone.util.PreferenceUtil;
import com.kabouzeid.appthemehelper.ThemeStore;
import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.model.system.SystemInfo;
import org.jellyfin.apiclient.model.users.AuthenticationResult;
public class LoginActivity extends AbsBaseActivity implements View.OnClickListener {
private ActivityLoginBinding binding;
private int primaryColor;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityLoginBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setDrawUnderStatusbar();
setStatusbarColorAuto();
setNavigationbarColorAuto();
setTaskDescriptionColorAuto();
setUpViews();
}
@Override
public void onPause() {
super.onPause();
overridePendingTransition(0, R.anim.fade_quick);
}
private void setUpViews() {
primaryColor = ThemeStore.primaryColor(this);
setUpToolbar();
setUpOnClickListeners();
binding.login.setBackgroundColor(primaryColor);
}
private void setUpToolbar() {
binding.toolbar.setBackgroundColor(primaryColor);
setSupportActionBar(binding.toolbar);
}
private void setUpOnClickListeners() {
binding.login.setOnClickListener(this);
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onClick(View v) {
String username = binding.username.getText().toString().trim();
String password = binding.password.getText().toString().trim();
String server = binding.server.getText().toString().trim();
Context context = this;
if (TextUtils.isEmpty(username)) {
Toast.makeText(this, getResources().getString(R.string.error_empty_username), Toast.LENGTH_SHORT).show();
return;
}
if (TextUtils.isEmpty(server)) {
Toast.makeText(this, getResources().getString(R.string.error_empty_server), Toast.LENGTH_SHORT).show();
return;
}
binding.login.setEnabled(false);
binding.login.setBackgroundColor(getResources().getColor(R.color.md_grey_800));
App.getApiClient().ChangeServerLocation(server);
App.getApiClient().AuthenticateUserAsync(username, password, new Response<AuthenticationResult>() {
@Override
public void onResponse(AuthenticationResult result) {
if (result.getAccessToken() == null) return;
check(server, result.getUser().getId(), result.getAccessToken());
}
@Override
public void onError(Exception exception) {
binding.login.setEnabled(true);
binding.login.setBackgroundColor(primaryColor);
if (exception.getMessage().contains("AuthFailureError")) {
Toast.makeText(context, context.getResources().getString(R.string.error_login_credentials), Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, context.getResources().getString(R.string.error_unreachable_server), Toast.LENGTH_SHORT).show();
}
}
});
}
private void check(String server, String user, String token) {
App.getApiClient().GetSystemInfoAsync(new Response<SystemInfo>() {
@Override
public void onResponse(SystemInfo result) {
if (result.getVersion().charAt(0) == '1') {
PreferenceUtil.getInstance(LoginActivity.this).setServer(server);
PreferenceUtil.getInstance(LoginActivity.this).setUser(user);
PreferenceUtil.getInstance(LoginActivity.this).setToken(token);
Intent intent = new Intent(LoginActivity.this, SplashActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
} else {
binding.login.setEnabled(true);
binding.login.setBackgroundColor(primaryColor);
Toast.makeText(LoginActivity.this, getResources().getString(R.string.error_version), Toast.LENGTH_SHORT).show();
}
}
});
}
}

View file

@ -0,0 +1,255 @@
package com.dkanada.gramophone.activities;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.drawerlayout.widget.DrawerLayout;
import com.kabouzeid.appthemehelper.ThemeStore;
import com.kabouzeid.appthemehelper.util.ATHUtil;
import com.kabouzeid.appthemehelper.util.NavigationViewUtil;
import com.dkanada.gramophone.databinding.ActivityMainContentBinding;
import com.dkanada.gramophone.databinding.ActivityMainDrawerLayoutBinding;
import com.dkanada.gramophone.databinding.NavigationDrawerHeaderBinding;
import com.dkanada.gramophone.dialogs.ConfirmLogoutDialog;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.glide.CustomGlideRequest;
import com.dkanada.gramophone.helper.MusicPlayerRemote;
import com.dkanada.gramophone.model.Song;
import com.dkanada.gramophone.activities.base.AbsSlidingMusicPanelActivity;
import com.dkanada.gramophone.ui.fragments.mainactivity.library.LibraryFragment;
import com.dkanada.gramophone.util.MusicUtil;
import com.dkanada.gramophone.util.QueryUtil;
import com.sothree.slidinguppanel.SlidingUpPanelLayout;
import org.jellyfin.apiclient.model.dto.BaseItemDto;
import java.util.List;
public class MainActivity extends AbsSlidingMusicPanelActivity {
private ActivityMainDrawerLayoutBinding binding;
private ActivityMainContentBinding contentBinding;
private NavigationDrawerHeaderBinding navigationBinding;
private boolean onLogout;
@Nullable
MainActivityFragmentCallbacks currentFragment;
@Nullable
private List<BaseItemDto> libraries;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setDrawUnderStatusbar();
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
binding.navigationView.setFitsSystemWindows(false);
}
Menu menu = binding.navigationView.getMenu();
QueryUtil.getLibraries(media -> {
libraries = (List<BaseItemDto>) media;
menu.clear();
for (BaseItemDto itemDto : libraries) {
if (menu.size() == 0) {
QueryUtil.currentLibrary = itemDto;
}
if (itemDto.getCollectionType() == null || !itemDto.getCollectionType().equals("music")) continue;
menu.add(R.id.navigation_drawer_menu_category_sections, itemDto.getId().hashCode(), menu.size(), itemDto.getName());
menu.getItem(menu.size() - 1).setIcon(R.drawable.ic_album_white_24dp);
}
menu.add(R.id.navigation_drawer_menu_category_other, R.id.nav_settings, menu.size(), R.string.action_settings);
menu.getItem(menu.size() - 1).setIcon(R.drawable.ic_settings_white_24dp);
menu.add(R.id.navigation_drawer_menu_category_other, R.id.nav_about, menu.size(), R.string.action_about);
menu.getItem(menu.size() - 1).setIcon(R.drawable.ic_info_outline_white_24dp);
menu.add(R.id.navigation_drawer_menu_category_other, R.id.nav_logout, menu.size(), R.string.logout);
menu.getItem(menu.size() - 1).setIcon(R.drawable.ic_exit_to_app_white_48dp);
setUpDrawerLayout();
menu.getItem(0).setChecked(true);
if (savedInstanceState == null) {
setCurrentFragment(LibraryFragment.newInstance());
} else {
restoreCurrentFragment();
}
});
}
@Override
public void onPause() {
super.onPause();
// only override when logout selected
if (onLogout) {
overridePendingTransition(0, R.anim.fade_slow);
onLogout = false;
}
}
private void setCurrentFragment(Fragment fragment) {
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, fragment, null).commit();
currentFragment = (MainActivityFragmentCallbacks) fragment;
}
private void restoreCurrentFragment() {
currentFragment = (MainActivityFragmentCallbacks) getSupportFragmentManager().findFragmentById(R.id.fragment_container);
}
@Override
protected View createContentView() {
binding = ActivityMainDrawerLayoutBinding.inflate(getLayoutInflater());
contentBinding = ActivityMainContentBinding.inflate(getLayoutInflater());
ViewGroup drawerContent = binding.drawerContentContainer;
drawerContent.addView(wrapSlidingMusicPanel(contentBinding.getRoot()));
return binding.getRoot();
}
private void setUpNavigationView() {
int accentColor = ThemeStore.accentColor(this);
NavigationViewUtil.setItemIconColors(binding.navigationView, ATHUtil.resolveColor(this, R.attr.iconColor, ThemeStore.textColorSecondary(this)), accentColor);
NavigationViewUtil.setItemTextColors(binding.navigationView, ThemeStore.textColorPrimary(this), accentColor);
binding.navigationView.setNavigationItemSelectedListener(menuItem -> {
binding.drawerLayout.closeDrawers();
switch (menuItem.getItemId()) {
case R.id.nav_settings:
new Handler().postDelayed(() -> startActivity(new Intent(MainActivity.this, SettingsActivity.class)), 200);
break;
case R.id.nav_about:
new Handler().postDelayed(() -> startActivity(new Intent(MainActivity.this, AboutActivity.class)), 200);
break;
case R.id.nav_logout:
onLogout = true;
ConfirmLogoutDialog.create().show(getSupportFragmentManager(), "CONFIRM_LOGOUT_DIALOG");
break;
}
// only run the following code when a new library has been selected
if (menuItem.getItemId() == QueryUtil.currentLibrary.getId().hashCode()) return true;
for (BaseItemDto itemDto : libraries) {
if (menuItem.getItemId() == itemDto.getId().hashCode()) {
QueryUtil.currentLibrary = itemDto;
setCurrentFragment(LibraryFragment.newInstance());
break;
}
}
// setCheckable must be applied to the items on creation
// it also applies a tacky background color for the checked item
// this is a hack to check the current item without that
if (menuItem.getItemId() == R.id.nav_settings
|| menuItem.getItemId() == R.id.nav_about
|| menuItem.getItemId() == R.id.nav_logout) return true;
for (int i = 0; i < binding.navigationView.getMenu().size(); i++) {
if (binding.navigationView.getMenu().getItem(i) == menuItem) {
binding.navigationView.getMenu().getItem(i).setChecked(true);
} else {
binding.navigationView.getMenu().getItem(i).setChecked(false);
}
}
return true;
});
}
private void setUpDrawerLayout() {
setUpNavigationView();
}
private void updateNavigationDrawerHeader() {
if (!MusicPlayerRemote.getPlayingQueue().isEmpty()) {
Song song = MusicPlayerRemote.getCurrentSong();
if (navigationBinding == null) {
navigationBinding = NavigationDrawerHeaderBinding.inflate(getLayoutInflater());
binding.navigationView.addHeaderView(navigationBinding.getRoot());
navigationBinding.getRoot().setOnClickListener(v -> {
binding.drawerLayout.closeDrawers();
if (getPanelState() == SlidingUpPanelLayout.PanelState.COLLAPSED) {
expandPanel();
}
});
}
navigationBinding.title.setText(song.title);
navigationBinding.text.setText(MusicUtil.getSongInfoString(song));
CustomGlideRequest.Builder
.from(this, song.primary, song.blurHash)
.build().centerInside().into(navigationBinding.image);
} else if (binding.navigationView.getHeaderCount() != 0) {
binding.navigationView.removeHeaderView(navigationBinding.getRoot());
navigationBinding = null;
}
}
@Override
public void onPlayMetadataChanged() {
super.onPlayMetadataChanged();
updateNavigationDrawerHeader();
}
@Override
public void onServiceConnected() {
super.onServiceConnected();
updateNavigationDrawerHeader();
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == android.R.id.home) {
if (binding.drawerLayout.isDrawerOpen(binding.navigationView)) {
binding.drawerLayout.closeDrawer(binding.navigationView);
} else {
binding.drawerLayout.openDrawer(binding.navigationView);
}
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public boolean handleBackPress() {
if (binding.drawerLayout.isDrawerOpen(binding.navigationView)) {
binding.drawerLayout.closeDrawers();
return true;
}
return super.handleBackPress() || (currentFragment != null && currentFragment.handleBackPress());
}
@Override
public void onPanelExpanded(View view) {
super.onPanelExpanded(view);
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
}
@Override
public void onPanelCollapsed(View view) {
super.onPanelCollapsed(view);
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
}
public interface MainActivityFragmentCallbacks {
boolean handleBackPress();
}
}

View file

@ -0,0 +1,215 @@
package com.dkanada.gramophone.activities;
import android.os.Bundle;
import android.os.Handler;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.SearchView;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.adapter.SearchAdapter;
import com.dkanada.gramophone.databinding.ActivitySearchBinding;
import com.dkanada.gramophone.interfaces.MediaCallback;
import com.dkanada.gramophone.model.Album;
import com.dkanada.gramophone.model.Artist;
import com.dkanada.gramophone.model.Song;
import com.dkanada.gramophone.activities.base.AbsMusicServiceActivity;
import com.dkanada.gramophone.util.QueryUtil;
import com.dkanada.gramophone.util.Util;
import com.kabouzeid.appthemehelper.ThemeStore;
import org.jellyfin.apiclient.model.querying.ArtistsQuery;
import org.jellyfin.apiclient.model.querying.ItemQuery;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class SearchActivity extends AbsMusicServiceActivity implements SearchView.OnQueryTextListener {
public String QUERY = "query";
private ActivitySearchBinding binding;
private SearchView searchView;
private Handler handler;
private SearchAdapter adapter;
private String query;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivitySearchBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setDrawUnderStatusbar();
setStatusbarColorAuto();
setNavigationbarColorAuto();
setTaskDescriptionColorAuto();
binding.recyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter = new SearchAdapter(this, Collections.emptyList());
adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
super.onChanged();
binding.empty.setVisibility(adapter.getItemCount() < 1 ? View.VISIBLE : View.GONE);
}
});
binding.recyclerView.setAdapter(adapter);
binding.recyclerView.setOnTouchListener((v, event) -> {
hideSoftKeyboard();
return false;
});
setUpToolBar();
handler = new Handler();
if (savedInstanceState != null) {
query = savedInstanceState.getString(QUERY);
search(query);
}
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(QUERY, query);
}
private void setUpToolBar() {
binding.toolbar.setBackgroundColor(ThemeStore.primaryColor(this));
setSupportActionBar(binding.toolbar);
// noinspection ConstantConditions
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_search, menu);
final MenuItem searchItem = menu.findItem(R.id.search);
searchView = (SearchView) searchItem.getActionView();
searchView.setQueryHint(getString(R.string.action_search));
searchView.setMaxWidth(Integer.MAX_VALUE);
searchItem.expandActionView();
searchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
@Override
public boolean onMenuItemActionExpand(MenuItem item) {
return true;
}
@Override
public boolean onMenuItemActionCollapse(MenuItem item) {
onBackPressed();
return false;
}
});
searchView.setQuery(query, false);
searchView.post(() -> searchView.setOnQueryTextListener(SearchActivity.this));
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
}
return super.onOptionsItemSelected(item);
}
private void search(@NonNull String query) {
this.query = query;
ItemQuery itemQuery = new ItemQuery();
itemQuery.setSearchTerm(query);
ArtistsQuery artistsQuery = new ArtistsQuery();
artistsQuery.setSearchTerm(query);
MediaCallback callback = new MediaCallback() {
private final List<Object> data = new ArrayList<>();
@SuppressWarnings("ConstantConditions")
@Override
public void onLoadMedia(List<?> media) {
data.addAll(media);
Map<Class<?>, List<Object>> byClass = new HashMap<>();
byClass.put(Artist.class, new ArrayList<>());
byClass.put(Album.class, new ArrayList<>());
byClass.put(Song.class, new ArrayList<>());
byClass.put(Object.class, new ArrayList<>());
for (Object datum : data) {
if (byClass.containsKey(datum.getClass())) {
byClass.get(datum.getClass()).add(datum);
} else {
byClass.get(Object.class).add(datum);
}
}
Collections.sort(byClass.get(Artist.class),
(one, two) -> ((Artist) one).name.compareTo(((Artist) two).name));
Collections.sort(byClass.get(Album.class),
(one, two) -> ((Album) one).title.compareTo(((Album) two).title));
Collections.sort(byClass.get(Song.class),
(one, two) -> ((Song) one).title.compareTo(((Song) two).title));
List<Object> sortedData = byClass.get(Artist.class);
sortedData.addAll(byClass.get(Album.class));
sortedData.addAll(byClass.get(Song.class));
sortedData.addAll(byClass.get(Object.class));
adapter.swapDataSet(sortedData);
}
};
QueryUtil.getArtists(artistsQuery, callback);
QueryUtil.getItems(itemQuery, callback);
}
@Override
public boolean onQueryTextSubmit(String query) {
hideSoftKeyboard();
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
handler.removeCallbacksAndMessages(null);
handler.postDelayed(new Runnable() {
@Override
public void run() {
search(newText);
}
}, 1000);
return false;
}
private void hideSoftKeyboard() {
Util.hideSoftKeyboard(SearchActivity.this);
if (searchView != null) {
searchView.clearFocus();
}
}
}

View file

@ -0,0 +1,256 @@
package com.dkanada.gramophone.activities;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceManager;
import androidx.preference.TwoStatePreference;
import com.afollestad.materialdialogs.color.ColorChooserDialog;
import com.dkanada.gramophone.databinding.ActivitySettingsBinding;
import com.dkanada.gramophone.preferences.DirectPlayPreference;
import com.dkanada.gramophone.preferences.DirectPlayPreferenceDialog;
import com.kabouzeid.appthemehelper.ThemeStore;
import com.kabouzeid.appthemehelper.common.prefs.supportv7.ATEColorPreference;
import com.kabouzeid.appthemehelper.common.prefs.supportv7.ATEPreferenceFragmentCompat;
import com.kabouzeid.appthemehelper.util.ColorUtil;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.shortcuts.DynamicShortcutManager;
import com.dkanada.gramophone.preferences.CategoryPreference;
import com.dkanada.gramophone.preferences.CategoryPreferenceDialog;
import com.dkanada.gramophone.preferences.NowPlayingScreenPreference;
import com.dkanada.gramophone.preferences.NowPlayingScreenPreferenceDialog;
import com.dkanada.gramophone.activities.base.AbsBaseActivity;
import com.dkanada.gramophone.util.PreferenceUtil;
public class SettingsActivity extends AbsBaseActivity implements ColorChooserDialog.ColorCallback {
private ActivitySettingsBinding binding;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivitySettingsBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setDrawUnderStatusbar();
setStatusbarColorAuto();
setNavigationbarColorAuto();
setTaskDescriptionColorAuto();
binding.toolbar.setBackgroundColor(ThemeStore.primaryColor(this));
setSupportActionBar(binding.toolbar);
// noinspection ConstantConditions
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction().replace(R.id.content_frame, new SettingsFragment()).commit();
} else {
SettingsFragment frag = (SettingsFragment) getSupportFragmentManager().findFragmentById(R.id.content_frame);
if (frag != null) frag.invalidateSettings();
}
}
@Override
public void onColorSelection(@NonNull ColorChooserDialog dialog, @ColorInt int selectedColor) {
switch (dialog.getTitle()) {
case R.string.pref_title_primary_color:
ThemeStore.editTheme(this).primaryColor(selectedColor).commit();
break;
case R.string.pref_title_accent_color:
ThemeStore.editTheme(this).accentColor(selectedColor).commit();
break;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
new DynamicShortcutManager(this).updateDynamicShortcuts();
}
recreate();
}
@Override
public void onColorChooserDismissed(@NonNull ColorChooserDialog dialog) {
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
public static class SettingsFragment extends ATEPreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener {
private static void setSummary(@NonNull Preference preference) {
setSummary(preference, PreferenceManager
.getDefaultSharedPreferences(preference.getContext())
.getString(preference.getKey(), ""));
}
private static void setSummary(Preference preference, @NonNull Object value) {
String stringValue = value.toString();
if (preference instanceof ListPreference) {
ListPreference listPreference = (ListPreference) preference;
int index = listPreference.findIndexOfValue(stringValue);
preference.setSummary(index >= 0 ? listPreference.getEntries()[index] : null);
} else {
preference.setSummary(stringValue);
}
}
@Override
public void onCreatePreferences(Bundle bundle, String s) {
addPreferencesFromResource(R.xml.pref_library);
addPreferencesFromResource(R.xml.pref_interface);
addPreferencesFromResource(R.xml.pref_notification);
addPreferencesFromResource(R.xml.pref_now_playing);
addPreferencesFromResource(R.xml.pref_lock_screen);
addPreferencesFromResource(R.xml.pref_playback);
addPreferencesFromResource(R.xml.pref_cache);
}
@Nullable
@Override
public DialogFragment onCreatePreferenceDialog(Preference preference) {
if (preference instanceof NowPlayingScreenPreference) {
return NowPlayingScreenPreferenceDialog.newInstance();
} else if (preference instanceof CategoryPreference) {
return CategoryPreferenceDialog.newInstance();
} else if (preference instanceof DirectPlayPreference) {
return DirectPlayPreferenceDialog.newInstance();
}
return super.onCreatePreferenceDialog(preference);
}
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
getListView().setPadding(0, 0, 0, 0);
invalidateSettings();
PreferenceUtil.getInstance(getActivity()).registerOnSharedPreferenceChangedListener(this);
}
@Override
public void onDestroyView() {
super.onDestroyView();
PreferenceUtil.getInstance(getActivity()).unregisterOnSharedPreferenceChangedListener(this);
}
private void invalidateSettings() {
final Preference generalTheme = findPreference(PreferenceUtil.GENERAL_THEME);
setSummary(generalTheme);
generalTheme.setOnPreferenceChangeListener((preference, o) -> {
String themeName = (String) o;
setSummary(generalTheme, o);
ThemeStore.markChanged(requireActivity());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
// set the new theme so that updateAppShortcuts can pull it
requireActivity().setTheme(PreferenceUtil.getThemeResource(themeName));
new DynamicShortcutManager(getActivity()).updateDynamicShortcuts();
}
requireActivity().recreate();
return true;
});
final ATEColorPreference primaryColorPref = findPreference(PreferenceUtil.PRIMARY_COLOR);
final int primaryColor = ThemeStore.primaryColor(requireActivity());
primaryColorPref.setColor(primaryColor, ColorUtil.darkenColor(primaryColor));
primaryColorPref.setOnPreferenceClickListener(preference -> {
new ColorChooserDialog.Builder(requireActivity(), R.string.pref_title_primary_color)
.accentMode(false)
.allowUserColorInput(true)
.allowUserColorInputAlpha(false)
.preselect(primaryColor)
.show(requireActivity());
return true;
});
final ATEColorPreference accentColorPref = findPreference(PreferenceUtil.ACCENT_COLOR);
final int accentColor = ThemeStore.accentColor(requireActivity());
accentColorPref.setColor(accentColor, ColorUtil.darkenColor(accentColor));
accentColorPref.setOnPreferenceClickListener(preference -> {
new ColorChooserDialog.Builder(requireActivity(), R.string.pref_title_accent_color)
.accentMode(true)
.allowUserColorInput(true)
.allowUserColorInputAlpha(false)
.preselect(accentColor)
.show(requireActivity());
return true;
});
final TwoStatePreference classicNotification = findPreference(PreferenceUtil.CLASSIC_NOTIFICATION);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
classicNotification.setVisible(false);
} else {
classicNotification.setChecked(PreferenceUtil.getInstance(getActivity()).getClassicNotification());
classicNotification.setOnPreferenceChangeListener((preference, newValue) -> {
PreferenceUtil.getInstance(getActivity()).setClassicNotification((Boolean) newValue);
return true;
});
}
final TwoStatePreference coloredNotification = findPreference(PreferenceUtil.COLORED_NOTIFICATION);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
coloredNotification.setEnabled(PreferenceUtil.getInstance(getActivity()).getClassicNotification());
} else {
coloredNotification.setChecked(PreferenceUtil.getInstance(getActivity()).getColoredNotification());
coloredNotification.setOnPreferenceChangeListener((preference, newValue) -> {
PreferenceUtil.getInstance(getActivity()).setColoredNotification((Boolean) newValue);
return true;
});
}
final TwoStatePreference colorAppShortcuts = findPreference(PreferenceUtil.COLORED_SHORTCUTS);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) {
colorAppShortcuts.setVisible(false);
} else {
colorAppShortcuts.setChecked(PreferenceUtil.getInstance(getActivity()).getColoredShortcuts());
colorAppShortcuts.setOnPreferenceChangeListener((preference, newValue) -> {
PreferenceUtil.getInstance(getActivity()).setColoredShortcuts((Boolean) newValue);
// update app shortcuts
new DynamicShortcutManager(getActivity()).updateDynamicShortcuts();
return true;
});
}
updateNowPlayingScreenSummary();
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
switch (key) {
case PreferenceUtil.NOW_PLAYING_SCREEN:
updateNowPlayingScreenSummary();
break;
case PreferenceUtil.CLASSIC_NOTIFICATION:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
findPreference(PreferenceUtil.COLORED_NOTIFICATION).setEnabled(sharedPreferences.getBoolean(key, false));
}
break;
}
}
private void updateNowPlayingScreenSummary() {
findPreference(PreferenceUtil.NOW_PLAYING_SCREEN).setSummary(PreferenceUtil.getInstance(getActivity()).getNowPlayingScreen().titleRes);
}
}
}

View file

@ -0,0 +1,113 @@
package com.dkanada.gramophone.activities;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.PowerManager;
import android.provider.Settings;
import androidx.annotation.RequiresApi;
import com.dkanada.gramophone.App;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.activities.base.AbsBaseActivity;
import com.dkanada.gramophone.util.PreferenceUtil;
import org.jellyfin.apiclient.interaction.EmptyResponse;
import org.jellyfin.apiclient.interaction.Response;
import org.jellyfin.apiclient.model.session.ClientCapabilities;
import org.jellyfin.apiclient.model.system.SystemInfo;
public class SplashActivity extends AbsBaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
setDrawUnderStatusbar();
setStatusbarColorAuto();
setNavigationbarColorAuto();
setTaskDescriptionColorAuto();
}
@Override
public void onPause() {
super.onPause();
overridePendingTransition(0, R.anim.fade_delay);
}
@Override
protected void onResume() {
super.onResume();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && detectBatteryOptimization()) {
showBatteryOptimizationDialog();
} else {
login();
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
private boolean detectBatteryOptimization() {
String packageName = getPackageName();
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
return !pm.isIgnoringBatteryOptimizations(packageName);
}
@RequiresApi(api = Build.VERSION_CODES.M)
private void showBatteryOptimizationDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(SplashActivity.this);
builder.setMessage(R.string.battery_optimizations_message)
.setTitle(R.string.battery_optimizations_title)
.setNegativeButton(R.string.ignore, (dialog, id) -> login())
.setPositiveButton(R.string.disable, (dialog, id) -> openPowerSettings())
.show();
}
@RequiresApi(api = Build.VERSION_CODES.M)
private void openPowerSettings() {
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
startActivity(intent);
}
public void login() {
if (PreferenceUtil.getInstance(this).getToken() == null) {
launchLoginActivity();
} else {
final Context context = this;
App.getApiClient().ChangeServerLocation(PreferenceUtil.getInstance(this).getServer());
App.getApiClient().SetAuthenticationInfo(PreferenceUtil.getInstance(this).getToken(), PreferenceUtil.getInstance(this).getUser());
App.getApiClient().GetSystemInfoAsync(new Response<SystemInfo>() {
@Override
public void onResponse(SystemInfo result) {
ClientCapabilities clientCapabilities = new ClientCapabilities();
clientCapabilities.setSupportsMediaControl(true);
clientCapabilities.setSupportsPersistentIdentifier(true);
App.getApiClient().ensureWebSocket();
App.getApiClient().ReportCapabilities(clientCapabilities, new EmptyResponse());
Intent intent = new Intent(context, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
context.startActivity(intent);
}
@Override
public void onError(Exception exception) {
launchLoginActivity();
}
});
}
}
private void launchLoginActivity() {
Intent intent = new Intent(this, LoginActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
}
}

View file

@ -0,0 +1,148 @@
package com.dkanada.gramophone.activities.base;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import android.view.KeyEvent;
import android.view.View;
import com.kabouzeid.appthemehelper.ThemeStore;
import com.dkanada.gramophone.R;
import com.google.android.material.snackbar.Snackbar;
public abstract class AbsBaseActivity extends AbsThemeActivity {
public static final int PERMISSION_REQUEST = 100;
private boolean hadPermissions;
private String[] permissions;
private String permissionDeniedMessage;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
permissions = getPermissionsToRequest();
hadPermissions = hasPermissions();
setPermissionDeniedMessage(null);
}
@Override
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
if (!hasPermissions()) {
requestPermissions();
}
}
@Override
protected void onResume() {
super.onResume();
final boolean hasPermissions = hasPermissions();
if (hasPermissions != hadPermissions) {
hadPermissions = hasPermissions;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
onHasPermissionsChanged(hasPermissions);
}
}
}
protected void onHasPermissionsChanged(boolean hasPermissions) {
// implemented by sub classes
}
@Override
public boolean dispatchKeyEvent(@NonNull KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_MENU && event.getAction() == KeyEvent.ACTION_UP) {
showOverflowMenu();
return true;
}
return super.dispatchKeyEvent(event);
}
protected void showOverflowMenu() {
}
@Nullable
protected String[] getPermissionsToRequest() {
return null;
}
protected View getSnackBarContainer() {
return getWindow().getDecorView();
}
protected void setPermissionDeniedMessage(String message) {
permissionDeniedMessage = message;
}
private String getPermissionDeniedMessage() {
return permissionDeniedMessage == null ? getString(R.string.permissions_denied) : permissionDeniedMessage;
}
protected void requestPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && permissions != null) {
requestPermissions(permissions, PERMISSION_REQUEST);
}
}
protected boolean hasPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && permissions != null) {
for (String permission : permissions) {
if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
}
return true;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST) {
for (int grantResult : grantResults) {
if (grantResult != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(AbsBaseActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
// user has deny from permission dialog
Snackbar.make(getSnackBarContainer(), getPermissionDeniedMessage(),
Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.action_grant, view -> requestPermissions())
.setActionTextColor(ThemeStore.accentColor(this))
.show();
} else {
// user has deny permission and checked never show permission dialog so you can redirect to application settings page
Snackbar.make(getSnackBarContainer(), getPermissionDeniedMessage(),
Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.action_settings, view -> {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", AbsBaseActivity.this.getPackageName(), null);
intent.setData(uri);
startActivity(intent);
})
.setActionTextColor(ThemeStore.accentColor(this))
.show();
}
return;
}
}
hadPermissions = true;
onHasPermissionsChanged(true);
}
}
}

View file

@ -0,0 +1,201 @@
package com.dkanada.gramophone.activities.base;
import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.helper.MusicPlayerRemote;
import com.dkanada.gramophone.interfaces.MusicServiceEventListener;
import com.dkanada.gramophone.service.MusicService;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
public abstract class AbsMusicServiceActivity extends AbsBaseActivity implements MusicServiceEventListener {
private final List<MusicServiceEventListener> mMusicServiceEventListeners = new ArrayList<>();
private MusicPlayerRemote.ServiceToken serviceToken;
private MusicStateReceiver musicStateReceiver;
private boolean receiverRegistered;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
serviceToken = MusicPlayerRemote.bindToService(this, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
AbsMusicServiceActivity.this.onServiceConnected();
}
@Override
public void onServiceDisconnected(ComponentName name) {
AbsMusicServiceActivity.this.onServiceDisconnected();
}
});
setPermissionDeniedMessage(getString(R.string.permission_external_storage_denied));
}
@Override
protected void onDestroy() {
super.onDestroy();
MusicPlayerRemote.unbindFromService(serviceToken);
if (receiverRegistered) {
unregisterReceiver(musicStateReceiver);
receiverRegistered = false;
}
}
public void addMusicServiceEventListener(final MusicServiceEventListener listener) {
if (listener != null) {
mMusicServiceEventListeners.add(listener);
}
}
public void removeMusicServiceEventListener(final MusicServiceEventListener listener) {
if (listener != null) {
mMusicServiceEventListeners.remove(listener);
}
}
@Override
public void onServiceConnected() {
if (!receiverRegistered) {
musicStateReceiver = new MusicStateReceiver(this);
final IntentFilter filter = new IntentFilter();
filter.addAction(MusicService.STATE_CHANGED);
filter.addAction(MusicService.SHUFFLE_MODE_CHANGED);
filter.addAction(MusicService.REPEAT_MODE_CHANGED);
filter.addAction(MusicService.META_CHANGED);
filter.addAction(MusicService.QUEUE_CHANGED);
registerReceiver(musicStateReceiver, filter);
receiverRegistered = true;
}
for (MusicServiceEventListener listener : mMusicServiceEventListeners) {
if (listener != null) {
listener.onServiceConnected();
}
}
}
@Override
public void onServiceDisconnected() {
if (receiverRegistered) {
unregisterReceiver(musicStateReceiver);
receiverRegistered = false;
}
for (MusicServiceEventListener listener : mMusicServiceEventListeners) {
if (listener != null) {
listener.onServiceDisconnected();
}
}
}
@Override
public void onPlayMetadataChanged() {
for (MusicServiceEventListener listener : mMusicServiceEventListeners) {
if (listener != null) {
listener.onPlayMetadataChanged();
}
}
}
@Override
public void onQueueChanged() {
for (MusicServiceEventListener listener : mMusicServiceEventListeners) {
if (listener != null) {
listener.onQueueChanged();
}
}
}
@Override
public void onPlayStateChanged() {
for (MusicServiceEventListener listener : mMusicServiceEventListeners) {
if (listener != null) {
listener.onPlayStateChanged();
}
}
}
@Override
public void onRepeatModeChanged() {
for (MusicServiceEventListener listener : mMusicServiceEventListeners) {
if (listener != null) {
listener.onRepeatModeChanged();
}
}
}
@Override
public void onShuffleModeChanged() {
for (MusicServiceEventListener listener : mMusicServiceEventListeners) {
if (listener != null) {
listener.onShuffleModeChanged();
}
}
}
private static final class MusicStateReceiver extends BroadcastReceiver {
private final WeakReference<AbsMusicServiceActivity> reference;
public MusicStateReceiver(final AbsMusicServiceActivity activity) {
reference = new WeakReference<>(activity);
}
@Override
public void onReceive(final Context context, @NonNull final Intent intent) {
final String action = intent.getAction();
AbsMusicServiceActivity activity = reference.get();
if (activity != null && action != null) {
switch (action) {
case MusicService.META_CHANGED:
activity.onPlayMetadataChanged();
break;
case MusicService.QUEUE_CHANGED:
activity.onQueueChanged();
break;
case MusicService.STATE_CHANGED:
activity.onPlayStateChanged();
break;
case MusicService.REPEAT_MODE_CHANGED:
activity.onRepeatModeChanged();
break;
case MusicService.SHUFFLE_MODE_CHANGED:
activity.onShuffleModeChanged();
break;
}
}
}
}
@Override
protected void onHasPermissionsChanged(boolean hasPermissions) {
// TODO is this method even required
super.onHasPermissionsChanged(hasPermissions);
}
@Nullable
@Override
protected String[] getPermissionsToRequest() {
return new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE};
}
}

View file

@ -0,0 +1,297 @@
package com.dkanada.gramophone.activities.base;
import android.animation.ValueAnimator;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.PathInterpolator;
import androidx.annotation.ColorInt;
import androidx.annotation.FloatRange;
import androidx.core.graphics.ColorUtils;
import androidx.fragment.app.Fragment;
import com.dkanada.gramophone.App;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.databinding.SlidingMusicPanelLayoutBinding;
import com.dkanada.gramophone.helper.MusicPlayerRemote;
import com.dkanada.gramophone.activities.SplashActivity;
import com.dkanada.gramophone.ui.fragments.player.AbsPlayerFragment;
import com.dkanada.gramophone.ui.fragments.player.MiniPlayerFragment;
import com.dkanada.gramophone.ui.fragments.player.NowPlayingScreen;
import com.dkanada.gramophone.ui.fragments.player.card.CardPlayerFragment;
import com.dkanada.gramophone.ui.fragments.player.flat.FlatPlayerFragment;
import com.dkanada.gramophone.util.PreferenceUtil;
import com.dkanada.gramophone.util.ViewUtil;
import com.sothree.slidinguppanel.SlidingUpPanelLayout;
public abstract class AbsSlidingMusicPanelActivity extends AbsMusicServiceActivity implements SlidingUpPanelLayout.PanelSlideListener, CardPlayerFragment.Callbacks {
private SlidingMusicPanelLayoutBinding binding;
private int navigationbarColor;
private int taskColor;
private boolean lightStatusbar;
private NowPlayingScreen currentNowPlayingScreen;
private AbsPlayerFragment playerFragment;
private MiniPlayerFragment miniPlayerFragment;
private ValueAnimator navigationBarColorAnimator;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(createContentView());
// TODO use a fragment for the splash activity
if (App.getApiClient() == null) {
Intent intent = new Intent(this, SplashActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
finish();
return;
}
currentNowPlayingScreen = PreferenceUtil.getInstance(this).getNowPlayingScreen();
// must implement AbsPlayerFragment
Fragment fragment;
switch (currentNowPlayingScreen) {
case FLAT:
fragment = new FlatPlayerFragment();
break;
case CARD:
default:
fragment = new CardPlayerFragment();
break;
}
getSupportFragmentManager().beginTransaction().replace(R.id.player_fragment_container, fragment).commit();
getSupportFragmentManager().executePendingTransactions();
playerFragment = (AbsPlayerFragment) getSupportFragmentManager().findFragmentById(R.id.player_fragment_container);
miniPlayerFragment = (MiniPlayerFragment) getSupportFragmentManager().findFragmentById(R.id.mini_player_fragment);
// noinspection ConstantConditions
miniPlayerFragment.getView().setOnClickListener(v -> expandPanel());
binding.slidingLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
binding.slidingLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
switch (getPanelState()) {
case EXPANDED:
onPanelSlide(binding.slidingLayout, 1);
onPanelExpanded(binding.slidingLayout);
break;
case COLLAPSED:
onPanelCollapsed(binding.slidingLayout);
break;
default:
playerFragment.onHide();
break;
}
}
});
binding.slidingLayout.addPanelSlideListener(this);
}
@Override
protected void onResume() {
super.onResume();
if (currentNowPlayingScreen != PreferenceUtil.getInstance(this).getNowPlayingScreen()) {
postRecreate();
}
}
public void setAntiDragView(View antiDragView) {
binding.slidingLayout.setAntiDragView(antiDragView);
}
protected abstract View createContentView();
@Override
public void onServiceConnected() {
super.onServiceConnected();
// don't call hideBottomBar(true) here as it causes a bug with the SlidingUpPanelLayout
if (!MusicPlayerRemote.getPlayingQueue().isEmpty()) {
binding.slidingLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
binding.slidingLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
hideBottomBar(false);
}
});
}
}
@Override
public void onQueueChanged() {
super.onQueueChanged();
hideBottomBar(MusicPlayerRemote.getPlayingQueue().isEmpty());
}
@Override
public void onPanelSlide(View panel, @FloatRange(from = 0, to = 1) float slideOffset) {
setMiniPlayerAlphaProgress(slideOffset);
if (navigationBarColorAnimator != null) navigationBarColorAnimator.cancel();
super.setNavigationbarColor(ColorUtils.blendARGB(navigationbarColor, playerFragment.getPaletteColor(), slideOffset));
}
@Override
public void onPanelStateChanged(View panel, SlidingUpPanelLayout.PanelState previousState, SlidingUpPanelLayout.PanelState newState) {
switch (newState) {
case COLLAPSED:
onPanelCollapsed(panel);
break;
case EXPANDED:
onPanelExpanded(panel);
break;
case ANCHORED:
// this fixes a bug where the panel would get stuck for some reason
collapsePanel();
break;
}
}
public void onPanelCollapsed(View panel) {
// restore values
super.setLightStatusbar(lightStatusbar);
super.setTaskDescriptionColor(taskColor);
super.setNavigationbarColor(navigationbarColor);
playerFragment.setMenuVisibility(false);
playerFragment.setUserVisibleHint(false);
playerFragment.onHide();
}
public void onPanelExpanded(View panel) {
// setting fragments values
int playerFragmentColor = playerFragment.getPaletteColor();
super.setLightStatusbar(false);
super.setTaskDescriptionColor(playerFragmentColor);
super.setNavigationbarColor(playerFragmentColor);
playerFragment.setMenuVisibility(true);
playerFragment.setUserVisibleHint(true);
playerFragment.onShow();
}
private void setMiniPlayerAlphaProgress(@FloatRange(from = 0, to = 1) float progress) {
if (miniPlayerFragment.getView() == null) return;
float alpha = 1 - progress;
miniPlayerFragment.getView().setAlpha(alpha);
// necessary to make the views below clickable
miniPlayerFragment.getView().setVisibility(alpha == 0 ? View.GONE : View.VISIBLE);
}
public SlidingUpPanelLayout.PanelState getPanelState() {
return binding.slidingLayout.getPanelState();
}
public void collapsePanel() {
binding.slidingLayout.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
}
public void expandPanel() {
binding.slidingLayout.setPanelState(SlidingUpPanelLayout.PanelState.EXPANDED);
}
public void hideBottomBar(final boolean hide) {
if (hide) {
binding.slidingLayout.setPanelHeight(0);
collapsePanel();
} else {
binding.slidingLayout.setPanelHeight(getResources().getDimensionPixelSize(R.dimen.mini_player_height));
}
}
protected View wrapSlidingMusicPanel(View view) {
binding = SlidingMusicPanelLayoutBinding.inflate(getLayoutInflater());
ViewGroup contentContainer = binding.contentContainer;
contentContainer.addView(view);
return binding.getRoot();
}
@Override
public void onBackPressed() {
if (!handleBackPress()) super.onBackPressed();
}
public boolean handleBackPress() {
if (binding.slidingLayout.getPanelHeight() != 0 && playerFragment.onBackPressed())
return true;
if (getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED) {
collapsePanel();
return true;
}
return false;
}
@Override
public void onPaletteColorChanged() {
if (getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED) {
int playerFragmentColor = playerFragment.getPaletteColor();
super.setTaskDescriptionColor(playerFragmentColor);
animateNavigationBarColor(playerFragmentColor);
}
}
@Override
public void setLightStatusbar(boolean enabled) {
lightStatusbar = enabled;
if (getPanelState() == SlidingUpPanelLayout.PanelState.COLLAPSED) {
super.setLightStatusbar(enabled);
}
}
@Override
public void setNavigationbarColor(int color) {
this.navigationbarColor = color;
if (getPanelState() == SlidingUpPanelLayout.PanelState.COLLAPSED) {
if (navigationBarColorAnimator != null) navigationBarColorAnimator.cancel();
super.setNavigationbarColor(color);
}
}
private void animateNavigationBarColor(int color) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (navigationBarColorAnimator != null) navigationBarColorAnimator.cancel();
navigationBarColorAnimator = ValueAnimator
.ofArgb(getWindow().getNavigationBarColor(), color)
.setDuration(ViewUtil.PHONOGRAPH_ANIM_TIME);
navigationBarColorAnimator.setInterpolator(new PathInterpolator(0.4f, 0f, 1f, 1f));
navigationBarColorAnimator.addUpdateListener(animation -> AbsSlidingMusicPanelActivity.super.setNavigationbarColor((Integer) animation.getAnimatedValue()));
navigationBarColorAnimator.start();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (navigationBarColorAnimator != null) navigationBarColorAnimator.cancel(); // just in case
}
@Override
public void setTaskDescriptionColor(@ColorInt int color) {
this.taskColor = color;
if (getPanelState() == null || getPanelState() == SlidingUpPanelLayout.PanelState.COLLAPSED) {
super.setTaskDescriptionColor(color);
}
}
@Override
protected View getSnackBarContainer() {
return findViewById(R.id.content_container);
}
}

View file

@ -0,0 +1,89 @@
package com.dkanada.gramophone.activities.base;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.ColorInt;
import com.kabouzeid.appthemehelper.ATH;
import com.kabouzeid.appthemehelper.ThemeStore;
import com.kabouzeid.appthemehelper.common.ATHToolbarActivity;
import com.kabouzeid.appthemehelper.util.ColorUtil;
import com.kabouzeid.appthemehelper.util.MaterialDialogsUtil;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.util.PreferenceUtil;
import com.dkanada.gramophone.util.Util;
public abstract class AbsThemeActivity extends ATHToolbarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(PreferenceUtil.getInstance(this).getTheme());
super.onCreate(savedInstanceState);
MaterialDialogsUtil.updateMaterialDialogsThemeSingleton(this);
if (!ThemeStore.coloredNavigationBar(this)) {
ThemeStore.editTheme(this).coloredNavigationBar(true).commit();
}
}
protected void setDrawUnderStatusbar() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Util.setAllowDrawUnderStatusBar(getWindow());
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Util.setStatusBarTranslucent(getWindow());
}
}
/**
* This will set the color of the view with the id "status_bar" on KitKat and Lollipop.
* On Lollipop if no such view is found it will set the statusbar color using the native method.
*
* @param color the new statusbar color (will be shifted down on Lollipop and above)
*/
public void setStatusbarColor(int color) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
final View statusBar = getWindow().getDecorView().getRootView().findViewById(R.id.status_bar);
if (statusBar != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
statusBar.setBackgroundColor(ColorUtil.darkenColor(color));
setLightStatusbarAuto(color);
} else {
statusBar.setBackgroundColor(color);
}
} else if (Build.VERSION.SDK_INT >= 21) {
getWindow().setStatusBarColor(ColorUtil.darkenColor(color));
setLightStatusbarAuto(color);
}
}
}
public void setStatusbarColorAuto() {
// we don't want to use status bar color because we are darkening the color on our own to support KitKat
setStatusbarColor(ThemeStore.primaryColor(this));
}
public void setTaskDescriptionColor(@ColorInt int color) {
ATH.setTaskDescriptionColor(this, color);
}
public void setTaskDescriptionColorAuto() {
setTaskDescriptionColor(ThemeStore.primaryColor(this));
}
public void setNavigationbarColor(int color) {
ATH.setNavigationbarColor(this, color);
}
public void setNavigationbarColorAuto() {
setNavigationbarColor(ThemeStore.navigationBarColor(this));
}
public void setLightStatusbar(boolean enabled) {
ATH.setLightStatusbar(this, enabled);
}
public void setLightStatusbarAuto(int bgColor) {
setLightStatusbar(ColorUtil.isColorLight(bgColor));
}
}

View file

@ -0,0 +1,270 @@
package com.dkanada.gramophone.activities.details;
import android.graphics.PorterDuff;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.afollestad.materialcab.MaterialCab;
import com.afollestad.materialdialogs.util.DialogUtils;
import com.dkanada.gramophone.databinding.ActivityAlbumDetailBinding;
import com.google.android.material.appbar.AppBarLayout;
import com.kabouzeid.appthemehelper.util.ColorUtil;
import com.kabouzeid.appthemehelper.util.MaterialValueHelper;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.adapter.song.AlbumSongAdapter;
import com.dkanada.gramophone.dialogs.AddToPlaylistDialog;
import com.dkanada.gramophone.dialogs.SleepTimerDialog;
import com.dkanada.gramophone.glide.CustomGlideRequest;
import com.dkanada.gramophone.glide.CustomPaletteTarget;
import com.dkanada.gramophone.helper.MusicPlayerRemote;
import com.dkanada.gramophone.interfaces.CabHolder;
import com.dkanada.gramophone.interfaces.MediaCallback;
import com.dkanada.gramophone.interfaces.PaletteColorHolder;
import com.dkanada.gramophone.model.Album;
import com.dkanada.gramophone.model.Artist;
import com.dkanada.gramophone.model.Song;
import com.dkanada.gramophone.activities.base.AbsSlidingMusicPanelActivity;
import com.dkanada.gramophone.util.MusicUtil;
import com.dkanada.gramophone.util.NavigationUtil;
import com.dkanada.gramophone.util.ThemeUtil;
import com.dkanada.gramophone.util.QueryUtil;
import org.jellyfin.apiclient.model.querying.ItemQuery;
import java.util.List;
public class AlbumDetailActivity extends AbsSlidingMusicPanelActivity implements PaletteColorHolder, CabHolder, AppBarLayout.OnOffsetChangedListener {
public static final String EXTRA_ALBUM = "extra_album";
private ActivityAlbumDetailBinding binding;
private MaterialCab cab;
private int headerViewHeight;
private int toolbarColor;
private Album album;
private AlbumSongAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setDrawUnderStatusbar();
setUpObservableListViewParams();
setUpToolbar();
setUpViews();
Album album = getIntent().getExtras().getParcelable(EXTRA_ALBUM);
loadAlbumCover(album);
setAlbum(album);
ItemQuery query = new ItemQuery();
query.setParentId(album.id);
query.setSortBy(new String[]{"ParentIndexNumber", "IndexNumber"});
QueryUtil.getSongs(query, new MediaCallback() {
@Override
public void onLoadMedia(List<?> media) {
album.songs = (List<Song>) media;
setAlbum(album);
}
});
}
@Override
public void onOffsetChanged (AppBarLayout appBarLayout, int verticalOffset) {
float headerAlpha = Math.max(0, Math.min(1, 1 + (2 * (float) verticalOffset / headerViewHeight)));
binding.header.setAlpha(headerAlpha);
}
@Override
protected View createContentView() {
binding = ActivityAlbumDetailBinding.inflate(getLayoutInflater());
return wrapSlidingMusicPanel(binding.getRoot());
}
private void setUpObservableListViewParams() {
headerViewHeight = getResources().getDimensionPixelSize(R.dimen.detail_header_height);
}
private void setUpViews() {
setUpRecyclerView();
setUpSongsAdapter();
binding.artistText.setOnClickListener(v -> {
if (album != null) {
NavigationUtil.goToArtist(AlbumDetailActivity.this, new Artist(album));
}
});
setColors(DialogUtils.resolveColor(this, R.attr.defaultFooterColor));
}
private void loadAlbumCover(Album album) {
CustomGlideRequest.Builder
.from(this, album.primary, album.blurHash)
.palette().build().dontAnimate()
.into(new CustomPaletteTarget(binding.image) {
@Override
public void onColorReady(int color) {
setColors(color);
}
});
}
private void setColors(int color) {
toolbarColor = color;
binding.appBarLayout.setBackgroundColor(color);
setNavigationbarColor(color);
setTaskDescriptionColor(color);
binding.toolbar.setBackgroundColor(color);
// needed to auto readjust the toolbar content color
setSupportActionBar(binding.toolbar);
setStatusbarColor(color);
int secondaryTextColor = MaterialValueHelper.getSecondaryTextColor(this, ColorUtil.isColorLight(color));
binding.artistIcon.setColorFilter(secondaryTextColor, PorterDuff.Mode.SRC_IN);
binding.durationIcon.setColorFilter(secondaryTextColor, PorterDuff.Mode.SRC_IN);
binding.songCountIcon.setColorFilter(secondaryTextColor, PorterDuff.Mode.SRC_IN);
binding.albumYearIcon.setColorFilter(secondaryTextColor, PorterDuff.Mode.SRC_IN);
binding.artistText.setTextColor(MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(color)));
binding.durationText.setTextColor(secondaryTextColor);
binding.songCountText.setTextColor(secondaryTextColor);
binding.albumYearText.setTextColor(secondaryTextColor);
}
@Override
public int getPaletteColor() {
return toolbarColor;
}
private void setUpRecyclerView() {
binding.appBarLayout.addOnOffsetChangedListener(this);
}
private void setUpToolbar() {
setSupportActionBar(binding.toolbar);
// noinspection ConstantConditions
getSupportActionBar().setTitle(null);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
private void setUpSongsAdapter() {
adapter = new AlbumSongAdapter(this, getAlbum().songs, R.layout.item_list, false, this);
binding.list.setLayoutManager(new GridLayoutManager(this, 1));
binding.list.setAdapter(adapter);
adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
super.onChanged();
if (adapter.getItemCount() == 0) finish();
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_album_detail, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
int id = item.getItemId();
final List<Song> songs = adapter.getDataSet();
switch (id) {
case R.id.action_sleep_timer:
new SleepTimerDialog().show(getSupportFragmentManager(), "SET_SLEEP_TIMER");
return true;
case R.id.action_shuffle_album:
MusicPlayerRemote.openAndShuffleQueue(songs, true);
return true;
case R.id.action_play_next:
MusicPlayerRemote.playNext(songs);
return true;
case R.id.action_add_to_queue:
MusicPlayerRemote.enqueue(songs);
return true;
case R.id.action_add_to_playlist:
AddToPlaylistDialog.create(songs).show(getSupportFragmentManager(), "ADD_PLAYLIST");
return true;
case android.R.id.home:
super.onBackPressed();
return true;
case R.id.action_go_to_artist:
NavigationUtil.goToArtist(this, new Artist(album));
return true;
}
return super.onOptionsItemSelected(item);
}
@NonNull
@Override
public MaterialCab openCab(int menuRes, @NonNull final MaterialCab.Callback callback) {
if (cab != null && cab.isActive()) cab.finish();
cab = new MaterialCab(this, R.id.cab_stub)
.setMenu(menuRes)
.setCloseDrawableRes(R.drawable.ic_close_white_24dp)
.setBackgroundColor(ThemeUtil.shiftBackgroundColorForLightText(getPaletteColor()))
.start(new MaterialCab.Callback() {
@Override
public boolean onCabCreated(MaterialCab materialCab, Menu menu) {
return callback.onCabCreated(materialCab, menu);
}
@Override
public boolean onCabItemClicked(MenuItem menuItem) {
return callback.onCabItemClicked(menuItem);
}
@Override
public boolean onCabFinished(MaterialCab materialCab) {
return callback.onCabFinished(materialCab);
}
});
return cab;
}
@Override
public void onBackPressed() {
if (cab != null && cab.isActive()) {
cab.finish();
} else {
binding.list.stopScroll();
super.onBackPressed();
}
}
@Override
public void setStatusbarColor(int color) {
super.setStatusbarColor(color);
setLightStatusbar(false);
}
private void setAlbum(Album album) {
this.album = album;
getSupportActionBar().setTitle(album.title);
binding.artistText.setText(album.artistName);
binding.songCountText.setText(MusicUtil.getSongCountString(this, album.songs.size()));
binding.durationText.setText(MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(this, album.songs)));
binding.albumYearText.setText(MusicUtil.getYearString(album.year));
if (album.songs.size() != 0) adapter.swapDataSet(album.songs);
}
private Album getAlbum() {
if (album == null) album = new Album();
return album;
}
}

View file

@ -0,0 +1,297 @@
package com.dkanada.gramophone.activities.details;
import android.graphics.PorterDuff;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.afollestad.materialcab.MaterialCab;
import com.afollestad.materialdialogs.util.DialogUtils;
import com.dkanada.gramophone.adapter.song.SongAdapter;
import com.dkanada.gramophone.databinding.ActivityArtistDetailBinding;
import com.google.android.material.appbar.AppBarLayout;
import com.kabouzeid.appthemehelper.util.ColorUtil;
import com.kabouzeid.appthemehelper.util.MaterialValueHelper;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.adapter.album.HorizontalAlbumAdapter;
import com.dkanada.gramophone.dialogs.AddToPlaylistDialog;
import com.dkanada.gramophone.dialogs.SleepTimerDialog;
import com.dkanada.gramophone.glide.CustomGlideRequest;
import com.dkanada.gramophone.glide.CustomPaletteTarget;
import com.dkanada.gramophone.helper.MusicPlayerRemote;
import com.dkanada.gramophone.interfaces.CabHolder;
import com.dkanada.gramophone.interfaces.MediaCallback;
import com.dkanada.gramophone.interfaces.PaletteColorHolder;
import com.dkanada.gramophone.model.Album;
import com.dkanada.gramophone.model.Artist;
import com.dkanada.gramophone.model.Song;
import com.dkanada.gramophone.activities.base.AbsSlidingMusicPanelActivity;
import com.dkanada.gramophone.util.MusicUtil;
import com.dkanada.gramophone.util.ThemeUtil;
import com.dkanada.gramophone.util.PreferenceUtil;
import com.dkanada.gramophone.util.QueryUtil;
import org.jellyfin.apiclient.model.querying.ItemQuery;
import java.util.List;
public class ArtistDetailActivity extends AbsSlidingMusicPanelActivity implements PaletteColorHolder, CabHolder, AppBarLayout.OnOffsetChangedListener {
public static final String EXTRA_ARTIST = "extra_artist";
private ActivityArtistDetailBinding binding;
private MaterialCab cab;
private int headerViewHeight;
private int toolbarColor;
private Artist artist;
private HorizontalAlbumAdapter albumAdapter;
private SongAdapter songAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// must be loaded before album adapter
usePalette = PreferenceUtil.getInstance(this).getAlbumArtistColoredFooters();
setDrawUnderStatusbar();
setUpObservableListViewParams();
setUpToolbar();
setUpViews();
Artist artist = getIntent().getExtras().getParcelable(EXTRA_ARTIST);
loadArtistImage(artist);
setArtist(artist);
ItemQuery albums = new ItemQuery();
albums.setArtistIds(new String[]{artist.id});
QueryUtil.getAlbums(albums, new MediaCallback() {
@Override
public void onLoadMedia(List<?> media) {
artist.albums = (List<Album>) media;
setArtist(artist);
}
});
ItemQuery songs = new ItemQuery();
songs.setArtistIds(new String[]{artist.id});
QueryUtil.getSongs(songs, new MediaCallback() {
@Override
public void onLoadMedia(List<?> media) {
artist.songs = (List<Song>) media;
setArtist(artist);
}
});
}
@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
float headerAlpha = Math.max(0, Math.min(1, 1 + (2 * (float) verticalOffset / headerViewHeight)));
binding.header.setAlpha(headerAlpha);
}
@Override
protected View createContentView() {
binding = ActivityArtistDetailBinding.inflate(getLayoutInflater());
return wrapSlidingMusicPanel(binding.getRoot());
}
private boolean usePalette;
private void setUpObservableListViewParams() {
headerViewHeight = getResources().getDimensionPixelSize(R.dimen.detail_header_height);
}
private void setUpViews() {
setUpSongListView();
setUpAlbumRecyclerView();
setColors(DialogUtils.resolveColor(this, R.attr.defaultFooterColor));
}
private void setUpSongListView() {
binding.appBarLayout.addOnOffsetChangedListener(this);
songAdapter = new SongAdapter(this, getArtist().songs, R.layout.item_list, false, this);
binding.songs.setLayoutManager(new GridLayoutManager(this, 1));
binding.songs.setAdapter(songAdapter);
binding.scrollView.setRecyclerView(binding.songs);
}
private void setUpAlbumRecyclerView() {
binding.albums.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
albumAdapter = new HorizontalAlbumAdapter(this, getArtist().albums, usePalette, this);
binding.albums.setAdapter(albumAdapter);
// NestedScrollView will ignore horizontal RecyclerView without this line
binding.albums.setNestedScrollingEnabled(false);
albumAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
super.onChanged();
if (albumAdapter.getItemCount() == 0) finish();
}
});
}
protected void setUsePalette(boolean usePalette) {
albumAdapter.usePalette(usePalette);
PreferenceUtil.getInstance(this).setAlbumArtistColoredFooters(usePalette);
this.usePalette = usePalette;
}
private void loadArtistImage(Artist artist) {
CustomGlideRequest.Builder
.from(this, artist.primary, artist.blurHash)
.palette().build().dontAnimate()
.into(new CustomPaletteTarget(binding.image) {
@Override
public void onColorReady(int color) {
setColors(color);
}
});
}
@Override
public int getPaletteColor() {
return toolbarColor;
}
private void setColors(int color) {
toolbarColor = color;
binding.appBarLayout.setBackgroundColor(color);
setNavigationbarColor(color);
setTaskDescriptionColor(color);
binding.toolbar.setBackgroundColor(color);
// needed to auto readjust the toolbar content color
setSupportActionBar(binding.toolbar);
setStatusbarColor(color);
int secondaryTextColor = MaterialValueHelper.getSecondaryTextColor(this, ColorUtil.isColorLight(color));
binding.durationIcon.setColorFilter(secondaryTextColor, PorterDuff.Mode.SRC_IN);
binding.songCountIcon.setColorFilter(secondaryTextColor, PorterDuff.Mode.SRC_IN);
binding.albumCountIcon.setColorFilter(secondaryTextColor, PorterDuff.Mode.SRC_IN);
binding.durationText.setTextColor(secondaryTextColor);
binding.songCountText.setTextColor(secondaryTextColor);
binding.albumCountText.setTextColor(secondaryTextColor);
}
private void setUpToolbar() {
setSupportActionBar(binding.toolbar);
// noinspection ConstantConditions
getSupportActionBar().setTitle(null);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_artist_detail, menu);
menu.findItem(R.id.action_colored_footers).setChecked(usePalette);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
int id = item.getItemId();
final List<Song> songs = songAdapter.getDataSet();
switch (id) {
case R.id.action_sleep_timer:
new SleepTimerDialog().show(getSupportFragmentManager(), "SET_SLEEP_TIMER");
return true;
case R.id.action_shuffle_artist:
MusicPlayerRemote.openAndShuffleQueue(songs, true);
return true;
case R.id.action_play_next:
MusicPlayerRemote.playNext(songs);
return true;
case R.id.action_add_to_queue:
MusicPlayerRemote.enqueue(songs);
return true;
case R.id.action_add_to_playlist:
AddToPlaylistDialog.create(songs).show(getSupportFragmentManager(), "ADD_PLAYLIST");
return true;
case android.R.id.home:
super.onBackPressed();
return true;
case R.id.action_colored_footers:
item.setChecked(!item.isChecked());
setUsePalette(item.isChecked());
return true;
}
return super.onOptionsItemSelected(item);
}
@NonNull
@Override
public MaterialCab openCab(int menuRes, @NonNull final MaterialCab.Callback callback) {
if (cab != null && cab.isActive()) cab.finish();
cab = new MaterialCab(this, R.id.cab_stub)
.setMenu(menuRes)
.setCloseDrawableRes(R.drawable.ic_close_white_24dp)
.setBackgroundColor(ThemeUtil.shiftBackgroundColorForLightText(getPaletteColor()))
.start(new MaterialCab.Callback() {
@Override
public boolean onCabCreated(MaterialCab materialCab, Menu menu) {
return callback.onCabCreated(materialCab, menu);
}
@Override
public boolean onCabItemClicked(MenuItem menuItem) {
return callback.onCabItemClicked(menuItem);
}
@Override
public boolean onCabFinished(MaterialCab materialCab) {
return callback.onCabFinished(materialCab);
}
});
return cab;
}
@Override
public void onBackPressed() {
if (cab != null && cab.isActive()) {
cab.finish();
} else {
binding.albums.stopScroll();
super.onBackPressed();
}
}
@Override
public void setStatusbarColor(int color) {
super.setStatusbarColor(color);
setLightStatusbar(false);
}
private void setArtist(Artist artist) {
this.artist = artist;
getSupportActionBar().setTitle(artist.name);
binding.songCountText.setText(MusicUtil.getSongCountString(this, artist.songs.size()));
binding.albumCountText.setText(MusicUtil.getAlbumCountString(this, artist.albums.size()));
binding.durationText.setText(MusicUtil.getReadableDurationString(MusicUtil.getTotalDuration(this, artist.songs)));
if (artist.songs.size() != 0) songAdapter.swapDataSet(artist.songs);
if (artist.albums.size() != 0) albumAdapter.swapDataSet(artist.albums);
}
private Artist getArtist() {
if (artist == null) artist = new Artist();
return artist;
}
}

View file

@ -0,0 +1,162 @@
package com.dkanada.gramophone.activities.details;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.afollestad.materialcab.MaterialCab;
import com.dkanada.gramophone.databinding.ActivityGenreDetailBinding;
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils;
import com.kabouzeid.appthemehelper.ThemeStore;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.adapter.song.SongAdapter;
import com.dkanada.gramophone.helper.MusicPlayerRemote;
import com.dkanada.gramophone.interfaces.CabHolder;
import com.dkanada.gramophone.interfaces.MediaCallback;
import com.dkanada.gramophone.model.Genre;
import com.dkanada.gramophone.model.Song;
import com.dkanada.gramophone.activities.base.AbsSlidingMusicPanelActivity;
import com.dkanada.gramophone.util.ThemeUtil;
import com.dkanada.gramophone.util.QueryUtil;
import com.dkanada.gramophone.util.ViewUtil;
import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView;
import org.jellyfin.apiclient.model.querying.ItemQuery;
import java.util.ArrayList;
import java.util.List;
public class GenreDetailActivity extends AbsSlidingMusicPanelActivity implements CabHolder {
public static final String EXTRA_GENRE = "extra_genre";
private ActivityGenreDetailBinding binding;
private Genre genre;
private MaterialCab cab;
private SongAdapter adapter;
private RecyclerView.Adapter wrappedAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setDrawUnderStatusbar();
setStatusbarColorAuto();
setNavigationbarColorAuto();
setTaskDescriptionColorAuto();
genre = getIntent().getExtras().getParcelable(EXTRA_GENRE);
setUpRecyclerView();
setUpToolBar();
ItemQuery query = new ItemQuery();
query.setGenreIds(new String[]{genre.id});
QueryUtil.getSongs(query, new MediaCallback() {
@Override
public void onLoadMedia(List<?> media) {
adapter.getDataSet().addAll((List<Song>) media);
adapter.notifyDataSetChanged();
}
});
}
@Override
protected View createContentView() {
binding = ActivityGenreDetailBinding.inflate(getLayoutInflater());
return wrapSlidingMusicPanel(binding.getRoot());
}
private void setUpRecyclerView() {
ViewUtil.setUpFastScrollRecyclerViewColor(this, ((FastScrollRecyclerView) binding.recyclerView), ThemeStore.accentColor(this));
binding.recyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter = new SongAdapter(this, new ArrayList<>(), R.layout.item_list, false, this);
binding.recyclerView.setAdapter(adapter);
adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
super.onChanged();
checkIsEmpty();
}
});
}
private void setUpToolBar() {
binding.toolbar.setBackgroundColor(ThemeStore.primaryColor(this));
setSupportActionBar(binding.toolbar);
// noinspection ConstantConditions
getSupportActionBar().setTitle(genre.name);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_genre_detail, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
int id = item.getItemId();
switch (id) {
case R.id.action_shuffle_genre:
MusicPlayerRemote.openAndShuffleQueue(adapter.getDataSet(), true);
return true;
case android.R.id.home:
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
@NonNull
@Override
public MaterialCab openCab(final int menu, final MaterialCab.Callback callback) {
if (cab != null && cab.isActive()) cab.finish();
cab = new MaterialCab(this, R.id.cab_stub)
.setMenu(menu)
.setCloseDrawableRes(R.drawable.ic_close_white_24dp)
.setBackgroundColor(ThemeUtil.shiftBackgroundColorForLightText(ThemeStore.primaryColor(this)))
.start(callback);
return cab;
}
@Override
public void onBackPressed() {
if (cab != null && cab.isActive()) cab.finish();
else {
binding.recyclerView.stopScroll();
super.onBackPressed();
}
}
private void checkIsEmpty() {
binding.empty.setVisibility(adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
}
@Override
protected void onDestroy() {
binding.recyclerView.setAdapter(null);
if (wrappedAdapter != null) {
WrapperAdapterUtils.releaseAll(wrappedAdapter);
wrappedAdapter = null;
}
adapter = null;
super.onDestroy();
}
}

View file

@ -0,0 +1,203 @@
package com.dkanada.gramophone.activities.details;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.afollestad.materialcab.MaterialCab;
import com.dkanada.gramophone.databinding.ActivityPlaylistDetailBinding;
import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.animator.RefactoredDefaultItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager;
import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils;
import com.kabouzeid.appthemehelper.ThemeStore;
import com.dkanada.gramophone.R;
import com.dkanada.gramophone.adapter.song.OrderablePlaylistSongAdapter;
import com.dkanada.gramophone.adapter.song.SongAdapter;
import com.dkanada.gramophone.helper.MusicPlayerRemote;
import com.dkanada.gramophone.helper.menu.PlaylistMenuHelper;
import com.dkanada.gramophone.interfaces.CabHolder;
import com.dkanada.gramophone.interfaces.MediaCallback;
import com.dkanada.gramophone.model.Playlist;
import com.dkanada.gramophone.model.PlaylistSong;
import com.dkanada.gramophone.model.Song;
import com.dkanada.gramophone.activities.base.AbsSlidingMusicPanelActivity;
import com.dkanada.gramophone.util.ThemeUtil;
import com.dkanada.gramophone.util.PlaylistUtil;
import com.dkanada.gramophone.util.ViewUtil;
import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView;
import org.jellyfin.apiclient.model.playlists.PlaylistItemQuery;
import java.util.ArrayList;
import java.util.List;
public class PlaylistDetailActivity extends AbsSlidingMusicPanelActivity implements CabHolder {
public static String EXTRA_PLAYLIST = "extra_playlist";
private ActivityPlaylistDetailBinding binding;
private Playlist playlist;
private MaterialCab cab;
private SongAdapter adapter;
private RecyclerView.Adapter wrappedAdapter;
private RecyclerViewDragDropManager recyclerViewDragDropManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setDrawUnderStatusbar();
setStatusbarColorAuto();
setNavigationbarColorAuto();
setTaskDescriptionColorAuto();
playlist = getIntent().getExtras().getParcelable(EXTRA_PLAYLIST);
setUpRecyclerView();
setUpToolbar();
PlaylistItemQuery query = new PlaylistItemQuery();
query.setId(playlist.id);
PlaylistUtil.getPlaylist(query, new MediaCallback() {
@Override
public void onLoadMedia(List<?> media) {
adapter.getDataSet().addAll((List<PlaylistSong>) media);
adapter.notifyDataSetChanged();
}
});
}
@Override
protected View createContentView() {
binding = ActivityPlaylistDetailBinding.inflate(getLayoutInflater());
return wrapSlidingMusicPanel(binding.getRoot());
}
private void setUpRecyclerView() {
ViewUtil.setUpFastScrollRecyclerViewColor(this, ((FastScrollRecyclerView) binding.recyclerView), ThemeStore.accentColor(this));
binding.recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerViewDragDropManager = new RecyclerViewDragDropManager();
final GeneralItemAnimator animator = new RefactoredDefaultItemAnimator();
adapter = new OrderablePlaylistSongAdapter(this, new ArrayList<>(), R.layout.item_list, false, this, (fromPosition, toPosition) -> {
PlaylistUtil.moveItem(playlist.id, (PlaylistSong) adapter.getDataSet().get(fromPosition), toPosition);
Song song = adapter.getDataSet().remove(fromPosition);
adapter.getDataSet().add(toPosition, song);
adapter.notifyItemMoved(fromPosition, toPosition);
});
wrappedAdapter = recyclerViewDragDropManager.createWrappedAdapter(adapter);
binding.recyclerView.setAdapter(wrappedAdapter);
binding.recyclerView.setItemAnimator(animator);
recyclerViewDragDropManager.attachRecyclerView(binding.recyclerView);
adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
super.onChanged();
checkIsEmpty();
}
});
}
private void setUpToolbar() {
binding.toolbar.setBackgroundColor(ThemeStore.primaryColor(this));
setSupportActionBar(binding.toolbar);
// noinspection ConstantConditions
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setToolbarTitle(playlist.name);
}
private void setToolbarTitle(String title) {
// noinspection ConstantConditions
getSupportActionBar().setTitle(title);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_playlist_detail, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
int id = item.getItemId();
switch (id) {
case R.id.action_shuffle_playlist:
MusicPlayerRemote.openAndShuffleQueue(adapter.getDataSet(), true);
return true;
case android.R.id.home:
onBackPressed();
return true;
}
return PlaylistMenuHelper.handleMenuClick(this, playlist, item);
}
@NonNull
@Override
public MaterialCab openCab(final int menu, final MaterialCab.Callback callback) {
if (cab != null && cab.isActive()) cab.finish();
cab = new MaterialCab(this, R.id.cab_stub)
.setMenu(menu)
.setCloseDrawableRes(R.drawable.ic_close_white_24dp)
.setBackgroundColor(ThemeUtil.shiftBackgroundColorForLightText(ThemeStore.primaryColor(this)))
.start(callback);
return cab;
}
@Override
public void onBackPressed() {
if (cab != null && cab.isActive()) {
cab.finish();
} else {
binding.recyclerView.stopScroll();
super.onBackPressed();
}
}
private void checkIsEmpty() {
binding.empty.setVisibility(adapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
}
@Override
public void onPause() {
if (recyclerViewDragDropManager != null) {
recyclerViewDragDropManager.cancelDrag();
}
super.onPause();
}
@Override
protected void onDestroy() {
if (recyclerViewDragDropManager != null) {
recyclerViewDragDropManager.release();
recyclerViewDragDropManager = null;
}
binding.recyclerView.setItemAnimator(null);
binding.recyclerView.setAdapter(null);
if (wrappedAdapter != null) {
WrapperAdapterUtils.releaseAll(wrappedAdapter);
wrappedAdapter = null;
}
adapter = null;
super.onDestroy();
}
}