diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 2068d88..0000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -YaMSD \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 2ab1e27..cfe4315 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -6,19 +6,13 @@ - + diff --git a/APKs/yamsd.apk b/APKs/yamsd.apk index e54986a..f4020c7 100644 Binary files a/APKs/yamsd.apk and b/APKs/yamsd.apk differ diff --git a/app/build.gradle b/app/build.gradle index a0866c0..e799d52 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -28,6 +28,10 @@ dependencies { testCompile 'org.mockito:mockito-core:1.10.19' testCompile 'org.json:json:20140107' - compile 'com.android.support:appcompat-v7:23.2.1' - compile 'com.android.support:design:23.2.1' + + compile 'com.android.support:appcompat-v7:23.4.0' + compile 'com.android.support:design:23.4.0' + compile 'com.squareup.picasso:picasso:2.5.2' + compile 'com.android.support:support-v4:23.4.0' + compile 'com.google.guava:guava:19.0' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ddf3bce..7c19d2b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,6 +4,7 @@ + - - - diff --git a/app/src/main/java/com/example/android/yamsd/AboutFragment.java b/app/src/main/java/com/example/android/yamsd/AboutFragment.java new file mode 100644 index 0000000..7a9d4d7 --- /dev/null +++ b/app/src/main/java/com/example/android/yamsd/AboutFragment.java @@ -0,0 +1,97 @@ +package com.example.android.yamsd; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v7.app.AppCompatActivity; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; + +import static com.google.common.base.Preconditions.checkNotNull; + + +public class AboutFragment extends Fragment { + + public AboutFragment() { + } + + public static AboutFragment newInstance() { + return new AboutFragment(); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_refresh: + CacheAndListBuffer + .getCacheAndListBuffer( + getActivity() + ) + .updateArtists(true); + return true; + case R.id.action_about: + AboutFragment aboutFragment = + AboutFragment.newInstance(); + + android.support.v4.app.FragmentTransaction fragmentTransaction = + getFragmentManager().beginTransaction(); + + fragmentTransaction.replace( + R.id.fragment_container, + aboutFragment + ); + fragmentTransaction.addToBackStack(null); + fragmentTransaction.commit(); + + return true; + case R.id.action_feedback: + EmailSender.sendMessage(getContext()); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + View rootView = inflater.inflate(R.layout.fragment_about, container, false); + View sendEmailButton = + rootView.findViewById(R.id.SEND_EMAIL_BUTTON); + + sendEmailButton.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + EmailSender.sendMessage(getContext()); + } + } + ); + return rootView; + + } + + + @Override + public void onResume() { + super.onResume(); + checkNotNull(((AppCompatActivity) getActivity()) + .getSupportActionBar()) + .setTitle("О приложении"); + } +} diff --git a/app/src/main/java/com/example/android/yamsd/ArtistActivity.java b/app/src/main/java/com/example/android/yamsd/ArtistActivity.java deleted file mode 100644 index 00dd0e9..0000000 --- a/app/src/main/java/com/example/android/yamsd/ArtistActivity.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.example.android.yamsd; - -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; -import android.util.Log; - -/* - * Описание Activity, отвечающей за вывод информации об отдельном исполнителе - */ -public class ArtistActivity extends AppCompatActivity { - - private final String LOG_TAG = getClass().getSimpleName(); - - - @Override - protected void onCreate(Bundle savedInstanceState) { - try { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_artist); - Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setTitle(getIntent().getStringExtra("name")); - } catch (NullPointerException e) { - Log.e(LOG_TAG, "Null pointer: " + e); - } - } - -} diff --git a/app/src/main/java/com/example/android/yamsd/ArtistActivityFragment.java b/app/src/main/java/com/example/android/yamsd/ArtistActivityFragment.java index 3dc9d69..c5bf4ca 100644 --- a/app/src/main/java/com/example/android/yamsd/ArtistActivityFragment.java +++ b/app/src/main/java/com/example/android/yamsd/ArtistActivityFragment.java @@ -1,19 +1,20 @@ package com.example.android.yamsd; -import android.content.Intent; -import android.graphics.Bitmap; -import android.os.AsyncTask; import android.os.Bundle; import android.support.v4.app.Fragment; -import android.util.Log; +import android.support.v7.app.AppCompatActivity; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import com.example.android.yamsd.ArtistsData.Artist; +import com.squareup.picasso.Picasso; + +import static com.google.common.base.Preconditions.checkNotNull; -import java.io.IOException; -import java.net.URL; /** * Фрагмент с информацией об одном артисте. */ @@ -22,41 +23,96 @@ public class ArtistActivityFragment extends Fragment { private String LOG_TAG = getClass().getSimpleName(); + Artist artist; + + + public static ArtistActivityFragment newInstance(int index) { + ArtistActivityFragment artistActivityFragment = + new ArtistActivityFragment(); + + Bundle args = new Bundle(); + args.putInt("index", index); + artistActivityFragment.setArguments(args); + + return artistActivityFragment; + } + public ArtistActivityFragment() { } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + } - try { - Intent artistInfoIntent = getActivity().getIntent(); - Artist artist = - new Artist( - artistInfoIntent.getStringExtra("name"), + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + } - artistInfoIntent.getStringArrayExtra("genres"), - artistInfoIntent.getIntExtra("albums", 0), - artistInfoIntent.getIntExtra("tracks", 0), + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_refresh: + CacheAndListBuffer + .getCacheAndListBuffer( + getActivity() + ) + .updateArtists(true); + return true; + case R.id.action_about: + AboutFragment aboutFragment = + AboutFragment.newInstance(); + + getFragmentManager() + .beginTransaction() + .replace( + R.id.fragment_container, + aboutFragment + ) + .addToBackStack(null) + .commit(); + + return true; + + case R.id.action_feedback: + EmailSender.sendMessage(getContext()); + return true; + + default: + return super.onOptionsItemSelected(item); + } + } - artistInfoIntent.getStringExtra("description"), - artistInfoIntent.getStringExtra("bigCover") - ); + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { - return loadArtistData(artist, inflater).getRootView(); - } catch (NullPointerException e) { - Log.e( - LOG_TAG, - "Null Pointer Exception while creating view: " + e - ); - } + int artistInfoIndex = getArguments().getInt("index", 0); + artist = + CacheAndListBuffer + .getCacheAndListBuffer( + getActivity() + ) + .getArtists() + .get(artistInfoIndex); - return null; + return loadArtistData(artist, inflater).getRootView(); + } + + + @Override + public void onResume() { + super.onResume(); + checkNotNull(((AppCompatActivity) getActivity()) + .getSupportActionBar()) + .setTitle(artist.getName()); } @@ -70,48 +126,14 @@ private ArtistViewHolder loadArtistData( "Artist", artist ); - new loadBigCoverTask(viewHolder).execute(artist); + + Picasso + .with(getContext()) + .load(artist.getBigCoverUrlString()) + .into(viewHolder.cover); return viewHolder; } - private class loadBigCoverTask - extends AsyncTask { - private String LOG_TAG = getClass().getSimpleName(); - - Artist artist; - ArtistViewHolder viewHolder; - - - public loadBigCoverTask(ArtistViewHolder viewHolder) { - this.viewHolder = viewHolder; - } - - - @Override - protected Bitmap doInBackground(Artist... params) { - try { - artist = params[0]; - - return (Bitmap) Utility.downloadData( - new URL(params[0].getBigCoverUrlString()), - "bitmap" - ); - - } catch (IOException e) { - Log.e(LOG_TAG, "Error while loading image: " + e); - } catch (NullPointerException e){ - Log.e(LOG_TAG, "Null pointer while loading image: " + e); - } - - return null; - } - - - @Override - protected void onPostExecute(Bitmap bitmap) { - viewHolder.setCoverBitmap(bitmap); - } - } } diff --git a/app/src/main/java/com/example/android/yamsd/ArtistViewHolder.java b/app/src/main/java/com/example/android/yamsd/ArtistViewHolder.java index 4a394f0..65f9675 100644 --- a/app/src/main/java/com/example/android/yamsd/ArtistViewHolder.java +++ b/app/src/main/java/com/example/android/yamsd/ArtistViewHolder.java @@ -1,6 +1,5 @@ package com.example.android.yamsd; -import android.graphics.Bitmap; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageView; @@ -26,7 +25,6 @@ public class ArtistViewHolder { TextView description; - public ArtistViewHolder ( LayoutInflater inflater, String activity, @@ -44,7 +42,6 @@ public ArtistViewHolder ( } } - private void inflateViews(int layoutId) { rootView = inflater.inflate(layoutId, null); @@ -88,13 +85,7 @@ private void inflateViews(int layoutId) { } } - public View getRootView() { return rootView; } - - - public void setCoverBitmap(Bitmap coverBitmap) { - cover.setImageBitmap(coverBitmap); - } } diff --git a/app/src/main/java/com/example/android/yamsd/ArtistsCache.java b/app/src/main/java/com/example/android/yamsd/ArtistsCache.java index 2106c7a..d1b923d 100644 --- a/app/src/main/java/com/example/android/yamsd/ArtistsCache.java +++ b/app/src/main/java/com/example/android/yamsd/ArtistsCache.java @@ -1,7 +1,9 @@ package com.example.android.yamsd; import android.content.Context; +import android.os.AsyncTask; import android.util.Log; +import android.widget.Toast; import com.example.android.yamsd.ArtistsData.Artist; @@ -13,22 +15,36 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.net.URL; +import java.util.ArrayList; /** - * Кэш для хранения списка артистов в формате json + * Кэш для хранения списка артистов * Загрузка из файла реализована исключительно из соображений простоты реализации */ public class ArtistsCache { + + private static ArtistsCache artistsCache = null; + private String LOG_TAG = getClass().getSimpleName(); + private String siteWithArtists = + "http://cache-default01e.cdn.yandex.net/" + + "download.cdn.yandex.net/mobilization-2016/artists.json"; private String cacheFileName = "artistsDownloaded"; + private File cacheFile; private Context context; - File cacheFile; - String artistsJsonFormat = null; + private String artistsJsonFormat = null; + + private ArrayList artists = null; + private CacheAndListBuffer cacheAndListBuffer; - public ArtistsCache(Context context) { + public ArtistsCache( + Context context, + CacheAndListBuffer cacheAndListBuffer + ) { try { this.context = context; cacheFile = new File(context.getFilesDir(), cacheFileName); @@ -46,12 +62,14 @@ public ArtistsCache(Context context) { } cacheFile.createNewFile(); + + this.cacheAndListBuffer = cacheAndListBuffer; + } catch (IOException e) { Log.e(LOG_TAG, "Some troubles at constructor: " + e); } } - public void writeToCache(String string) { Log.v(LOG_TAG, "Writing to cache"); FileOutputStream outputStream = null; @@ -79,7 +97,6 @@ public void writeToCache(String string) { } } - public void readFromCache() { Log.v(LOG_TAG, "Reading from cache"); InputStream inputStream; @@ -108,12 +125,9 @@ public void readFromCache() { } } catch (IOException e) { Log.e(LOG_TAG, "IOException: " + e); - } catch (NullPointerException e) { - Log.e(LOG_TAG, "Null Pointer: " + e); } } - public boolean notExistsOrEmpty() { try { if (cacheFile.exists()) { @@ -133,11 +147,66 @@ public boolean notExistsOrEmpty() { return true; } + public void updateArtists() { + if (notExistsOrEmpty()) { + downloadArtistsFromCloud(); + } else { + artists = downloadArtistsFromCache(); + Log.v(LOG_TAG, "Loaded from cache"); + } + } + + public void downloadArtistsFromCloud() { + //Костыль, срабатывающий при самом первом запуске приложения + //Будет загрузка из данной ниже json-строки, а не из интернета + //или кэша + artists = Utility.getArtistsByDefault(); + Toast.makeText(context, "Обновление...", Toast.LENGTH_SHORT).show(); + new ArtistsLoadingTask().execute(siteWithArtists); + Log.v(LOG_TAG, "Loaded from internet"); + } - public Artist[] getArtistsFromCache() { + + public ArrayList downloadArtistsFromCache() { if (artistsJsonFormat == null) { readFromCache(); + } else { + artists = Utility.getArtists(artistsJsonFormat); } - return Utility.getArtists(artistsJsonFormat); + return artists; + } + + + public ArrayList getArtists() { + return artists; + } + + + private class ArtistsLoadingTask extends AsyncTask { + private final String LOG_TAG = getClass().getSimpleName(); + + @Override + protected String doInBackground(String... params) { + try { + return (String) Utility.downloadData(new URL(params[0])); + } catch (IOException e) { + Log.e(LOG_TAG, "IOException: " + e); + return null; + } + } + + + @Override + protected void onPostExecute(String result) { + super.onPostExecute(result); + + if (result != null) { + artists = Utility.getArtists(result); + writeToCache(result); + } + + cacheAndListBuffer.updateArtistsViewAdapter(artists); + } + } } diff --git a/app/src/main/java/com/example/android/yamsd/ArtistsData/Artist.java b/app/src/main/java/com/example/android/yamsd/ArtistsData/Artist.java index 2376129..562027f 100644 --- a/app/src/main/java/com/example/android/yamsd/ArtistsData/Artist.java +++ b/app/src/main/java/com/example/android/yamsd/ArtistsData/Artist.java @@ -40,6 +40,19 @@ public class Artist { private final String _bigCover = "big"; private final String _smallCover = "small"; + public Artist() { + + this.id = -1; + this.name = "Идет загрузка"; + + this.genres = new String[0]; + + this.tracksCount = 0; + this.albumsCount = 0; + + this.description = "Пожалуйста, подождите немного"; + } + public Artist(JSONObject jsonArtist) { try { this.id = jsonArtist.getInt(_id); diff --git a/app/src/main/java/com/example/android/yamsd/ArtistsListFragment.java b/app/src/main/java/com/example/android/yamsd/ArtistsListFragment.java new file mode 100644 index 0000000..06ff853 --- /dev/null +++ b/app/src/main/java/com/example/android/yamsd/ArtistsListFragment.java @@ -0,0 +1,125 @@ +package com.example.android.yamsd; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v7.app.AppCompatActivity; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ListView; + +/** + * Фрагмент со списком артистов + */ +public class ArtistsListFragment extends Fragment { + + private CacheAndListBuffer cacheAndListBuffer; + + public static ArtistsListFragment newInstance() { + return new ArtistsListFragment(); + } + + public ArtistsListFragment() { + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public void onResume() { + super.onResume(); + try { + ((AppCompatActivity) getActivity()) + .getSupportActionBar() + .setTitle("Исполнители"); + } catch (NullPointerException e) { + e.printStackTrace(); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_refresh: + cacheAndListBuffer.updateArtists(true); + return true; + case R.id.action_about: + AboutFragment aboutFragment = + AboutFragment.newInstance(); + + getFragmentManager() + .beginTransaction() + .replace( + R.id.fragment_container, + aboutFragment + ) + .addToBackStack(null) + .commit(); + + return true; + + case R.id.action_feedback: + EmailSender.sendMessage(getContext()); + return true; + + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + View listOfArtistsView = + inflater.inflate(R.layout.fragment_list_of_artists, container, false); + + cacheAndListBuffer = + CacheAndListBuffer + .getCacheAndListBuffer( + getActivity() + ); + + ListView listView = (ListView) listOfArtistsView.findViewById(R.id.artists_list); + listView.setAdapter(new ListOfArtistsAdapter( + getContext(), + R.layout.fragment_list_of_artists, + R.id.artists_list, + cacheAndListBuffer.getArtists() + )); + listView.setOnItemClickListener( + new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + + ArtistActivityFragment artistActivityFragment = + ArtistActivityFragment.newInstance(position); + + getFragmentManager() + .beginTransaction() + .replace( + R.id.fragment_container, + artistActivityFragment + ) + .addToBackStack(null) + .commit(); + } + } + ); + + return listOfArtistsView; + } + +} diff --git a/app/src/main/java/com/example/android/yamsd/CacheAndListBuffer.java b/app/src/main/java/com/example/android/yamsd/CacheAndListBuffer.java new file mode 100644 index 0000000..655f203 --- /dev/null +++ b/app/src/main/java/com/example/android/yamsd/CacheAndListBuffer.java @@ -0,0 +1,64 @@ +package com.example.android.yamsd; + +import android.app.Activity; +import android.widget.ListView; + +import com.example.android.yamsd.ArtistsData.Artist; + +import java.util.ArrayList; + +/** + * Вспомогательный класс, обеспечивающий связь между + * ArtistsListFragment и хранилищем артистов + */ +public class CacheAndListBuffer { + + private Activity activity; + private ArtistsCache artistsCache; + + private static CacheAndListBuffer cacheAndListBuffer; + + public synchronized static CacheAndListBuffer getCacheAndListBuffer ( + Activity activity + ) { + if (cacheAndListBuffer == null) { + cacheAndListBuffer = new CacheAndListBuffer(activity); + } + + return cacheAndListBuffer; + } + + private CacheAndListBuffer(Activity activity) { + + this.activity = activity; + //Создание списка артистов + artistsCache = new ArtistsCache(activity, this); + artistsCache.updateArtists(); + } + + public void updateArtistsViewAdapter( + ArrayList artists + ) { + ListOfArtistsAdapter listOfArtistsAdapter = (ListOfArtistsAdapter) + ((ListView) activity.findViewById(R.id.artists_list)).getAdapter(); + listOfArtistsAdapter.clear(); + for (Artist artist : artists) { + listOfArtistsAdapter.add(artist); + } + + listOfArtistsAdapter.notifyDataSetChanged(); + } + + public void updateArtists(boolean downloadArtists) { + if (downloadArtists) { + artistsCache.downloadArtistsFromCloud(); + } else { + artistsCache.updateArtists(); + } + } + + public ArrayList getArtists() { + return artistsCache.getArtists(); + } + +} diff --git a/app/src/main/java/com/example/android/yamsd/EmailSender.java b/app/src/main/java/com/example/android/yamsd/EmailSender.java new file mode 100644 index 0000000..db93637 --- /dev/null +++ b/app/src/main/java/com/example/android/yamsd/EmailSender.java @@ -0,0 +1,22 @@ +package com.example.android.yamsd; + +import android.content.Context; +import android.content.Intent; + +/** + * Класс, предназначенный для отправки Email-сообщений + */ +public class EmailSender { + private static final String[] EMAIL = {"lutsenko.yuriy@yandex.ru"}; + + public static void sendMessage(Context context) { + new Intent(Intent.ACTION_SEND) + .setType("message/rfc822") + .putExtra(Intent.EXTRA_EMAIL, EMAIL); + context.startActivity( + new Intent(Intent.ACTION_SEND) + .setType("message/rfc822") + .putExtra(Intent.EXTRA_EMAIL, EMAIL) + ); + } +} diff --git a/app/src/main/java/com/example/android/yamsd/HeadPhonesPluggedReceiver.java b/app/src/main/java/com/example/android/yamsd/HeadPhonesPluggedReceiver.java new file mode 100644 index 0000000..3f61853 --- /dev/null +++ b/app/src/main/java/com/example/android/yamsd/HeadPhonesPluggedReceiver.java @@ -0,0 +1,43 @@ +package com.example.android.yamsd; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +/** + * BroadcastReceiver вставленных наушников + */ +public class HeadPhonesPluggedReceiver extends BroadcastReceiver { + + NotificationReaction notificationReaction; + + public static HeadPhonesPluggedReceiver newReceiver( + NotificationReaction notificationReaction + ) { + return new HeadPhonesPluggedReceiver(notificationReaction); + } + + private HeadPhonesPluggedReceiver(NotificationReaction notificationReaction) { + this.notificationReaction = notificationReaction; + } + + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(Intent.ACTION_HEADSET_PLUG)) { + int state = intent.getIntExtra("state", -1); + + switch(state) { + case 0: + notificationReaction.stopNotification(); + break; + case 1: + notificationReaction.startNotification(); + break; + default: + Log.e("HeadPhonesPluggedRcvr", "Что не так с наушниками?"); + break; + } + } + } +} diff --git a/app/src/main/java/com/example/android/yamsd/ListOfArtistsActivity.java b/app/src/main/java/com/example/android/yamsd/ListOfArtistsActivity.java index 1373385..9e05d5b 100644 --- a/app/src/main/java/com/example/android/yamsd/ListOfArtistsActivity.java +++ b/app/src/main/java/com/example/android/yamsd/ListOfArtistsActivity.java @@ -1,5 +1,7 @@ package com.example.android.yamsd; +import android.content.Intent; +import android.content.IntentFilter; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; @@ -7,18 +9,61 @@ public class ListOfArtistsActivity extends AppCompatActivity { + private final String LOG_TAG = getClass().getSimpleName(); + + HeadPhonesPluggedReceiver headPhonesPluggedReceiver; + IntentFilter headPhonesIntentFilter; + + NotificationReaction notificationReaction; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_list_of_artists); + + notificationReaction = new NotificationReaction(this); + + headPhonesPluggedReceiver = + HeadPhonesPluggedReceiver.newReceiver(notificationReaction); + headPhonesIntentFilter = + new IntentFilter(Intent.ACTION_HEADSET_PLUG); + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); - } + //Начинается реализация сменяемого объекта + ArtistsListFragment artistsListFragment = + ArtistsListFragment.newInstance(); + + getSupportFragmentManager() + .beginTransaction() + .replace( + R.id.fragment_container, + artistsListFragment + ) + .commit(); + + } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_list_of_artists, menu); return true; } + + + @Override + protected void onResume() { + registerReceiver(headPhonesPluggedReceiver, headPhonesIntentFilter); + + super.onResume(); + } + + @Override + protected void onPause() { + notificationReaction.stopNotification(); + unregisterReceiver(headPhonesPluggedReceiver); + + super.onPause(); + } } diff --git a/app/src/main/java/com/example/android/yamsd/ListOfArtistsActivityFragment.java b/app/src/main/java/com/example/android/yamsd/ListOfArtistsActivityFragment.java deleted file mode 100644 index 971da68..0000000 --- a/app/src/main/java/com/example/android/yamsd/ListOfArtistsActivityFragment.java +++ /dev/null @@ -1,186 +0,0 @@ -package com.example.android.yamsd; - -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ListView; -import android.widget.Toast; - -import com.example.android.yamsd.ArtistsData.Artist; - -import java.io.IOException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Arrays; - -/** - * Фрагмент со списком артистов - */ -public class ListOfArtistsActivityFragment extends Fragment { - private final String LOG_TAG = getClass().getSimpleName(); - - private Artist[] artists = null; - private ListOfArtistsAdapter listOfArtistsAdapter; - - private String siteWithArtists = - "http://cache-default01e.cdn.yandex.net/" + - "download.cdn.yandex.net/mobilization-2016/artists.json"; - - private ArtistsCache artistsCache = null; - - - public ListOfArtistsActivityFragment() { - } - - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - } - - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - super.onCreateOptionsMenu(menu, inflater); - } - - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == R.id.action_refresh) { - updateArtists(true); - return true; - } - return super.onOptionsItemSelected(item); - } - - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - - View listOfArtistsView = - inflater.inflate(R.layout.fragment_list_of_artists, container, false); - - //Создание списка артистов - artistsCache = new ArtistsCache(getContext()); - updateArtists(artistsCache.notExistsOrEmpty()); - - ArrayList artistsList = new ArrayList<>(Arrays.asList(artists)); - listOfArtistsAdapter = - new ListOfArtistsAdapter( - getActivity(), - R.layout.single_artist_in_list, - R.id.single_artist_in_list, - artistsList - ); - - //Создание ListView, на элементы которой можно нажимать - ListView listView = (ListView) listOfArtistsView.findViewById(R.id.artists_list); - listView.setAdapter(listOfArtistsAdapter); - listView.setOnItemClickListener( - new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - Artist artist = artists[position]; - startActivity(createIntent(artist)); - } - } - ); - - return listOfArtistsView; - } - - - private Intent createIntent(Artist artist) { - Intent artistInfoIntent = new Intent(getActivity(), ArtistActivity.class); - - artistInfoIntent.putExtra("id", artist.getId()); - artistInfoIntent.putExtra("name", artist.getName()); - - artistInfoIntent.putExtra("genres", artist.getGenres()); - artistInfoIntent.putExtra("tracks", artist.getTracksCount()); - artistInfoIntent.putExtra("albums", artist.getAlbumsCount()); - - artistInfoIntent.putExtra("description", artist.getDescription()); - - artistInfoIntent.putExtra("bigCover", artist.getBigCoverUrlString()); - - return artistInfoIntent; - } - - - private void updateArtists(boolean refreshModeOn) { - if (refreshModeOn) { - //Костыль, срабатывающий при самом первом запуске приложения - //Будет загрузка из данной ниже json-строки, а не из интернета - //или кэша - artists = Utility.getArtists("[{\n" + - " \"id\": 0,\n" + - " \"name\": \"0\",\n" + - " \"genres\": [\n" + - " \"rnb\"\n" + - " ],\n" + - " \"tracks\": 0,\n" + - " \"albums\": 0,\n" + - " \"link\": \"\",\n" + - " \"description\": \"Nothing to say\",\n" + - " \"cover\": {\n" + - " \"small\": \"http://avatars.yandex.net/get-music-content/dfc531f5.p.1080505/300x300\",\n" + - " \"big\": \"http://avatars.yandex.net/get-music-content/dfc531f5.p.1080505/300x300\"\n" + - " }\n" + - " }]"); - Toast.makeText(getContext(), "Обновление...", Toast.LENGTH_SHORT).show(); - new ArtistsLoadingTask().execute(siteWithArtists); - Log.v(LOG_TAG, "Loaded from internet"); - } else { - artists = - artistsCache.getArtistsFromCache(); - Log.v(LOG_TAG, "Loaded from cache"); - } - } - - - private class ArtistsLoadingTask extends AsyncTask { - private final String LOG_TAG = getClass().getSimpleName(); - - @Override - protected String doInBackground(String... params) { - try { - return (String) Utility.downloadData(new URL(params[0]), "json"); - } catch (IOException e) { - Log.e(LOG_TAG, "IOException: " + e); - return null; - } - } - - - @Override - protected void onPostExecute(String result) { - super.onPostExecute(result); - artists = Utility.getArtists(result); - - if (result != null) { - - listOfArtistsAdapter.clear(); - for (Artist artist : artists) { - listOfArtistsAdapter.add(artist); - } - - artistsCache.writeToCache(result); - } - listOfArtistsAdapter.notifyDataSetChanged(); - } - - } -} diff --git a/app/src/main/java/com/example/android/yamsd/ListOfArtistsAdapter.java b/app/src/main/java/com/example/android/yamsd/ListOfArtistsAdapter.java index 664397f..9fe55a5 100644 --- a/app/src/main/java/com/example/android/yamsd/ListOfArtistsAdapter.java +++ b/app/src/main/java/com/example/android/yamsd/ListOfArtistsAdapter.java @@ -1,18 +1,14 @@ package com.example.android.yamsd; import android.content.Context; -import android.graphics.Bitmap; -import android.os.AsyncTask; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import com.example.android.yamsd.ArtistsData.Artist; +import com.squareup.picasso.Picasso; -import java.io.IOException; -import java.net.URL; import java.util.ArrayList; /** @@ -25,7 +21,6 @@ public class ListOfArtistsAdapter extends ArrayAdapter { private Context context; private ArrayList artists; - private SmallImageCache smallImageCache = null; public ListOfArtistsAdapter( @@ -38,7 +33,6 @@ public ListOfArtistsAdapter( this.context = context; this.artists = artists; - smallImageCache = new SmallImageCache(); } @@ -59,71 +53,24 @@ public long getItemId(int position) { @Override public View getView(int position, View convertView, ViewGroup parent) { - - try { - return loadListItem(artists.get(position)).getRootView(); - } catch (NullPointerException e) { - Log.e(LOG_TAG, "getRootView() works incorrectly: " + e); - } - - return null; + return loadListItem(artists.get(position)).getRootView(); } private ArtistViewHolder loadListItem(Artist artist) { - ArtistViewHolder viewHolder = + final ArtistViewHolder viewHolder = new ArtistViewHolder( LayoutInflater.from(context), "ListOfArtists", artist ); - new loadSmallCoverTask(viewHolder).execute(artist); - - return viewHolder; - } - - - private class loadSmallCoverTask - extends AsyncTask { - private String LOG_TAG = getClass().getSimpleName(); - - Artist artist; - ArtistViewHolder viewHolder; - public loadSmallCoverTask(ArtistViewHolder viewHolder) { - this.viewHolder = viewHolder; - } + Picasso + .with(context) + .load(artist.getSmallCoverUrlString()) + .into(viewHolder.cover); - - @Override - protected Bitmap doInBackground(Artist... params) { - - try { - artist = params[0]; - - Bitmap bitmap = - smallImageCache.getBitmapFromMemCache(String.valueOf(artist.getId())); - if (bitmap == null) { - bitmap = (Bitmap) Utility.downloadData( - new URL(params[0].getSmallCoverUrlString()), - "bitmap" - ); - smallImageCache.addBitmapToMemoryCache(String.valueOf(artist.getId()), bitmap); - } - - return bitmap; - } catch (IOException e) { - Log.e(LOG_TAG, "Error while loading image: " + e); - } - - return null; - } - - - @Override - protected void onPostExecute(Bitmap bitmap) { - viewHolder.setCoverBitmap(bitmap); - } + return viewHolder; } diff --git a/app/src/main/java/com/example/android/yamsd/NotificationReaction.java b/app/src/main/java/com/example/android/yamsd/NotificationReaction.java new file mode 100644 index 0000000..af860bd --- /dev/null +++ b/app/src/main/java/com/example/android/yamsd/NotificationReaction.java @@ -0,0 +1,92 @@ +package com.example.android.yamsd; + +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.support.v4.app.NotificationCompat; +import android.widget.RemoteViews; + +/** + * Класс, описывающий взаимодействие + * пользователя с нотификацией при воткнутых + * наушниках + */ +public class NotificationReaction { + + public static String MUSIC_APP = "yandexmusic://"; + public static String RADIO_APP = "yandexradio://"; + + public static String MUSIC_ID = "ru.yandex.music"; + public static String RADIO_ID = "ru.yandex.radio"; + + public static String MARKET = "market://details?id="; + + private Context context; + + private NotificationCompat.Builder builder; + private NotificationManager manager; + + RemoteViews remoteViews; + + + public NotificationReaction(Context context) { + this.context = context; + remoteViews = + new RemoteViews( + context.getPackageName(), + R.layout.notification_headset_plugged + ); + + builder = new NotificationCompat.Builder(context) + .setAutoCancel(true) + .setSmallIcon(R.drawable.icon_add) + .setContent(remoteViews); + + createPendingIntent(MUSIC_APP, MUSIC_ID, R.id.LISTEN_TO_MUSIC_BUTTON); + createPendingIntent(RADIO_APP, RADIO_ID, R.id.LISTEN_TO_RADIO_BUTTON); + + manager = + (NotificationManager) context + .getSystemService(Context.NOTIFICATION_SERVICE); + } + + + private void createPendingIntent( + String kindOfIntentForApp, + String packageName, + int buttonId + ) { + Intent intent = null; + PendingIntent musicPendingIntent = null; + + if (Utility.appInstalled(context, packageName)) { + intent = new Intent( + Intent.ACTION_VIEW, + Uri.parse(kindOfIntentForApp) + ); + } else { + intent = new Intent( + Intent.ACTION_VIEW, + Uri.parse(MARKET + packageName) + ); + } + + musicPendingIntent = + PendingIntent.getActivity(context, 0, intent, 0); + + remoteViews.setOnClickPendingIntent( + buttonId, + musicPendingIntent + ); + } + + public void startNotification() { + manager.notify(0, builder.build()); + } + + public void stopNotification() { + manager.cancelAll(); + } +} diff --git a/app/src/main/java/com/example/android/yamsd/SmallImageCache.java b/app/src/main/java/com/example/android/yamsd/SmallImageCache.java deleted file mode 100644 index 41846b9..0000000 --- a/app/src/main/java/com/example/android/yamsd/SmallImageCache.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.example.android.yamsd; - -import android.graphics.Bitmap; -import android.util.Log; -import android.util.LruCache; - -/** - * Кэш для сохранения маленьких картинок. - */ -public class SmallImageCache { - - private String LOG_TAG = getClass().getSimpleName(); - - private LruCache stringBitmapLruCache; - - final int maxMemory = (int) (Runtime.getRuntime().maxMemory()) / 1024; - final int cacheSize = maxMemory / 8; - - - public SmallImageCache() { - - stringBitmapLruCache = new LruCache(cacheSize) { - @Override - protected int sizeOf(String key, Bitmap bitmap) { - return bitmap.getByteCount() / 1024; - } - }; - } - - - public void addBitmapToMemoryCache(String key, Bitmap bitmap) { - try { - if (getBitmapFromMemCache(key) == null) { - stringBitmapLruCache.put(key, bitmap); - } - } catch (NullPointerException e) { - Log.e(LOG_TAG, "Unable to load image to cache"); - } - } - - - public Bitmap getBitmapFromMemCache(String key) { - return stringBitmapLruCache.get(key); - } -} diff --git a/app/src/main/java/com/example/android/yamsd/Utility.java b/app/src/main/java/com/example/android/yamsd/Utility.java index b51da2e..e0931d1 100644 --- a/app/src/main/java/com/example/android/yamsd/Utility.java +++ b/app/src/main/java/com/example/android/yamsd/Utility.java @@ -1,6 +1,7 @@ package com.example.android.yamsd; -import android.graphics.BitmapFactory; +import android.content.Context; +import android.content.pm.PackageManager; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; @@ -16,6 +17,8 @@ import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; /** * Вспомогательный класс, созданный для вынесения повторяющихся функций @@ -24,7 +27,6 @@ public class Utility { private static String LOG_TAG = "Utility"; - //Вспомогательные функции для создания View-шек @Nullable public static String pluralize(int amount, String word) { @@ -54,7 +56,6 @@ public static String pluralize(int amount, String word) { return null; } - @NonNull public static String getAlbumsAndTracksAsString( int albumsCount, @@ -81,35 +82,27 @@ public static String getAlbumsAndTracksAsString( .toString(); } - @Nullable public static String getGenresAsSingleString(String[] genres) { - try { - String stringSingleArtistGenres = ""; + String stringSingleArtistGenres = ""; - for (int i = 0; i < genres.length; i++) { - stringSingleArtistGenres += genres[i]; - if (i < genres.length - 1) { - stringSingleArtistGenres += ", "; - } + for (int i = 0; i < genres.length; i++) { + stringSingleArtistGenres += genres[i]; + if (i < genres.length - 1) { + stringSingleArtistGenres += ", "; } - - return stringSingleArtistGenres; - } catch (NullPointerException e) { - Log.e(LOG_TAG, "No data on genres"); } - return null; + return stringSingleArtistGenres; } - - //Функция для загрузки данных (картинок или списка исполнителей) из интернета - public static Object downloadData(URL pageWithData, String dataType) + //Функция для загрузки списка исполнителей из интернета + public static String downloadData(URL pageWithData) throws IOException { HttpURLConnection httpURLConnection = (HttpURLConnection)pageWithData.openConnection(); InputStream inputStream = null; - Object data = null; + String data = null; try { @@ -123,12 +116,7 @@ public static Object downloadData(URL pageWithData, String dataType) Log.v(LOG_TAG, "Response code: " + response); inputStream = httpURLConnection.getInputStream(); - //Сохранение - if (dataType.equals("bitmap")) { - data = BitmapFactory.decodeStream(inputStream); - } else if (dataType.equals("json")) { - data = readJsonString(inputStream); - } + data = readJsonString(inputStream); } finally { if (httpURLConnection != null) { httpURLConnection.disconnect(); @@ -145,7 +133,6 @@ public static Object downloadData(URL pageWithData, String dataType) return data; } - //Функции для считывания и переработки скачанной json-строки в список артистов public static String readJsonString(InputStream jsonStream) throws IOException { @@ -164,8 +151,7 @@ public static String readJsonString(InputStream jsonStream) return jsonString; } - - public static Artist[] getArtists(String jsonString) { + public static ArrayList getArtists(String jsonString) { try { JSONArray jsonArtists = new JSONArray(jsonString); @@ -175,13 +161,33 @@ public static Artist[] getArtists(String jsonString) { artistsList[i] = new Artist(jsonArtists.getJSONObject(i)); } - return artistsList; + return new ArrayList<>(Arrays.asList(artistsList)); } catch (JSONException e) { Log.e(LOG_TAG, "Incorrect JSON: " + e); - } catch (NullPointerException e) { - Log.e(LOG_TAG, "JSON not loaded properly: " + e); } return null; } + + public static ArrayList getArtistsByDefault() { + Artist artist = new Artist(); + ArrayList artists = new ArrayList<>(); + artists.add(artist); + + return artists; + } + + public static boolean appInstalled(Context context, String packageName) { + try { + context + .getPackageManager() + .getPackageInfo( + packageName, + PackageManager.GET_ACTIVITIES + ); + return true; + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } } diff --git a/app/src/main/res/layout/activity_artist.xml b/app/src/main/res/layout/activity_artist.xml deleted file mode 100644 index dd34511..0000000 --- a/app/src/main/res/layout/activity_artist.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/layout/activity_list_of_artists.xml b/app/src/main/res/layout/activity_list_of_artists.xml index 0021260..f9cb3c5 100644 --- a/app/src/main/res/layout/activity_list_of_artists.xml +++ b/app/src/main/res/layout/activity_list_of_artists.xml @@ -1,11 +1,13 @@ - + tools:context="com.example.android.yamsd.ListOfArtistsActivity" + android:orientation="vertical"> - + - + diff --git a/app/src/main/res/layout/content_artist.xml b/app/src/main/res/layout/content_artist.xml index cd70b3f..6af1352 100644 --- a/app/src/main/res/layout/content_artist.xml +++ b/app/src/main/res/layout/content_artist.xml @@ -6,4 +6,4 @@ android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" - tools:layout="@layout/fragment_artist" /> + tools:layout="@layout/fragment_artist" /> \ No newline at end of file diff --git a/app/src/main/res/layout/content_list_of_artists.xml b/app/src/main/res/layout/content_list_of_artists.xml index 1810135..63cce4d 100644 --- a/app/src/main/res/layout/content_list_of_artists.xml +++ b/app/src/main/res/layout/content_list_of_artists.xml @@ -1,8 +1,8 @@ + + + +